[pypy-commit] pypy pytest-update: sync pylib and pytest with the latest versions

RonnyPfannschmidt noreply at buildbot.pypy.org
Fri Feb 15 09:14:26 CET 2013


Author: Ronny Pfannschmidt <Ronny.Pfannschmidt at gmx.de>
Branch: pytest-update
Changeset: r61244:ca87ef6535e0
Date: 2013-02-15 09:13 +0100
http://bitbucket.org/pypy/pypy/changeset/ca87ef6535e0/

Log:	sync pylib and pytest with the latest versions

diff too long, truncating to 2000 out of 4863 lines

diff --git a/_pytest/__init__.py b/_pytest/__init__.py
--- a/_pytest/__init__.py
+++ b/_pytest/__init__.py
@@ -1,2 +1,2 @@
 #
-__version__ = '2.2.4.dev2'
+__version__ = '2.3.5dev6'
diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py
--- a/_pytest/assertion/__init__.py
+++ b/_pytest/assertion/__init__.py
@@ -50,7 +50,7 @@
     hook = None
     if mode == "rewrite":
         hook = rewrite.AssertionRewritingHook()
-        sys.meta_path.append(hook)
+        sys.meta_path.insert(0, hook)
     warn_about_missing_assertion(mode)
     config._assertstate = AssertionState(config, mode)
     config._assertstate.hook = hook
@@ -73,8 +73,12 @@
     def callbinrepr(op, left, right):
         hook_result = item.ihook.pytest_assertrepr_compare(
             config=item.config, op=op, left=left, right=right)
+
         for new_expl in hook_result:
             if new_expl:
+                # Don't include pageloads of data unless we are very verbose (-vv)
+                if len(''.join(new_expl[1:])) > 80*8 and item.config.option.verbose < 2:
+                    new_expl[1:] = ['Detailed information truncated, use "-vv" to see']
                 res = '\n~'.join(new_expl)
                 if item.config.getvalue("assertmode") == "rewrite":
                     # The result will be fed back a python % formatting
diff --git a/_pytest/assertion/newinterpret.py b/_pytest/assertion/newinterpret.py
--- a/_pytest/assertion/newinterpret.py
+++ b/_pytest/assertion/newinterpret.py
@@ -11,7 +11,7 @@
 from _pytest.assertion.reinterpret import BuiltinAssertionError
 
 
-if sys.platform.startswith("java") and sys.version_info < (2, 5, 2):
+if sys.platform.startswith("java"):
     # See http://bugs.jython.org/issue1497
     _exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
               "ListComp", "GeneratorExp", "Yield", "Compare", "Call",
diff --git a/_pytest/assertion/oldinterpret.py b/_pytest/assertion/oldinterpret.py
--- a/_pytest/assertion/oldinterpret.py
+++ b/_pytest/assertion/oldinterpret.py
@@ -526,10 +526,13 @@
     # example:
     def f():
         return 5
+
     def g():
         return 3
+
     def h(x):
         return 'never'
+
     check("f() * g() == 5")
     check("not f()")
     check("not (f() and g() or 0)")
diff --git a/_pytest/assertion/reinterpret.py b/_pytest/assertion/reinterpret.py
--- a/_pytest/assertion/reinterpret.py
+++ b/_pytest/assertion/reinterpret.py
@@ -44,4 +44,3 @@
     from _pytest.assertion.newinterpret import interpret as reinterpret
 else:
     reinterpret = reinterpret_old
-
diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py
--- a/_pytest/assertion/rewrite.py
+++ b/_pytest/assertion/rewrite.py
@@ -34,13 +34,13 @@
     PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
     del ver, impl
 
-PYC_EXT = ".py" + "c" if __debug__ else "o"
+PYC_EXT = ".py" + (__debug__ and "c" or "o")
 PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
 
 REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
 
 class AssertionRewritingHook(object):
-    """Import hook which rewrites asserts."""
+    """PEP302 Import hook which rewrites asserts."""
 
     def __init__(self):
         self.session = None
@@ -95,7 +95,8 @@
             finally:
                 self.session = sess
         else:
-            state.trace("matched test file (was specified on cmdline): %r" % (fn,))
+            state.trace("matched test file (was specified on cmdline): %r" %
+                        (fn,))
         # The requested module looks like a test file, so rewrite it. This is
         # the most magical part of the process: load the source, rewrite the
         # asserts, and load the rewritten source. We also cache the rewritten
@@ -121,14 +122,14 @@
                     # because we're in a zip file.
                     write = False
                 elif e == errno.EACCES:
-                    state.trace("read only directory: %r" % (fn_pypath.dirname,))
+                    state.trace("read only directory: %r" % fn_pypath.dirname)
                     write = False
                 else:
                     raise
         cache_name = fn_pypath.basename[:-3] + PYC_TAIL
         pyc = os.path.join(cache_dir, cache_name)
-        # Notice that even if we're in a read-only directory, I'm going to check
-        # for a cached pyc. This may not be optimal...
+        # Notice that even if we're in a read-only directory, I'm going
+        # to check for a cached pyc. This may not be optimal...
         co = _read_pyc(fn_pypath, pyc)
         if co is None:
             state.trace("rewriting %r" % (fn,))
@@ -160,10 +161,11 @@
         return sys.modules[name]
 
 def _write_pyc(co, source_path, pyc):
-    # Technically, we don't have to have the same pyc format as (C)Python, since
-    # these "pycs" should never be seen by builtin import. However, there's
-    # little reason deviate, and I hope sometime to be able to use
-    # imp.load_compiled to load them. (See the comment in load_module above.)
+    # Technically, we don't have to have the same pyc format as
+    # (C)Python, since these "pycs" should never be seen by builtin
+    # import. However, there's little reason deviate, and I hope
+    # sometime to be able to use imp.load_compiled to load them. (See
+    # the comment in load_module above.)
     mtime = int(source_path.mtime())
     try:
         fp = open(pyc, "wb")
@@ -240,9 +242,8 @@
         except EnvironmentError:
             return None
         # Check for invalid or out of date pyc file.
-        if (len(data) != 8 or
-            data[:4] != imp.get_magic() or
-            struct.unpack("<l", data[4:])[0] != mtime):
+        if (len(data) != 8 or data[:4] != imp.get_magic() or
+                struct.unpack("<l", data[4:])[0] != mtime):
             return None
         co = marshal.load(fp)
         if not isinstance(co, types.CodeType):
@@ -261,6 +262,9 @@
 _saferepr = py.io.saferepr
 from _pytest.assertion.util import format_explanation as _format_explanation
 
+def _should_repr_global_name(obj):
+    return not hasattr(obj, "__name__") and not py.builtin.callable(obj)
+
 def _format_boolop(explanations, is_or):
     return "(" + (is_or and " or " or " and ").join(explanations) + ")"
 
@@ -280,35 +284,35 @@
 
 
 unary_map = {
-    ast.Not : "not %s",
-    ast.Invert : "~%s",
-    ast.USub : "-%s",
-    ast.UAdd : "+%s"
+    ast.Not: "not %s",
+    ast.Invert: "~%s",
+    ast.USub: "-%s",
+    ast.UAdd: "+%s"
 }
 
 binop_map = {
-    ast.BitOr : "|",
-    ast.BitXor : "^",
-    ast.BitAnd : "&",
-    ast.LShift : "<<",
-    ast.RShift : ">>",
-    ast.Add : "+",
-    ast.Sub : "-",
-    ast.Mult : "*",
-    ast.Div : "/",
-    ast.FloorDiv : "//",
-    ast.Mod : "%",
-    ast.Eq : "==",
-    ast.NotEq : "!=",
-    ast.Lt : "<",
-    ast.LtE : "<=",
-    ast.Gt : ">",
-    ast.GtE : ">=",
-    ast.Pow : "**",
-    ast.Is : "is",
-    ast.IsNot : "is not",
-    ast.In : "in",
-    ast.NotIn : "not in"
+    ast.BitOr: "|",
+    ast.BitXor: "^",
+    ast.BitAnd: "&",
+    ast.LShift: "<<",
+    ast.RShift: ">>",
+    ast.Add: "+",
+    ast.Sub: "-",
+    ast.Mult: "*",
+    ast.Div: "/",
+    ast.FloorDiv: "//",
+    ast.Mod: "%%", # escaped for string formatting
+    ast.Eq: "==",
+    ast.NotEq: "!=",
+    ast.Lt: "<",
+    ast.LtE: "<=",
+    ast.Gt: ">",
+    ast.GtE: ">=",
+    ast.Pow: "**",
+    ast.Is: "is",
+    ast.IsNot: "is not",
+    ast.In: "in",
+    ast.NotIn: "not in"
 }
 
 
@@ -341,7 +345,7 @@
         lineno = 0
         for item in mod.body:
             if (expect_docstring and isinstance(item, ast.Expr) and
-                isinstance(item.value, ast.Str)):
+                    isinstance(item.value, ast.Str)):
                 doc = item.value.s
                 if "PYTEST_DONT_REWRITE" in doc:
                     # The module has disabled assertion rewriting.
@@ -462,7 +466,8 @@
         body.append(raise_)
         # Clear temporary variables by setting them to None.
         if self.variables:
-            variables = [ast.Name(name, ast.Store()) for name in self.variables]
+            variables = [ast.Name(name, ast.Store())
+                         for name in self.variables]
             clear = ast.Assign(variables, ast.Name("None", ast.Load()))
             self.statements.append(clear)
         # Fix line numbers.
@@ -471,11 +476,12 @@
         return self.statements
 
     def visit_Name(self, name):
-        # Check if the name is local or not.
+        # Display the repr of the name if it's a local variable or
+        # _should_repr_global_name() thinks it's acceptable.
         locs = ast.Call(self.builtin("locals"), [], [], None, None)
-        globs = ast.Call(self.builtin("globals"), [], [], None, None)
-        ops = [ast.In(), ast.IsNot()]
-        test = ast.Compare(ast.Str(name.id), ops, [locs, globs])
+        inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
+        dorepr = self.helper("should_repr_global_name", name)
+        test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
         expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
         return name, self.explanation_param(expr)
 
@@ -548,7 +554,8 @@
             new_kwarg, expl = self.visit(call.kwargs)
             arg_expls.append("**" + expl)
         expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
-        new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg)
+        new_call = ast.Call(new_func, new_args, new_kwargs,
+                            new_star, new_kwarg)
         res = self.assign(new_call)
         res_expl = self.explanation_param(self.display(res))
         outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
diff --git a/_pytest/assertion/util.py b/_pytest/assertion/util.py
--- a/_pytest/assertion/util.py
+++ b/_pytest/assertion/util.py
@@ -116,16 +116,11 @@
         excinfo = py.code.ExceptionInfo()
         explanation = ['(pytest_assertion plugin: representation of '
             'details failed. Probably an object has a faulty __repr__.)',
-            str(excinfo)
-            ]
-
+            str(excinfo)]
 
     if not explanation:
         return None
 
-    # Don't include pageloads of data, should be configurable
-    if len(''.join(explanation)) > 80*8:
-        explanation = ['Detailed information too verbose, truncated']
 
     return [summary] + explanation
 
diff --git a/_pytest/capture.py b/_pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -119,22 +119,20 @@
         return "", ""
 
     def activate_funcargs(self, pyfuncitem):
-        if not hasattr(pyfuncitem, 'funcargs'):
-            return
-        assert not hasattr(self, '_capturing_funcargs')
-        self._capturing_funcargs = capturing_funcargs = []
-        for name, capfuncarg in pyfuncitem.funcargs.items():
-            if name in ('capsys', 'capfd'):
-                capturing_funcargs.append(capfuncarg)
-                capfuncarg._start()
+        funcargs = getattr(pyfuncitem, "funcargs", None)
+        if funcargs is not None:
+            for name, capfuncarg in funcargs.items():
+                if name in ('capsys', 'capfd'):
+                    assert not hasattr(self, '_capturing_funcarg')
+                    self._capturing_funcarg = capfuncarg
+                    capfuncarg._start()
 
     def deactivate_funcargs(self):
-        capturing_funcargs = getattr(self, '_capturing_funcargs', None)
-        if capturing_funcargs is not None:
-            while capturing_funcargs:
-                capfuncarg = capturing_funcargs.pop()
-                capfuncarg._finalize()
-            del self._capturing_funcargs
+        capturing_funcarg = getattr(self, '_capturing_funcarg', None)
+        if capturing_funcarg:
+            outerr = capturing_funcarg._finalize()
+            del self._capturing_funcarg
+            return outerr
 
     def pytest_make_collect_report(self, __multicall__, collector):
         method = self._getmethod(collector.config, collector.fspath)
@@ -169,9 +167,12 @@
 
     @pytest.mark.tryfirst
     def pytest_runtest_makereport(self, __multicall__, item, call):
-        self.deactivate_funcargs()
+        funcarg_outerr = self.deactivate_funcargs()
         rep = __multicall__.execute()
         outerr = self.suspendcapture(item)
+        if funcarg_outerr is not None:
+            outerr = (outerr[0] + funcarg_outerr[0],
+                      outerr[1] + funcarg_outerr[1])
         if not rep.passed:
             addouterr(rep, outerr)
         if not rep.passed or rep.when == "teardown":
@@ -179,23 +180,29 @@
         item.outerr = outerr
         return rep
 
+error_capsysfderror = "cannot use capsys and capfd at the same time"
+
 def pytest_funcarg__capsys(request):
     """enables capturing of writes to sys.stdout/sys.stderr and makes
     captured output available via ``capsys.readouterr()`` method calls
     which return a ``(out, err)`` tuple.
     """
-    return CaptureFuncarg(py.io.StdCapture)
+    if "capfd" in request._funcargs:
+        raise request.raiseerror(error_capsysfderror)
+    return CaptureFixture(py.io.StdCapture)
 
 def pytest_funcarg__capfd(request):
     """enables capturing of writes to file descriptors 1 and 2 and makes
     captured output available via ``capsys.readouterr()`` method calls
     which return a ``(out, err)`` tuple.
     """
+    if "capsys" in request._funcargs:
+        request.raiseerror(error_capsysfderror)
     if not hasattr(os, 'dup'):
-        py.test.skip("capfd funcarg needs os.dup")
-    return CaptureFuncarg(py.io.StdCaptureFD)
+        pytest.skip("capfd funcarg needs os.dup")
+    return CaptureFixture(py.io.StdCaptureFD)
 
-class CaptureFuncarg:
+class CaptureFixture:
     def __init__(self, captureclass):
         self.capture = captureclass(now=False)
 
@@ -204,11 +211,15 @@
 
     def _finalize(self):
         if hasattr(self, 'capture'):
-            self.capture.reset()
+            outerr = self._outerr = self.capture.reset()
             del self.capture
+            return outerr
 
     def readouterr(self):
-        return self.capture.readouterr()
+        try:
+            return self.capture.readouterr()
+        except AttributeError:
+            return self._outerr
 
     def close(self):
         self._finalize()
diff --git a/_pytest/config.py b/_pytest/config.py
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -19,7 +19,7 @@
         fin()
 
 class Parser:
-    """ Parser for command line arguments. """
+    """ Parser for command line arguments and ini-file values.  """
 
     def __init__(self, usage=None, processopt=None):
         self._anonymous = OptionGroup("custom options", parser=self)
@@ -35,15 +35,17 @@
             if option.dest:
                 self._processopt(option)
 
-    def addnote(self, note):
-        self._notes.append(note)
-
     def getgroup(self, name, description="", after=None):
         """ get (or create) a named option Group.
 
-        :name: unique name of the option group.
+        :name: name of the option group.
         :description: long description for --help output.
         :after: name of other group, used for ordering --help output.
+
+        The returned group object has an ``addoption`` method with the same
+        signature as :py:func:`parser.addoption
+        <_pytest.config.Parser.addoption>` but will be shown in the
+        respective group in the output of ``pytest. --help``.
         """
         for group in self._groups:
             if group.name == name:
@@ -57,7 +59,19 @@
         return group
 
     def addoption(self, *opts, **attrs):
-        """ add an optparse-style option. """
+        """ register a command line option.
+
+        :opts: option names, can be short or long options.
+        :attrs: same attributes which the ``add_option()`` function of the
+           `optparse library
+           <http://docs.python.org/library/optparse.html#module-optparse>`_
+           accepts.
+
+        After command line parsing options are available on the pytest config
+        object via ``config.option.NAME`` where ``NAME`` is usually set
+        by passing a ``dest`` attribute, for example
+        ``addoption("--long", dest="NAME", ...)``.
+        """
         self._anonymous.addoption(*opts, **attrs)
 
     def parse(self, args):
@@ -78,7 +92,15 @@
         return args
 
     def addini(self, name, help, type=None, default=None):
-        """ add an ini-file option with the given name and description. """
+        """ register an ini-file option.
+
+        :name: name of the ini-variable
+        :type: type of the variable, can be ``pathlist``, ``args`` or ``linelist``.
+        :default: default value if no ini-file option exists but is queried.
+
+        The value of ini-variables can be retrieved via a call to
+        :py:func:`config.getini(name) <_pytest.config.Config.getini>`.
+        """
         assert type in (None, "pathlist", "args", "linelist")
         self._inidict[name] = (help, type, default)
         self._ininames.append(name)
@@ -154,20 +176,24 @@
                     p = current.join(opt1[len(opt)+1:], abs=1)
                 self._confcutdir = p
                 break
-        for arg in args + [current]:
+        foundanchor = False
+        for arg in args:
             if hasattr(arg, 'startswith') and arg.startswith("--"):
                 continue
             anchor = current.join(arg, abs=1)
-            if anchor.check(): # we found some file object
-                self._path2confmods[None] = self.getconftestmodules(anchor)
-                # let's also consider test* dirs
-                if anchor.check(dir=1):
-                    for x in anchor.listdir("test*"):
-                        if x.check(dir=1):
-                            self.getconftestmodules(x)
-                break
-        else:
-            assert 0, "no root of filesystem?"
+            if exists(anchor): # we found some file object
+                self._try_load_conftest(anchor)
+                foundanchor = True
+        if not foundanchor:
+            self._try_load_conftest(current)
+
+    def _try_load_conftest(self, anchor):
+        self._path2confmods[None] = self.getconftestmodules(anchor)
+        # let's also consider test* subdirs
+        if anchor.check(dir=1):
+            for x in anchor.listdir("test*"):
+                if x.check(dir=1):
+                    self.getconftestmodules(x)
 
     def getconftestmodules(self, path):
         """ return a list of imported conftest modules for the given path.  """
@@ -245,8 +271,8 @@
 class Config(object):
     """ access to configuration values, pluginmanager and plugin hooks.  """
     def __init__(self, pluginmanager=None):
-        #: command line option values, usually added via parser.addoption(...)
-        #: or parser.getgroup(...).addoption(...) calls
+        #: access to command line option as attributes.
+        #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
         self.option = CmdOptions()
         self._parser = Parser(
             usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
@@ -258,6 +284,7 @@
         self._conftest = Conftest(onimport=self._onimportconftest)
         self.hook = self.pluginmanager.hook
         self._inicache = {}
+        self._opt2dest = {}
         self._cleanup = []
 
     @classmethod
@@ -278,6 +305,9 @@
         self.pluginmanager.consider_conftest(conftestmodule)
 
     def _processopt(self, opt):
+        for name in opt._short_opts + opt._long_opts:
+            self._opt2dest[name] = opt.dest
+
         if hasattr(opt, 'default') and opt.dest:
             if not hasattr(self.option, opt.dest):
                 setattr(self.option, opt.dest, opt.default)
@@ -356,8 +386,9 @@
         x.append(line) # modifies the cached list inline
 
     def getini(self, name):
-        """ return configuration value from an ini file. If the
-        specified name hasn't been registered through a prior ``parse.addini``
+        """ return configuration value from an :ref:`ini file <inifiles>`. If the
+        specified name hasn't been registered through a prior
+        :py:func:`parser.addini <pytest.config.Parser.addini>`
         call (usually from a plugin), a ValueError is raised. """
         try:
             return self._inicache[name]
@@ -411,8 +442,22 @@
             self._checkconftest(name)
         return self._conftest.rget(name, path)
 
+    def getoption(self, name):
+        """ return command line option value.
+
+        :arg name: name of the option.  You may also specify
+            the literal ``--OPT`` option instead of the "dest" option name.
+        """
+        name = self._opt2dest.get(name, name)
+        try:
+            return getattr(self.option, name)
+        except AttributeError:
+            raise ValueError("no option named %r" % (name,))
+
     def getvalue(self, name, path=None):
-        """ return ``name`` value looked set from command line options.
+        """ return command line option value.
+
+        :arg name: name of the command line option
 
         (deprecated) if we can't find the option also lookup
         the name in a matching conftest file.
@@ -434,6 +479,11 @@
         except KeyError:
             py.test.skip("no %r value found" %(name,))
 
+def exists(path, ignore=EnvironmentError):
+    try:
+        return path.check()
+    except ignore:
+        return False
 
 def getcfg(args, inibasenames):
     args = [x for x in args if not str(x).startswith("-")]
@@ -444,20 +494,9 @@
         for base in arg.parts(reverse=True):
             for inibasename in inibasenames:
                 p = base.join(inibasename)
-                if p.check():
+                if exists(p):
                     iniconfig = py.iniconfig.IniConfig(p)
                     if 'pytest' in iniconfig.sections:
                         return iniconfig['pytest']
     return {}
 
-def findupwards(current, basename):
-    current = py.path.local(current)
-    while 1:
-        p = current.join(basename)
-        if p.check():
-            return p
-        p = current.dirpath()
-        if p == current:
-            return
-        current = p
-
diff --git a/_pytest/core.py b/_pytest/core.py
--- a/_pytest/core.py
+++ b/_pytest/core.py
@@ -24,12 +24,28 @@
     def get(self, name):
         return TagTracerSub(self, (name,))
 
+    def format_message(self, tags, args):
+        if isinstance(args[-1], dict):
+            extra = args[-1]
+            args = args[:-1]
+        else:
+            extra = {}
+
+        content = " ".join(map(str, args))
+        indent = "  " * self.indent
+
+        lines = [
+            "%s%s [%s]\n" %(indent, content, ":".join(tags))
+        ]
+
+        for name, value in extra.items():
+            lines.append("%s    %s: %s\n" % (indent, name, value))
+        return lines
+
     def processmessage(self, tags, args):
-        if self.writer is not None:
-            if args:
-                indent = "  " * self.indent
-                content = " ".join(map(str, args))
-                self.writer("%s%s [%s]\n" %(indent, content, ":".join(tags)))
+        if self.writer is not None and args:
+            lines = self.format_message(tags, args)
+            self.writer(''.join(lines))
         try:
             self._tag2proc[tags](tags, args)
         except KeyError:
@@ -79,10 +95,11 @@
                 self.import_plugin(spec)
 
     def register(self, plugin, name=None, prepend=False):
-        assert not self.isregistered(plugin), plugin
+        if self._name2plugin.get(name, None) == -1:
+            return
         name = name or getattr(plugin, '__name__', str(id(plugin)))
-        if name in self._name2plugin:
-            return False
+        if self.isregistered(plugin, name):
+            raise ValueError("Plugin already registered: %s=%s" %(name, plugin))
         #self.trace("registering", name, plugin)
         self._name2plugin[name] = plugin
         self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
@@ -321,13 +338,18 @@
     name = importspec
     try:
         mod = "_pytest." + name
-        return __import__(mod, None, None, '__doc__')
+        __import__(mod)
+        return sys.modules[mod]
     except ImportError:
         #e = py.std.sys.exc_info()[1]
         #if str(e).find(name) == -1:
         #    raise
         pass #
-    return __import__(importspec, None, None, '__doc__')
+    try:
+        __import__(importspec)
+    except ImportError:
+        raise ImportError(importspec)
+    return sys.modules[importspec]
 
 class MultiCall:
     """ execute a call into multiple python functions/methods. """
@@ -460,16 +482,15 @@
             pluginmanager=_pluginmanager, args=args)
 
 def main(args=None, plugins=None):
-    """ returned exit code integer, after an in-process testing run
-    with the given command line arguments, preloading an optional list
-    of passed in plugin objects. """
-    try:
-        config = _prepareconfig(args, plugins)
-        exitstatus = config.hook.pytest_cmdline_main(config=config)
-    except UsageError:
-        e = sys.exc_info()[1]
-        sys.stderr.write("ERROR: %s\n" %(e.args[0],))
-        exitstatus = 3
+    """ return exit code, after performing an in-process test run.
+
+    :arg args: list of command line arguments.
+
+    :arg plugins: list of plugin objects to be auto-registered during
+                  initialization.
+    """
+    config = _prepareconfig(args, plugins)
+    exitstatus = config.hook.pytest_cmdline_main(config=config)
     return exitstatus
 
 class UsageError(Exception):
diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py
--- a/_pytest/helpconfig.py
+++ b/_pytest/helpconfig.py
@@ -79,6 +79,8 @@
 
     tw.line() ; tw.line()
     #tw.sep("=")
+    tw.line("to see available markers type: py.test --markers")
+    tw.line("to see available fixtures type: py.test --fixtures")
     return
 
     tw.line("conftest.py options:")
diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -23,8 +23,28 @@
     """modify command line arguments before option parsing. """
 
 def pytest_addoption(parser):
-    """add optparse-style options and ini-style config values via calls
-    to ``parser.addoption`` and ``parser.addini(...)``.
+    """register optparse-style options and ini-style config values.
+
+    This function must be implemented in a :ref:`plugin <pluginorder>` and is
+    called once at the beginning of a test run.
+
+    :arg parser: To add command line options, call
+        :py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
+        To add ini-file values call :py:func:`parser.addini(...)
+        <_pytest.config.Parser.addini>`.
+
+    Options can later be accessed through the
+    :py:class:`config <_pytest.config.Config>` object, respectively:
+
+    - :py:func:`config.getoption(name) <_pytest.config.Config.getoption>` to
+      retrieve the value of a command line option.
+
+    - :py:func:`config.getini(name) <_pytest.config.Config.getini>` to retrieve
+      a value read from an ini-style file.
+
+    The config object is passed around on many internal objects via the ``.config``
+    attribute or can be retrieved as the ``pytestconfig`` fixture or accessed
+    via (deprecated) ``pytest.config``.
     """
 
 def pytest_cmdline_main(config):
@@ -33,7 +53,7 @@
 pytest_cmdline_main.firstresult = True
 
 def pytest_configure(config):
-    """ called after command line options have been parsed.
+    """ called after command line options have been parsed
         and all plugins and initial conftest files been loaded.
     """
 
@@ -193,7 +213,7 @@
 # hooks for influencing reporting (invoked from _pytest_terminal)
 # -------------------------------------------------------------------------
 
-def pytest_report_header(config):
+def pytest_report_header(config, startdir):
     """ return a string to be displayed as header info for terminal reporting."""
 
 def pytest_report_teststatus(report):
diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py
--- a/_pytest/junitxml.py
+++ b/_pytest/junitxml.py
@@ -57,7 +57,7 @@
             return unicode('#x%02X') % i
         else:
             return unicode('#x%04X') % i
-    return illegal_xml_re.sub(repl, py.xml.escape(arg))
+    return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
 
 def pytest_addoption(parser):
     group = parser.getgroup("terminal reporting")
@@ -81,19 +81,22 @@
         config.pluginmanager.unregister(xml)
 
 
+def mangle_testnames(names):
+    names = [x.replace(".py", "") for x in names if x != '()']
+    names[0] = names[0].replace("/", '.')
+    return names
+
 class LogXML(object):
     def __init__(self, logfile, prefix):
         logfile = os.path.expanduser(os.path.expandvars(logfile))
-        self.logfile = os.path.normpath(logfile)
+        self.logfile = os.path.normpath(os.path.abspath(logfile))
         self.prefix = prefix
         self.tests = []
         self.passed = self.skipped = 0
         self.failed = self.errors = 0
 
     def _opentestcase(self, report):
-        names = report.nodeid.split("::")
-        names[0] = names[0].replace("/", '.')
-        names = [x.replace(".py", "") for x in names if x != "()"]
+        names = mangle_testnames(report.nodeid.split("::"))
         classnames = names[:-1]
         if self.prefix:
             classnames.insert(0, self.prefix)
@@ -111,7 +114,7 @@
 
     def append_failure(self, report):
         #msg = str(report.longrepr.reprtraceback.extraline)
-        if "xfail" in report.keywords:
+        if hasattr(report, "wasxfail"):
             self.append(
                 Junit.skipped(message="xfail-marked test passes unexpectedly"))
             self.skipped += 1
@@ -145,8 +148,8 @@
         self.errors += 1
 
     def append_skipped(self, report):
-        if "xfail" in report.keywords:
-            self.append(Junit.skipped(str(report.keywords['xfail']),
+        if hasattr(report, "wasxfail"):
+            self.append(Junit.skipped(str(report.wasxfail),
                                       message="expected test failure"))
         else:
             filename, lineno, skipreason = report.longrepr
diff --git a/_pytest/main.py b/_pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -2,7 +2,15 @@
 
 import py
 import pytest, _pytest
+import inspect
 import os, sys, imp
+try:
+    from collections import MutableMapping as MappingMixin
+except ImportError:
+    from UserDict import DictMixin as MappingMixin
+
+from _pytest.mark import MarkInfo
+
 tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
 
 # exitcodes for the command line
@@ -10,6 +18,7 @@
 EXIT_TESTSFAILED = 1
 EXIT_INTERRUPTED = 2
 EXIT_INTERNALERROR = 3
+EXIT_USAGEERROR = 4
 
 name_re = py.std.re.compile("^[a-zA-Z_]\w*$")
 
@@ -28,7 +37,6 @@
     group._addoption('--maxfail', metavar="num",
                action="store", type="int", dest="maxfail", default=0,
                help="exit after first num failures or errors.")
-
     group._addoption('--strict', action="store_true",
                help="run pytest in strict mode, warnings become errors.")
 
@@ -65,30 +73,36 @@
     session.exitstatus = EXIT_OK
     initstate = 0
     try:
-        config.pluginmanager.do_configure(config)
-        initstate = 1
-        config.hook.pytest_sessionstart(session=session)
-        initstate = 2
-        doit(config, session)
-    except pytest.UsageError:
-        raise
-    except KeyboardInterrupt:
-        excinfo = py.code.ExceptionInfo()
-        config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
-        session.exitstatus = EXIT_INTERRUPTED
-    except:
-        excinfo = py.code.ExceptionInfo()
-        config.pluginmanager.notify_exception(excinfo, config.option)
-        session.exitstatus = EXIT_INTERNALERROR
-        if excinfo.errisinstance(SystemExit):
-            sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
-    if initstate >= 2:
-        config.hook.pytest_sessionfinish(session=session,
-            exitstatus=session.exitstatus or (session._testsfailed and 1))
-    if not session.exitstatus and session._testsfailed:
-        session.exitstatus = EXIT_TESTSFAILED
-    if initstate >= 1:
-        config.pluginmanager.do_unconfigure(config)
+        try:
+            config.pluginmanager.do_configure(config)
+            initstate = 1
+            config.hook.pytest_sessionstart(session=session)
+            initstate = 2
+            doit(config, session)
+        except pytest.UsageError:
+            msg = sys.exc_info()[1].args[0]
+            sys.stderr.write("ERROR: %s\n" %(msg,))
+            session.exitstatus = EXIT_USAGEERROR
+        except KeyboardInterrupt:
+            excinfo = py.code.ExceptionInfo()
+            config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
+            session.exitstatus = EXIT_INTERRUPTED
+        except:
+            excinfo = py.code.ExceptionInfo()
+            config.pluginmanager.notify_exception(excinfo, config.option)
+            session.exitstatus = EXIT_INTERNALERROR
+            if excinfo.errisinstance(SystemExit):
+                sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
+        else:
+            if session._testsfailed:
+                session.exitstatus = EXIT_TESTSFAILED
+    finally:
+        if initstate >= 2:
+            config.hook.pytest_sessionfinish(
+                session=session,
+                exitstatus=session.exitstatus)
+        if initstate >= 1:
+            config.pluginmanager.do_unconfigure(config)
     return session.exitstatus
 
 def pytest_cmdline_main(config):
@@ -106,11 +120,18 @@
 def pytest_runtestloop(session):
     if session.config.option.collectonly:
         return True
+
+    def getnextitem(i):
+        # this is a function to avoid python2
+        # keeping sys.exc_info set when calling into a test
+        # python2 keeps sys.exc_info till the frame is left
+        try:
+            return session.items[i+1]
+        except IndexError:
+            return None
+
     for i, item in enumerate(session.items):
-        try:
-            nextitem = session.items[i+1]
-        except IndexError:
-            nextitem = None
+        nextitem = getnextitem(i)
         item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
         if session.shouldstop:
             raise session.Interrupted(session.shouldstop)
@@ -129,8 +150,10 @@
     def __init__(self, fspath, config):
         self.fspath = fspath
         self.config = config
+
     def __getattr__(self, name):
         hookmethod = getattr(self.config.hook, name)
+
         def call_matching_hooks(**kwargs):
             plugins = self.config._getmatchingplugins(self.fspath)
             return hookmethod.pcall(plugins, **kwargs)
@@ -138,31 +161,71 @@
 
 def compatproperty(name):
     def fget(self):
+        # deprecated - use pytest.name
         return getattr(pytest, name)
-    return property(fget, None, None,
-        "deprecated attribute %r, use pytest.%s" % (name,name))
+
+    return property(fget)
+
+class NodeKeywords(MappingMixin):
+    def __init__(self, node):
+        parent = node.parent
+        bases = parent and (parent.keywords._markers,) or ()
+        self._markers = type("dynmarker", bases, {node.name: True})
+
+    def __getitem__(self, key):
+        try:
+            return getattr(self._markers, key)
+        except AttributeError:
+            raise KeyError(key)
+
+    def __setitem__(self, key, value):
+        setattr(self._markers, key, value)
+
+    def __delitem__(self, key):
+        delattr(self._markers, key)
+
+    def __iter__(self):
+        return iter(self.keys())
+
+    def __len__(self):
+        return len(self.keys())
+
+    def keys(self):
+        return dir(self._markers)
 
 class Node(object):
-    """ base class for all Nodes in the collection tree.
+    """ base class for Collector and Item the test collection tree.
     Collector subclasses have children, Items are terminal nodes."""
 
     def __init__(self, name, parent=None, config=None, session=None):
-        #: a unique name with the scope of the parent
+        #: a unique name within the scope of the parent node
         self.name = name
 
         #: the parent collector node.
         self.parent = parent
 
-        #: the test config object
+        #: the pytest config object
         self.config = config or parent.config
 
-        #: the collection this node is part of
+        #: the session this node is part of
         self.session = session or parent.session
 
-        #: filesystem path where this node was collected from
+        #: filesystem path where this node was collected from (can be None)
         self.fspath = getattr(parent, 'fspath', None)
-        self.ihook = self.session.gethookproxy(self.fspath)
-        self.keywords = {self.name: True}
+
+        #: keywords/markers collected from all scopes
+        self.keywords = NodeKeywords(self)
+
+        #self.extrainit()
+
+    @property
+    def ihook(self):
+        """ fspath sensitive hook proxy used to call pytest hooks"""
+        return self.session.gethookproxy(self.fspath)
+
+    #def extrainit(self):
+    #    """"extra initialization after Node is initialized.  Implemented
+    #    by some subclasses. """
 
     Module = compatproperty("Module")
     Class = compatproperty("Class")
@@ -180,25 +243,28 @@
         return cls
 
     def __repr__(self):
-        return "<%s %r>" %(self.__class__.__name__, getattr(self, 'name', None))
+        return "<%s %r>" %(self.__class__.__name__,
+                           getattr(self, 'name', None))
 
     # methods for ordering nodes
     @property
     def nodeid(self):
+        """ a ::-separated string denoting its collection tree address. """
         try:
             return self._nodeid
         except AttributeError:
             self._nodeid = x = self._makeid()
             return x
 
+
     def _makeid(self):
         return self.parent.nodeid + "::" + self.name
 
     def __eq__(self, other):
         if not isinstance(other, Node):
             return False
-        return self.__class__ == other.__class__ and \
-               self.name == other.name and self.parent == other.parent
+        return (self.__class__ == other.__class__ and
+                self.name == other.name and self.parent == other.parent)
 
     def __ne__(self, other):
         return not self == other
@@ -257,6 +323,9 @@
         pass
 
     def _repr_failure_py(self, excinfo, style=None):
+        fm = self.session._fixturemanager
+        if excinfo.errisinstance(fm.FixtureLookupError):
+            return excinfo.value.formatrepr()
         if self.config.option.fulltrace:
             style="long"
         else:
@@ -365,9 +434,9 @@
         __module__ = 'builtins' # for py3
 
     def __init__(self, config):
-        super(Session, self).__init__(py.path.local(), parent=None,
-            config=config, session=self)
-        assert self.config.pluginmanager.register(self, name="session", prepend=True)
+        FSCollector.__init__(self, py.path.local(), parent=None,
+                             config=config, session=self)
+        self.config.pluginmanager.register(self, name="session", prepend=True)
         self._testsfailed = 0
         self.shouldstop = False
         self.trace = config.trace.root.get("collection")
@@ -378,7 +447,7 @@
             raise self.Interrupted(self.shouldstop)
 
     def pytest_runtest_logreport(self, report):
-        if report.failed and 'xfail' not in getattr(report, 'keywords', []):
+        if report.failed and not hasattr(report, 'wasxfail'):
             self._testsfailed += 1
             maxfail = self.config.getvalue("maxfail")
             if maxfail and self._testsfailed >= maxfail:
@@ -453,7 +522,7 @@
         if path.check(dir=1):
             assert not names, "invalid arg %r" %(arg,)
             for path in path.visit(fil=lambda x: x.check(file=1),
-                rec=self._recurse, bf=True, sort=True):
+                                   rec=self._recurse, bf=True, sort=True):
                 for x in self._collectfile(path):
                     yield x
         else:
@@ -465,13 +534,13 @@
         ihook = self.gethookproxy(path)
         if not self.isinitpath(path):
             if ihook.pytest_ignore_collect(path=path, config=self.config):
-               return ()
+                return ()
         return ihook.pytest_collect_file(path=path, parent=self)
 
     def _recurse(self, path):
         ihook = self.gethookproxy(path.dirpath())
         if ihook.pytest_ignore_collect(path=path, config=self.config):
-           return
+            return
         for pat in self._norecursepatterns:
             if path.check(fnmatch=pat):
                 return False
@@ -574,3 +643,12 @@
                     for x in self.genitems(subnode):
                         yield x
             node.ihook.pytest_collectreport(report=rep)
+
+def getfslineno(obj):
+    # xxx let decorators etc specify a sane ordering
+    if hasattr(obj, 'place_as'):
+        obj = obj.place_as
+    fslineno = py.code.getfslineno(obj)
+    assert isinstance(fslineno[1], int), obj
+    return fslineno
+
diff --git a/_pytest/mark.py b/_pytest/mark.py
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -8,11 +8,12 @@
     group = parser.getgroup("general")
     group._addoption('-k',
         action="store", dest="keyword", default='', metavar="KEYWORDEXPR",
-        help="only run tests which match given keyword expression.  "
-             "An expression consists of space-separated terms. "
-             "Each term must match. Precede a term with '-' to negate. "
-             "Terminate expression with ':' to make the first match match "
-             "all subsequent tests (usually file-order). ")
+        help="only run tests which match the given expression. "
+             "An expression is a python evaluatable expression "
+             "where all names are substring-matched against test names "
+             "and keywords.  Example: -k 'test_method or test_other' "
+             "matches all test functions whose name contains "
+             "'test_method' or 'test_other'.")
 
     group._addoption("-m",
         action="store", dest="markexpr", default="", metavar="MARKEXPR",
@@ -51,7 +52,7 @@
     remaining = []
     deselected = []
     for colitem in items:
-        if keywordexpr and skipbykeyword(colitem, keywordexpr):
+        if keywordexpr and not matchkeyword(colitem, keywordexpr):
             deselected.append(colitem)
         else:
             if selectuntil:
@@ -72,45 +73,26 @@
     def __getitem__(self, name):
         return name in self._mydict
 
+class SubstringDict:
+    def __init__(self, mydict):
+        self._mydict = mydict
+    def __getitem__(self, name):
+        for key in self._mydict:
+            if name in key:
+                return True
+        return False
+
 def matchmark(colitem, matchexpr):
-    return eval(matchexpr, {}, BoolDict(colitem.obj.__dict__))
+    return eval(matchexpr, {}, BoolDict(colitem.keywords))
+
+def matchkeyword(colitem, keywordexpr):
+    keywordexpr = keywordexpr.replace("-", "not ")
+    return eval(keywordexpr, {}, SubstringDict(colitem.keywords))
 
 def pytest_configure(config):
     if config.option.strict:
         pytest.mark._config = config
 
-def skipbykeyword(colitem, keywordexpr):
-    """ return True if they given keyword expression means to
-        skip this collector/item.
-    """
-    if not keywordexpr:
-        return
-
-    itemkeywords = getkeywords(colitem)
-    for key in filter(None, keywordexpr.split()):
-        eor = key[:1] == '-'
-        if eor:
-            key = key[1:]
-        if not (eor ^ matchonekeyword(key, itemkeywords)):
-            return True
-
-def getkeywords(node):
-    keywords = {}
-    while node is not None:
-        keywords.update(node.keywords)
-        node = node.parent
-    return keywords
-
-
-def matchonekeyword(key, itemkeywords):
-    for elem in key.split("."):
-        for kw in itemkeywords:
-            if elem in kw:
-                break
-        else:
-            return False
-    return True
-
 class MarkGenerator:
     """ Factory for :class:`MarkDecorator` objects - exposed as
     a ``py.test.mark`` singleton instance.  Example::
diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py
--- a/_pytest/monkeypatch.py
+++ b/_pytest/monkeypatch.py
@@ -1,6 +1,6 @@
 """ monkeypatching and mocking functionality.  """
 
-import os, sys
+import os, sys, inspect
 
 def pytest_funcarg__monkeypatch(request):
     """The returned ``monkeypatch`` funcarg provides these
@@ -39,6 +39,10 @@
         oldval = getattr(obj, name, notset)
         if raising and oldval is notset:
             raise AttributeError("%r has no attribute %r" %(obj, name))
+
+        # avoid class descriptors like staticmethod/classmethod
+        if inspect.isclass(obj):
+            oldval = obj.__dict__.get(name, notset)
         self._setattr.insert(0, (obj, name, oldval))
         setattr(obj, name, value)
 
diff --git a/_pytest/nose.py b/_pytest/nose.py
--- a/_pytest/nose.py
+++ b/_pytest/nose.py
@@ -3,6 +3,8 @@
 import pytest, py
 import inspect
 import sys
+from _pytest import unittest
+
 
 def pytest_runtest_makereport(__multicall__, item, call):
     SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None)
@@ -15,7 +17,7 @@
 
 @pytest.mark.trylast
 def pytest_runtest_setup(item):
-    if isinstance(item, (pytest.Function)):
+    if is_potential_nosetest(item):
         if isinstance(item.parent, pytest.Generator):
             gen = item.parent
             if not hasattr(gen, '_nosegensetup'):
@@ -28,7 +30,7 @@
             call_optional(item.parent.obj, 'setup')
 
 def pytest_runtest_teardown(item):
-    if isinstance(item, pytest.Function):
+    if is_potential_nosetest(item):
         if not call_optional(item.obj, 'teardown'):
             call_optional(item.parent.obj, 'teardown')
         #if hasattr(item.parent, '_nosegensetup'):
@@ -39,9 +41,18 @@
     if isinstance(collector, pytest.Generator):
         call_optional(collector.obj, 'setup')
 
+
+def is_potential_nosetest(item):
+    # extra check needed since we do not do nose style setup/teardown
+    # on direct unittest style classes
+    return isinstance(item, pytest.Function) and \
+        not isinstance(item, unittest.TestCaseFunction)
+
+
 def call_optional(obj, name):
     method = getattr(obj, name, None)
-    if method:
+    isfixture = hasattr(method, "_pytestfixturefunction")
+    if method is not None and not isfixture and py.builtin.callable(method):
         # If there's any problems allow the exception to raise rather than
         # silently ignoring them
         method()
diff --git a/_pytest/pastebin.py b/_pytest/pastebin.py
--- a/_pytest/pastebin.py
+++ b/_pytest/pastebin.py
@@ -2,7 +2,7 @@
 import py, sys
 
 class url:
-    base = "http://paste.pocoo.org"
+    base = "http://bpaste.net"
     xmlrpc = base + "/xmlrpc/"
     show = base + "/show/"
 
@@ -11,7 +11,7 @@
     group._addoption('--pastebin', metavar="mode",
         action='store', dest="pastebin", default=None,
         type="choice", choices=['failed', 'all'],
-        help="send failed|all info to Pocoo pastebin service.")
+        help="send failed|all info to bpaste.net pastebin service.")
 
 def pytest_configure(__multicall__, config):
     import tempfile
diff --git a/_pytest/pdb.py b/_pytest/pdb.py
--- a/_pytest/pdb.py
+++ b/_pytest/pdb.py
@@ -50,7 +50,7 @@
 
 def pytest_runtest_makereport():
     pytestPDB.item = None
-    
+
 class PdbInvoke:
     @pytest.mark.tryfirst
     def pytest_runtest_makereport(self, item, call, __multicall__):
@@ -59,7 +59,7 @@
             call.excinfo.errisinstance(pytest.skip.Exception) or \
             call.excinfo.errisinstance(py.std.bdb.BdbQuit):
             return rep
-        if "xfail" in rep.keywords:
+        if hasattr(rep, "wasxfail"):
             return rep
         # we assume that the above execute() suspended capturing
         # XXX we re-use the TerminalReporter's terminalwriter
diff --git a/_pytest/pytester.py b/_pytest/pytester.py
--- a/_pytest/pytester.py
+++ b/_pytest/pytester.py
@@ -2,6 +2,7 @@
 
 import py, pytest
 import sys, os
+import codecs
 import re
 import inspect
 import time
@@ -194,10 +195,7 @@
             except py.error.EEXIST:
                 continue
             break
-        # we need to create another subdir
-        # because Directory.collect() currently loads
-        # conftest.py from sibling directories
-        self.tmpdir = tmpdir.mkdir(name)
+        self.tmpdir = tmpdir
         self.plugins = []
         self._syspathremove = []
         self.chdir() # always chdir
@@ -244,8 +242,10 @@
         ret = None
         for name, value in items:
             p = self.tmpdir.join(name).new(ext=ext)
-            source = py.builtin._totext(py.code.Source(value)).lstrip()
-            p.write(source.encode("utf-8"), "wb")
+            source = py.builtin._totext(py.code.Source(value)).strip()
+            content = source.encode("utf-8") # + "\n"
+            #content = content.rstrip() + "\n"
+            p.write(content, "wb")
             if ret is None:
                 ret = p
         return ret
@@ -318,7 +318,7 @@
         # used from runner functional tests
         item = self.getitem(source)
         # the test class where we are called from wants to provide the runner
-        testclassinstance = py.builtin._getimself(self.request.function)
+        testclassinstance = self.request.instance
         runner = testclassinstance.getrunner()
         return runner(item)
 
@@ -355,7 +355,7 @@
         if not plugins:
             plugins = []
         plugins.append(Collect())
-        ret = self.pytestmain(list(args), plugins=[Collect()])
+        ret = self.pytestmain(list(args), plugins=plugins)
         reprec = rec[0]
         reprec.ret = ret
         assert len(rec) == 1
@@ -388,10 +388,12 @@
         return config
 
     def getitem(self,  source, funcname="test_func"):
-        for item in self.getitems(source):
+        items = self.getitems(source)
+        for item in items:
             if item.name == funcname:
                 return item
-        assert 0, "%r item not found in module:\n%s" %(funcname, source)
+        assert 0, "%r item not found in module:\n%s\nitems: %s" %(
+                  funcname, source, items)
 
     def getitems(self,  source):
         modcol = self.getmodulecol(source)
@@ -417,7 +419,8 @@
             str(os.getcwd()), env.get('PYTHONPATH', '')]))
         kw['env'] = env
         #print "env", env
-        return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
+        return py.std.subprocess.Popen(cmdargs,
+                                       stdout=stdout, stderr=stderr, **kw)
 
     def pytestmain(self, *args, **kwargs):
         class ResetCapturing:
@@ -438,28 +441,35 @@
         p1 = self.tmpdir.join("stdout")
         p2 = self.tmpdir.join("stderr")
         print_("running", cmdargs, "curdir=", py.path.local())
-        f1 = p1.open("wb")
-        f2 = p2.open("wb")
-        now = time.time()
-        popen = self.popen(cmdargs, stdout=f1, stderr=f2,
-            close_fds=(sys.platform != "win32"))
-        ret = popen.wait()
-        f1.close()
-        f2.close()
-        out = p1.read("rb")
-        out = getdecoded(out).splitlines()
-        err = p2.read("rb")
-        err = getdecoded(err).splitlines()
-        def dump_lines(lines, fp):
-            try:
-                for line in lines:
-                    py.builtin.print_(line, file=fp)
-            except UnicodeEncodeError:
-                print("couldn't print to %s because of encoding" % (fp,))
-        dump_lines(out, sys.stdout)
-        dump_lines(err, sys.stderr)
+        f1 = codecs.open(str(p1), "w", encoding="utf8")
+        f2 = codecs.open(str(p2), "w", encoding="utf8")
+        try:
+            now = time.time()
+            popen = self.popen(cmdargs, stdout=f1, stderr=f2,
+                close_fds=(sys.platform != "win32"))
+            ret = popen.wait()
+        finally:
+            f1.close()
+            f2.close()
+        f1 = codecs.open(str(p1), "r", encoding="utf8")
+        f2 = codecs.open(str(p2), "r", encoding="utf8")
+        try:
+            out = f1.read().splitlines()
+            err = f2.read().splitlines()
+        finally:
+            f1.close()
+            f2.close()
+        self._dump_lines(out, sys.stdout)
+        self._dump_lines(err, sys.stderr)
         return RunResult(ret, out, err, time.time()-now)
 
+    def _dump_lines(self, lines, fp):
+        try:
+            for line in lines:
+                py.builtin.print_(line, file=fp)
+        except UnicodeEncodeError:
+            print("couldn't print to %s because of encoding" % (fp,))
+
     def runpybin(self, scriptname, *args):
         fullargs = self._getpybinargs(scriptname) + args
         return self.run(*fullargs)
@@ -520,6 +530,8 @@
             pytest.skip("pypy-64 bit not supported")
         if sys.platform == "darwin":
             pytest.xfail("pexpect does not work reliably on darwin?!")
+        if sys.platform.startswith("freebsd"):
+            pytest.xfail("pexpect does not work reliably on freebsd")
         logfile = self.tmpdir.join("spawn.out")
         child = pexpect.spawn(cmd, logfile=logfile.open("w"))
         child.timeout = expect_timeout
diff --git a/_pytest/python.py b/_pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -3,17 +3,85 @@
 import inspect
 import sys
 import pytest
+from _pytest.main import getfslineno
+from _pytest.monkeypatch import monkeypatch
 from py._code.code import TerminalRepr
-from _pytest.monkeypatch import monkeypatch
 
 import _pytest
 cutdir = py.path.local(_pytest.__file__).dirpath()
 
+callable = py.builtin.callable
+
+def getimfunc(func):
+    try:
+        return func.__func__
+    except AttributeError:
+        try:
+            return func.im_func
+        except AttributeError:
+            return func
+
+
+class FixtureFunctionMarker:
+    def __init__(self, scope, params, autouse=False):
+        self.scope = scope
+        self.params = params
+        self.autouse = autouse
+
+    def __call__(self, function):
+        if inspect.isclass(function):
+            raise ValueError("class fixtures not supported (may be in the future)")
+        function._pytestfixturefunction = self
+        return function
+
+
+def fixture(scope="function", params=None, autouse=False):
+    """ (return a) decorator to mark a fixture factory function.
+
+    This decorator can be used (with or or without parameters) to define
+    a fixture function.  The name of the fixture function can later be
+    referenced to cause its invocation ahead of running tests: test
+    modules or classes can use the pytest.mark.usefixtures(fixturename)
+    marker.  Test functions can directly use fixture names as input
+    arguments in which case the fixture instance returned from the fixture
+    function will be injected.
+
+    :arg scope: the scope for which this fixture is shared, one of
+                "function" (default), "class", "module", "session".
+
+    :arg params: an optional list of parameters which will cause multiple
+                invocations of the fixture function and all of the tests
+                using it.
+
+    :arg autouse: if True, the fixture func is activated for all tests that
+                can see it.  If False (the default) then an explicit
+                reference is needed to activate the fixture.
+    """
+    if callable(scope) and params is None and autouse == False:
+        # direct decoration
+        return FixtureFunctionMarker("function", params, autouse)(scope)
+    else:
+        return FixtureFunctionMarker(scope, params, autouse=autouse)
+
+defaultfuncargprefixmarker = fixture()
+
+def pyobj_property(name):
+    def get(self):
+        node = self.getparent(getattr(pytest, name))
+        if node is not None:
+            return node.obj
+    doc = "python %s object this node was collected from (can be None)." % (
+          name.lower(),)
+    return property(get, None, None, doc)
+
+
 def pytest_addoption(parser):
     group = parser.getgroup("general")
-    group.addoption('--funcargs',
-               action="store_true", dest="showfuncargs", default=False,
-               help="show available function arguments, sorted by plugin")
+    group.addoption('--fixtures', '--funcargs',
+               action="store_true", dest="showfixtures", default=False,
+               help="show available fixtures, sorted by plugin appearance")
+    parser.addini("usefixtures", type="args", default=[],
+        help="list of default fixtures to be used with this project")
     parser.addini("python_files", type="args",
         default=('test_*.py', '*_test.py'),
         help="glob-style file patterns for Python test module discovery")
@@ -23,18 +91,18 @@
         help="prefixes for Python test function and method discovery")
 
 def pytest_cmdline_main(config):
-    if config.option.showfuncargs:
-        showfuncargs(config)
+    if config.option.showfixtures:
+        showfixtures(config)
         return 0
 
 
 def pytest_generate_tests(metafunc):
     try:
-        param = metafunc.function.parametrize
+        markers = metafunc.function.parametrize
     except AttributeError:
         return
-    for p in param:
-        metafunc.parametrize(*p.args, **p.kwargs)
+    for marker in markers:
+        metafunc.parametrize(*marker.args, **marker.kwargs)
 
 def pytest_configure(config):
     config.addinivalue_line("markers",
@@ -42,24 +110,35 @@
         "times passing in multiple different argument value sets. Example: "
         "@parametrize('arg1', [1,2]) would lead to two calls of the decorated "
         "test function, one with arg1=1 and another with arg1=2."
+        " see http://pytest.org/latest/parametrize.html for more info and "
+        "examples."
+    )
+    config.addinivalue_line("markers",
+        "usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
+        "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures "
     )
 
+def pytest_sessionstart(session):
+    session._fixturemanager = FixtureManager(session)
 
 @pytest.mark.trylast
 def pytest_namespace():
     raises.Exception = pytest.fail.Exception
     return {
+        'fixture': fixture,
         'raises' : raises,
         'collect': {
         'Module': Module, 'Class': Class, 'Instance': Instance,
         'Function': Function, 'Generator': Generator,
-        '_fillfuncargs': fillfuncargs}
+        '_fillfuncargs': fillfixtures}
     }
 
-def pytest_funcarg__pytestconfig(request):
+ at fixture()
+def pytestconfig(request):
     """ the pytest config object with access to command line opts."""
     return request.config
 
+
 def pytest_pyfunc_call(__multicall__, pyfuncitem):
     if not __multicall__.execute():
         testfunction = pyfuncitem.obj
@@ -67,7 +146,10 @@
             testfunction(*pyfuncitem._args)
         else:
             funcargs = pyfuncitem.funcargs
-            testfunction(**funcargs)
+            testargs = {}
+            for arg in pyfuncitem._fixtureinfo.argnames:
+                testargs[arg] = funcargs[arg]
+            testfunction(**testargs)
 
 def pytest_collect_file(path, parent):
     ext = path.ext
@@ -79,8 +161,8 @@
                     break
             else:
                return
-        return parent.ihook.pytest_pycollect_makemodule(
-            path=path, parent=parent)
+        ihook = parent.session.gethookproxy(path)
+        return ihook.pytest_pycollect_makemodule(path=path, parent=parent)
 
 def pytest_pycollect_makemodule(path, parent):
     return Module(path, parent)
@@ -100,7 +182,7 @@
         if is_generator(obj):
             return Generator(name, parent=collector)
         else:
-            return collector._genfunctions(name, obj)
+            return list(collector._genfunctions(name, obj))
 
 def is_generator(func):
     try:
@@ -109,7 +191,12 @@
         # assume them to not be generators
         return False
 
-class PyobjMixin(object):
+class PyobjContext(object):
+    module = pyobj_property("Module")
+    cls = pyobj_property("Class")
+    instance = pyobj_property("Instance")
+
+class PyobjMixin(PyobjContext):
     def obj():
         def fget(self):
             try:
@@ -147,18 +234,7 @@
         return s.replace(".[", "[")
 
     def _getfslineno(self):
-        try:
-            return self._fslineno
-        except AttributeError:
-            pass
-        obj = self.obj
-        # xxx let decorators etc specify a sane ordering
-        if hasattr(obj, 'place_as'):
-            obj = obj.place_as
-
-        self._fslineno = py.code.getfslineno(obj)
-        assert isinstance(self._fslineno[1], int), obj
-        return self._fslineno
+        return getfslineno(self.obj)
 
     def reportinfo(self):
         # XXX caching?
@@ -168,17 +244,15 @@
             fspath = sys.modules[obj.__module__].__file__
             if fspath.endswith(".pyc"):
                 fspath = fspath[:-1]
-            #assert 0
-            #fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
             lineno = obj.compat_co_firstlineno
             modpath = obj.__module__
         else:
-            fspath, lineno = self._getfslineno()
+            fspath, lineno = getfslineno(obj)
             modpath = self.getmodpath()
         assert isinstance(lineno, int)
         return fspath, lineno, modpath
 
-class PyCollectorMixin(PyobjMixin, pytest.Collector):
+class PyCollector(PyobjMixin, pytest.Collector):
 
     def funcnamefilter(self, name):
         for prefix in self.config.getini("python_functions"):
@@ -203,17 +277,17 @@
                 if name in seen:
                     continue
                 seen[name] = True
-                if name[0] != "_":
-                    res = self.makeitem(name, obj)
-                    if res is None:
-                        continue
-                    if not isinstance(res, list):
-                        res = [res]
-                    l.extend(res)
+                res = self.makeitem(name, obj)
+                if res is None:
+                    continue
+                if not isinstance(res, list):
+                    res = [res]
+                l.extend(res)
         l.sort(key=lambda item: item.reportinfo()[:2])
         return l
 
     def makeitem(self, name, obj):
+        #assert self.ihook.fspath == self.fspath, self
         return self.ihook.pytest_pycollect_makeitem(
             collector=self, name=name, obj=obj)
 
@@ -222,8 +296,10 @@
         clscol = self.getparent(Class)
         cls = clscol and clscol.obj or None
         transfer_markers(funcobj, cls, module)
-        metafunc = Metafunc(funcobj, config=self.config,
-            cls=cls, module=module)
+        fm = self.session._fixturemanager
+        fixtureinfo = fm.getfixtureinfo(self, funcobj, cls)
+        metafunc = Metafunc(funcobj, fixtureinfo, self.config,
+                            cls=cls, module=module)
         gentesthook = self.config.hook.pytest_generate_tests
         extra = [module]
         if cls is not None:
@@ -232,14 +308,20 @@
         gentesthook.pcall(plugins, metafunc=metafunc)
         Function = self._getcustomclass("Function")
         if not metafunc._calls:
-            return Function(name, parent=self)
-        l = []
-        for callspec in metafunc._calls:
-            subname = "%s[%s]" %(name, callspec.id)
-            function = Function(name=subname, parent=self,
-                callspec=callspec, callobj=funcobj, keywords={callspec.id:True})
-            l.append(function)
-        return l
+            yield Function(name, parent=self)
+        else:
+            for callspec in metafunc._calls:
+                subname = "%s[%s]" %(name, callspec.id)
+                yield Function(name=subname, parent=self,
+                               callspec=callspec, callobj=funcobj,
+                               keywords={callspec.id:True})
+
+
+class FuncFixtureInfo:
+    def __init__(self, argnames, names_closure, name2fixturedefs):
+        self.argnames = argnames
+        self.names_closure = names_closure
+        self.name2fixturedefs = name2fixturedefs
 
 def transfer_markers(funcobj, cls, mod):
     # XXX this should rather be code in the mark plugin or the mark
@@ -255,10 +337,15 @@
         else:
             pytestmark(funcobj)
 
-class Module(pytest.File, PyCollectorMixin):
+class Module(pytest.File, PyCollector):
+    """ Collector for test classes and functions. """
     def _getobj(self):
         return self._memoizedcall('_obj', self._importtestmodule)
 
+    def collect(self):
+        self.session._fixturemanager.parsefactories(self)
+        return super(Module, self).collect()
+
     def _importtestmodule(self):
         # we assume we are only called once per module
         try:
@@ -283,45 +370,54 @@
         return mod
 
     def setup(self):
-        if hasattr(self.obj, 'setup_module'):
+        setup_module = xunitsetup(self.obj, "setup_module")
+        if setup_module is not None:
             #XXX: nose compat hack, move to nose plugin
             # if it takes a positional arg, its probably a pytest style one
             # so we pass the current module object
-            if inspect.getargspec(self.obj.setup_module)[0]:
-                self.obj.setup_module(self.obj)
+            if inspect.getargspec(setup_module)[0]:
+                setup_module(self.obj)
             else:
-                self.obj.setup_module()
+                setup_module()
 
     def teardown(self):
-        if hasattr(self.obj, 'teardown_module'):
+        teardown_module = xunitsetup(self.obj, 'teardown_module')
+        if teardown_module is not None:
             #XXX: nose compat hack, move to nose plugin
             # if it takes a positional arg, its probably a py.test style one
             # so we pass the current module object
-            if inspect.getargspec(self.obj.teardown_module)[0]:
-                self.obj.teardown_module(self.obj)
+            if inspect.getargspec(teardown_module)[0]:
+                teardown_module(self.obj)
             else:
-                self.obj.teardown_module()
+                teardown_module()
 
-class Class(PyCollectorMixin, pytest.Collector):
-
+class Class(PyCollector):
+    """ Collector for test methods. """
     def collect(self):
         return [self._getcustomclass("Instance")(name="()", parent=self)]
 
     def setup(self):
-        setup_class = getattr(self.obj, 'setup_class', None)
+        setup_class = xunitsetup(self.obj, 'setup_class')
         if setup_class is not None:
             setup_class = getattr(setup_class, 'im_func', setup_class)
+            setup_class = getattr(setup_class, '__func__', setup_class)
             setup_class(self.obj)
 
     def teardown(self):
-        teardown_class = getattr(self.obj, 'teardown_class', None)
+        teardown_class = xunitsetup(self.obj, 'teardown_class')
         if teardown_class is not None:
             teardown_class = getattr(teardown_class, 'im_func', teardown_class)
+            teardown_class = getattr(teardown_class, '__func__', teardown_class)
             teardown_class(self.obj)
 
-class Instance(PyCollectorMixin, pytest.Collector):
+class Instance(PyCollector):
     def _getobj(self):
-        return self.parent.obj()
+        obj = self.parent.obj()
+        return obj
+
+    def collect(self):
+        self.session._fixturemanager.parsefactories(self)
+        return super(Instance, self).collect()
 
     def newinstance(self):
         self.obj = self._getobj()
@@ -330,6 +426,7 @@
 class FunctionMixin(PyobjMixin):
     """ mixin for the code common to Function and Generator.
     """
+
     def setup(self):
         """ perform setup for this test function. """
         if hasattr(self, '_preservedparent'):
@@ -343,7 +440,7 @@
             name = 'setup_method'
         else:
             name = 'setup_function'
-        setup_func_or_method = getattr(obj, name, None)
+        setup_func_or_method = xunitsetup(obj, name)
         if setup_func_or_method is not None:
             setup_func_or_method(self.obj)
 
@@ -354,7 +451,7 @@
         else:
             name = 'teardown_function'
         obj = self.parent.obj
-        teardown_func_or_meth = getattr(obj, name, None)
+        teardown_func_or_meth = xunitsetup(obj, name)
         if teardown_func_or_meth is not None:
             teardown_func_or_meth(self.obj)
 
@@ -371,13 +468,6 @@
             excinfo.traceback = ntraceback.filter()
 
     def _repr_failure_py(self, excinfo, style="long"):
-        if excinfo.errisinstance(FuncargRequest.LookupError):
-            fspath, lineno, msg = self.reportinfo()
-            lines, _ = inspect.getsourcelines(self.obj)
-            for i, line in enumerate(lines):
-                if line.strip().startswith('def'):
-                    return FuncargLookupErrorRepr(fspath, lineno,
-            lines[:i+1], str(excinfo.value))
         if excinfo.errisinstance(pytest.fail.Exception):
             if not excinfo.value.pytrace:
                 return str(excinfo.value)
@@ -389,24 +479,8 @@
         return self._repr_failure_py(excinfo,
             style=self.config.option.tbstyle)
 
-class FuncargLookupErrorRepr(TerminalRepr):
-    def __init__(self, filename, firstlineno, deflines, errorstring):
-        self.deflines = deflines
-        self.errorstring = errorstring
-        self.filename = filename
-        self.firstlineno = firstlineno
 
-    def toterminal(self, tw):
-        tw.line()
-        for line in self.deflines:
-            tw.line("    " + line.strip())
-        for line in self.errorstring.split("\n"):
-            tw.line("        " + line.strip(), red=True)
-        tw.line()
-        tw.line("%s:%d" % (self.filename, self.firstlineno+1))
-
-
-class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
+class Generator(FunctionMixin, PyCollector):
     def collect(self):
         # test generators are seen as collectors but they also
         # invoke setup/teardown on popular request
@@ -418,7 +492,7 @@
         seen = {}
         for i, x in enumerate(self.obj()):
             name, call, args = self.getcallargs(x)
-            if not py.builtin.callable(call):
+            if not callable(call):
                 raise TypeError("%r yielded non callable test %r" %(self.obj, call,))
             if name is None:
                 name = "[%d]" % i
@@ -443,77 +517,6 @@
         return name, call, args
 
 
-#
-#  Test Items
-#
-_dummy = object()
-class Function(FunctionMixin, pytest.Item):
-    """ a Function Item is responsible for setting up
-        and executing a Python callable test object.
-    """
-    _genid = None
-    def __init__(self, name, parent=None, args=None, config=None,
-                 callspec=None, callobj=_dummy, keywords=None, session=None):
-        super(Function, self).__init__(name, parent,
-            config=config, session=session)
-        self._args = args
-        if self._isyieldedfunction():
-            assert not callspec, (
-                "yielded functions (deprecated) cannot have funcargs")
-        else:
-            if callspec is not None:
-                self.callspec = callspec
-                self.funcargs = callspec.funcargs or {}
-                self._genid = callspec.id
-                if hasattr(callspec, "param"):
-                    self._requestparam = callspec.param
-            else:
-                self.funcargs = {}
-        if callobj is not _dummy:
-            self._obj = callobj
-        self.function = getattr(self.obj, 'im_func', self.obj)
-        self.keywords.update(py.builtin._getfuncdict(self.obj) or {})
-        if keywords:
-            self.keywords.update(keywords)
-
-    def _getobj(self):
-        name = self.name
-        i = name.find("[") # parametrization
-        if i != -1:
-            name = name[:i]
-        return getattr(self.parent.obj, name)
-
-    def _isyieldedfunction(self):
-        return self._args is not None
-
-    def runtest(self):
-        """ execute the underlying test function. """
-        self.ihook.pytest_pyfunc_call(pyfuncitem=self)
-
-    def setup(self):
-        super(Function, self).setup()
-        if hasattr(self, 'funcargs'):


More information about the pypy-commit mailing list