Commits

holger krekel  committed 05555a5

remove old py copy, add current pytest and py lib snapshots (from pytest-2.0.1dev and py-1.4.1dev)
and some initial tweeks to conftest.py

  • Participants
  • Parent commits e896633
  • Branches pytest2

Comments (0)

Files changed (129)

File _pytest/__init__.py

+#

File _pytest/assertion.py

+"""
+support for presented detailed information in failing assertions.
+"""
+import py
+import sys
+from _pytest.monkeypatch import monkeypatch
+
+def pytest_addoption(parser):
+    group = parser.getgroup("debugconfig")
+    group._addoption('--no-assert', action="store_true", default=False,
+        dest="noassert",
+        help="disable python assert expression reinterpretation."),
+
+def pytest_configure(config):
+    # The _pytesthook attribute on the AssertionError is used by
+    # py._code._assertionnew to detect this plugin was loaded and in
+    # turn call the hooks defined here as part of the
+    # DebugInterpreter.
+    config._monkeypatch = m = monkeypatch()
+    warn_about_missing_assertion()
+    if not config.getvalue("noassert") and not config.getvalue("nomagic"):
+        def callbinrepr(op, left, right):
+            hook_result = config.hook.pytest_assertrepr_compare(
+                config=config, op=op, left=left, right=right)
+            for new_expl in hook_result:
+                if new_expl:
+                    return '\n~'.join(new_expl)
+        m.setattr(py.builtin.builtins,
+                  'AssertionError', py.code._AssertionError)
+        m.setattr(py.code, '_reprcompare', callbinrepr)
+
+def pytest_unconfigure(config):
+    config._monkeypatch.undo()
+
+def warn_about_missing_assertion():
+    try:
+        assert False
+    except AssertionError:
+        pass
+    else:
+        sys.stderr.write("WARNING: failing tests may report as passing because "
+        "assertions are turned off!  (are you using python -O?)\n")
+
+# Provide basestring in python3
+try:
+    basestring = basestring
+except NameError:
+    basestring = str
+
+
+def pytest_assertrepr_compare(op, left, right):
+    """return specialised explanations for some operators/operands"""
+    width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
+    left_repr = py.io.saferepr(left, maxsize=width/2)
+    right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
+    summary = '%s %s %s' % (left_repr, op, right_repr)
+
+    issequence = lambda x: isinstance(x, (list, tuple))
+    istext = lambda x: isinstance(x, basestring)
+    isdict = lambda x: isinstance(x, dict)
+    isset = lambda x: isinstance(x, set)
+
+    explanation = None
+    try:
+        if op == '==':
+            if istext(left) and istext(right):
+                explanation = _diff_text(left, right)
+            elif issequence(left) and issequence(right):
+                explanation = _compare_eq_sequence(left, right)
+            elif isset(left) and isset(right):
+                explanation = _compare_eq_set(left, right)
+            elif isdict(left) and isdict(right):
+                explanation = _diff_text(py.std.pprint.pformat(left),
+                                         py.std.pprint.pformat(right))
+        elif op == 'not in':
+            if istext(left) and istext(right):
+                explanation = _notin_text(left, right)
+    except py.builtin._sysex:
+        raise
+    except:
+        excinfo = py.code.ExceptionInfo()
+        explanation = ['(pytest_assertion plugin: representation of '
+            'details failed. Probably an object has a faulty __repr__.)',
+            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
+
+
+def _diff_text(left, right):
+    """Return the explanation for the diff between text
+
+    This will skip leading and trailing characters which are
+    identical to keep the diff minimal.
+    """
+    explanation = []
+    i = 0 # just in case left or right has zero length
+    for i in range(min(len(left), len(right))):
+        if left[i] != right[i]:
+            break
+    if i > 42:
+        i -= 10                 # Provide some context
+        explanation = ['Skipping %s identical '
+                       'leading characters in diff' % i]
+        left = left[i:]
+        right = right[i:]
+    if len(left) == len(right):
+        for i in range(len(left)):
+            if left[-i] != right[-i]:
+                break
+        if i > 42:
+            i -= 10     # Provide some context
+            explanation += ['Skipping %s identical '
+                            'trailing characters in diff' % i]
+            left = left[:-i]
+            right = right[:-i]
+    explanation += [line.strip('\n')
+                    for line in py.std.difflib.ndiff(left.splitlines(),
+                                                     right.splitlines())]
+    return explanation
+
+
+def _compare_eq_sequence(left, right):
+    explanation = []
+    for i in range(min(len(left), len(right))):
+        if left[i] != right[i]:
+            explanation += ['At index %s diff: %r != %r' %
+                            (i, left[i], right[i])]
+            break
+    if len(left) > len(right):
+        explanation += ['Left contains more items, '
+            'first extra item: %s' % py.io.saferepr(left[len(right)],)]
+    elif len(left) < len(right):
+        explanation += ['Right contains more items, '
+            'first extra item: %s' % py.io.saferepr(right[len(left)],)]
+    return explanation # + _diff_text(py.std.pprint.pformat(left),
+                       #             py.std.pprint.pformat(right))
+
+
+def _compare_eq_set(left, right):
+    explanation = []
+    diff_left = left - right
+    diff_right = right - left
+    if diff_left:
+        explanation.append('Extra items in the left set:')
+        for item in diff_left:
+            explanation.append(py.io.saferepr(item))
+    if diff_right:
+        explanation.append('Extra items in the right set:')
+        for item in diff_right:
+            explanation.append(py.io.saferepr(item))
+    return explanation
+
+
+def _notin_text(term, text):
+    index = text.find(term)
+    head = text[:index]
+    tail = text[index+len(term):]
+    correct_text = head + tail
+    return _diff_text(correct_text, text)

File _pytest/capture.py

+""" per-test stdout/stderr capturing mechanisms, ``capsys`` and ``capfd`` function arguments.  """
+
+import pytest, py
+import os
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general")
+    group._addoption('--capture', action="store", default=None,
+        metavar="method", type="choice", choices=['fd', 'sys', 'no'],
+        help="per-test capturing method: one of fd (default)|sys|no.")
+    group._addoption('-s', action="store_const", const="no", dest="capture",
+        help="shortcut for --capture=no.")
+
+def addouterr(rep, outerr):
+    repr = getattr(rep, 'longrepr', None)
+    if not hasattr(repr, 'addsection'):
+        return
+    for secname, content in zip(["out", "err"], outerr):
+        if content:
+            repr.addsection("Captured std%s" % secname, content.rstrip())
+
+def pytest_unconfigure(config):
+    # registered in config.py during early conftest.py loading
+    capman = config.pluginmanager.getplugin('capturemanager')
+    while capman._method2capture:
+        name, cap = capman._method2capture.popitem()
+        # XXX logging module may wants to close it itself on process exit
+        # otherwise we could do finalization here and call "reset()".
+        cap.suspend()
+
+class NoCapture:
+    def startall(self):
+        pass
+    def resume(self):
+        pass
+    def reset(self):
+        pass
+    def suspend(self):
+        return "", ""
+
+class CaptureManager:
+    def __init__(self):
+        self._method2capture = {}
+
+    def _maketempfile(self):
+        f = py.std.tempfile.TemporaryFile()
+        newf = py.io.dupfile(f, encoding="UTF-8")
+        f.close()
+        return newf
+
+    def _makestringio(self):
+        return py.io.TextIO()
+
+    def _getcapture(self, method):
+        if method == "fd":
+            return py.io.StdCaptureFD(now=False,
+                out=self._maketempfile(), err=self._maketempfile()
+            )
+        elif method == "sys":
+            return py.io.StdCapture(now=False,
+                out=self._makestringio(), err=self._makestringio()
+            )
+        elif method == "no":
+            return NoCapture()
+        else:
+            raise ValueError("unknown capturing method: %r" % method)
+
+    def _getmethod_preoptionparse(self, args):
+        if '-s' in args or "--capture=no" in args:
+            return "no"
+        elif hasattr(os, 'dup') and '--capture=sys' not in args:
+            return "fd"
+        else:
+            return "sys"
+
+    def _getmethod(self, config, fspath):
+        if config.option.capture:
+            method = config.option.capture
+        else:
+            try:
+                method = config._conftest.rget("option_capture", path=fspath)
+            except KeyError:
+                method = "fd"
+        if method == "fd" and not hasattr(os, 'dup'): # e.g. jython
+            method = "sys"
+        return method
+
+    def resumecapture_item(self, item):
+        method = self._getmethod(item.config, item.fspath)
+        if not hasattr(item, 'outerr'):
+            item.outerr = ('', '') # we accumulate outerr on the item
+        return self.resumecapture(method)
+
+    def resumecapture(self, method):
+        if hasattr(self, '_capturing'):
+            raise ValueError("cannot resume, already capturing with %r" %
+                (self._capturing,))
+        cap = self._method2capture.get(method)
+        self._capturing = method
+        if cap is None:
+            self._method2capture[method] = cap = self._getcapture(method)
+            cap.startall()
+        else:
+            cap.resume()
+
+    def suspendcapture(self, item=None):
+        self.deactivate_funcargs()
+        if hasattr(self, '_capturing'):
+            method = self._capturing
+            cap = self._method2capture.get(method)
+            if cap is not None:
+                outerr = cap.suspend()
+            del self._capturing
+            if item:
+                outerr = (item.outerr[0] + outerr[0],
+                          item.outerr[1] + outerr[1])
+            return outerr
+        if hasattr(item, 'outerr'):
+            return item.outerr
+        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()
+
+    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
+
+    def pytest_make_collect_report(self, __multicall__, collector):
+        method = self._getmethod(collector.config, collector.fspath)
+        try:
+            self.resumecapture(method)
+        except ValueError:
+            return # recursive collect, XXX refactor capturing
+                   # to allow for more lightweight recursive capturing
+        try:
+            rep = __multicall__.execute()
+        finally:
+            outerr = self.suspendcapture()
+        addouterr(rep, outerr)
+        return rep
+
+    @pytest.mark.tryfirst
+    def pytest_runtest_setup(self, item):
+        self.resumecapture_item(item)
+
+    @pytest.mark.tryfirst
+    def pytest_runtest_call(self, item):
+        self.resumecapture_item(item)
+        self.activate_funcargs(item)
+
+    @pytest.mark.tryfirst
+    def pytest_runtest_teardown(self, item):
+        self.resumecapture_item(item)
+
+    def pytest__teardown_final(self, __multicall__, session):
+        method = self._getmethod(session.config, None)
+        self.resumecapture(method)
+        try:
+            rep = __multicall__.execute()
+        finally:
+            outerr = self.suspendcapture()
+        if rep:
+            addouterr(rep, outerr)
+        return rep
+
+    def pytest_keyboard_interrupt(self, excinfo):
+        if hasattr(self, '_capturing'):
+            self.suspendcapture()
+
+    @pytest.mark.tryfirst
+    def pytest_runtest_makereport(self, __multicall__, item, call):
+        self.deactivate_funcargs()
+        rep = __multicall__.execute()
+        outerr = self.suspendcapture(item)
+        if not rep.passed:
+            addouterr(rep, outerr)
+        if not rep.passed or rep.when == "teardown":
+            outerr = ('', '')
+        item.outerr = outerr
+        return rep
+
+def pytest_funcarg__capsys(request):
+    """captures writes to sys.stdout/sys.stderr and makes
+    them available successively via a ``capsys.readouterr()`` method
+    which returns a ``(out, err)`` tuple of captured snapshot strings.
+    """
+    return CaptureFuncarg(py.io.StdCapture)
+
+def pytest_funcarg__capfd(request):
+    """captures writes to file descriptors 1 and 2 and makes
+    snapshotted ``(out, err)`` string tuples available
+    via the ``capsys.readouterr()`` method.  If the underlying
+    platform does not have ``os.dup`` (e.g. Jython) tests using
+    this funcarg will automatically skip.
+    """
+    if not hasattr(os, 'dup'):
+        py.test.skip("capfd funcarg needs os.dup")
+    return CaptureFuncarg(py.io.StdCaptureFD)
+
+class CaptureFuncarg:
+    def __init__(self, captureclass):
+        self.capture = captureclass(now=False)
+
+    def _start(self):
+        self.capture.startall()
+
+    def _finalize(self):
+        if hasattr(self, 'capture'):
+            self.capture.reset()
+            del self.capture
+
+    def readouterr(self):
+        return self.capture.readouterr()
+
+    def close(self):
+        self._finalize()

File _pytest/config.py

+""" command line options, ini-file and conftest.py processing. """
+
+import py
+import sys, os
+from _pytest.core import PluginManager
+import pytest
+
+def pytest_cmdline_parse(pluginmanager, args):
+    config = Config(pluginmanager)
+    config.parse(args)
+    if config.option.debug:
+        config.trace.root.setwriter(sys.stderr.write)
+    return config
+
+class Parser:
+    """ Parser for command line arguments. """
+
+    def __init__(self, usage=None, processopt=None):
+        self._anonymous = OptionGroup("custom options", parser=self)
+        self._groups = []
+        self._processopt = processopt
+        self._usage = usage
+        self._inidict = {}
+        self._ininames = []
+        self.hints = []
+
+    def processoption(self, option):
+        if self._processopt:
+            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.
+        :description: long description for --help output.
+        :after: name of other group, used for ordering --help output.
+        """
+        for group in self._groups:
+            if group.name == name:
+                return group
+        group = OptionGroup(name, description, parser=self)
+        i = 0
+        for i, grp in enumerate(self._groups):
+            if grp.name == after:
+                break
+        self._groups.insert(i+1, group)
+        return group
+
+    def addoption(self, *opts, **attrs):
+        """ add an optparse-style option. """
+        self._anonymous.addoption(*opts, **attrs)
+
+    def parse(self, args):
+        self.optparser = optparser = MyOptionParser(self)
+        groups = self._groups + [self._anonymous]
+        for group in groups:
+            if group.options:
+                desc = group.description or group.name
+                optgroup = py.std.optparse.OptionGroup(optparser, desc)
+                optgroup.add_options(group.options)
+                optparser.add_option_group(optgroup)
+        return self.optparser.parse_args([str(x) for x in args])
+
+    def parse_setoption(self, args, option):
+        parsedoption, args = self.parse(args)
+        for name, value in parsedoption.__dict__.items():
+            setattr(option, name, value)
+        return args
+
+    def addini(self, name, help, type=None, default=None):
+        """ add an ini-file option with the given name and description. """
+        assert type in (None, "pathlist", "args", "linelist")
+        self._inidict[name] = (help, type, default)
+        self._ininames.append(name)
+
+class OptionGroup:
+    def __init__(self, name, description="", parser=None):
+        self.name = name
+        self.description = description
+        self.options = []
+        self.parser = parser
+
+    def addoption(self, *optnames, **attrs):
+        """ add an option to this group. """
+        option = py.std.optparse.Option(*optnames, **attrs)
+        self._addoption_instance(option, shortupper=False)
+
+    def _addoption(self, *optnames, **attrs):
+        option = py.std.optparse.Option(*optnames, **attrs)
+        self._addoption_instance(option, shortupper=True)
+
+    def _addoption_instance(self, option, shortupper=False):
+        if not shortupper:
+            for opt in option._short_opts:
+                if opt[0] == '-' and opt[1].islower():
+                    raise ValueError("lowercase shortoptions reserved")
+        if self.parser:
+            self.parser.processoption(option)
+        self.options.append(option)
+
+
+class MyOptionParser(py.std.optparse.OptionParser):
+    def __init__(self, parser):
+        self._parser = parser
+        py.std.optparse.OptionParser.__init__(self, usage=parser._usage,
+            add_help_option=False)
+    def format_epilog(self, formatter):
+        hints = self._parser.hints
+        if hints:
+            s = "\n".join(["hint: " + x for x in hints]) + "\n"
+            s = "\n" + s + "\n"
+            return s
+        return ""
+
+class Conftest(object):
+    """ the single place for accessing values and interacting
+        towards conftest modules from py.test objects.
+    """
+    def __init__(self, onimport=None, confcutdir=None):
+        self._path2confmods = {}
+        self._onimport = onimport
+        self._conftestpath2mod = {}
+        self._confcutdir = confcutdir
+
+    def setinitial(self, args):
+        """ try to find a first anchor path for looking up global values
+            from conftests. This function is usually called _before_
+            argument parsing.  conftest files may add command line options
+            and we thus have no completely safe way of determining
+            which parts of the arguments are actually related to options
+            and which are file system paths.  We just try here to get
+            bootstrapped ...
+        """
+        current = py.path.local()
+        opt = '--confcutdir'
+        for i in range(len(args)):
+            opt1 = str(args[i])
+            if opt1.startswith(opt):
+                if opt1 == opt:
+                    if len(args) > i:
+                        p = current.join(args[i+1], abs=True)
+                elif opt1.startswith(opt + "="):
+                    p = current.join(opt1[len(opt)+1:], abs=1)
+                self._confcutdir = p
+                break
+        for arg in args + [current]:
+            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?"
+
+    def getconftestmodules(self, path):
+        """ return a list of imported conftest modules for the given path.  """
+        try:
+            clist = self._path2confmods[path]
+        except KeyError:
+            if path is None:
+                raise ValueError("missing default confest.")
+            dp = path.dirpath()
+            clist = []
+            if dp != path:
+                cutdir = self._confcutdir
+                if cutdir and path != cutdir and not path.relto(cutdir):
+                    pass
+                else:
+                    conftestpath = path.join("conftest.py")
+                    if conftestpath.check(file=1):
+                        clist.append(self.importconftest(conftestpath))
+                clist[:0] = self.getconftestmodules(dp)
+            self._path2confmods[path] = clist
+        # be defensive: avoid changes from caller side to
+        # affect us by always returning a copy of the actual list
+        return clist[:]
+
+    def rget(self, name, path=None):
+        mod, value = self.rget_with_confmod(name, path)
+        return value
+
+    def rget_with_confmod(self, name, path=None):
+        modules = self.getconftestmodules(path)
+        modules.reverse()
+        for mod in modules:
+            try:
+                return mod, getattr(mod, name)
+            except AttributeError:
+                continue
+        raise KeyError(name)
+
+    def importconftest(self, conftestpath):
+        assert conftestpath.check(), conftestpath
+        try:
+            return self._conftestpath2mod[conftestpath]
+        except KeyError:
+            pkgpath = conftestpath.pypkgpath()
+            if pkgpath is None:
+                _ensure_removed_sysmodule(conftestpath.purebasename)
+            self._conftestpath2mod[conftestpath] = mod = conftestpath.pyimport()
+            dirpath = conftestpath.dirpath()
+            if dirpath in self._path2confmods:
+                for path, mods in self._path2confmods.items():
+                    if path and path.relto(dirpath) or path == dirpath:
+                        assert mod not in mods
+                        mods.append(mod)
+            self._postimport(mod)
+            return mod
+
+    def _postimport(self, mod):
+        if self._onimport:
+            self._onimport(mod)
+        return mod
+
+def _ensure_removed_sysmodule(modname):
+    try:
+        del sys.modules[modname]
+    except KeyError:
+        pass
+
+class CmdOptions(object):
+    """ holds cmdline options as attributes."""
+    def __init__(self, **kwargs):
+        self.__dict__.update(kwargs)
+    def __repr__(self):
+        return "<CmdOptions %r>" %(self.__dict__,)
+
+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
+        self.option = CmdOptions()
+        self._parser = Parser(
+            usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
+            processopt=self._processopt,
+        )
+        #: a pluginmanager instance
+        self.pluginmanager = pluginmanager or PluginManager(load=True)
+        self.trace = self.pluginmanager.trace.root.get("config")
+        self._conftest = Conftest(onimport=self._onimportconftest)
+        self.hook = self.pluginmanager.hook
+        self._inicache = {}
+
+    def _onimportconftest(self, conftestmodule):
+        self.trace("loaded conftestmodule %r" %(conftestmodule,))
+        self.pluginmanager.consider_conftest(conftestmodule)
+
+    def _processopt(self, opt):
+        if hasattr(opt, 'default') and opt.dest:
+            if not hasattr(self.option, opt.dest):
+                setattr(self.option, opt.dest, opt.default)
+
+    def _getmatchingplugins(self, fspath):
+        allconftests = self._conftest._conftestpath2mod.values()
+        plugins = [x for x in self.pluginmanager.getplugins()
+                        if x not in allconftests]
+        plugins += self._conftest.getconftestmodules(fspath)
+        return plugins
+
+    def _setinitialconftest(self, args):
+        # capture output during conftest init (#issue93)
+        from _pytest.capture import CaptureManager
+        capman = CaptureManager()
+        self.pluginmanager.register(capman, 'capturemanager')
+        # will be unregistered in capture.py's unconfigure()
+        capman.resumecapture(capman._getmethod_preoptionparse(args))
+        try:
+            try:
+                self._conftest.setinitial(args)
+            finally:
+                out, err = capman.suspendcapture() # logging might have got it
+        except:
+            sys.stdout.write(out)
+            sys.stderr.write(err)
+            raise
+
+    def _initini(self, args):
+        self.inicfg = getcfg(args, ["pytest.ini", "tox.ini", "setup.cfg"])
+        self._parser.addini('addopts', 'extra command line options', 'args')
+        self._parser.addini('minversion', 'minimally required pytest version')
+
+    def _preparse(self, args, addopts=True):
+        self._initini(args)
+        if addopts:
+            args[:] = self.getini("addopts") + args
+        self._checkversion()
+        self.pluginmanager.consider_preparse(args)
+        self.pluginmanager.consider_setuptools_entrypoints()
+        self.pluginmanager.consider_env()
+        self._setinitialconftest(args)
+        self.pluginmanager.do_addoption(self._parser)
+        if addopts:
+            self.hook.pytest_cmdline_preparse(config=self, args=args)
+
+    def _checkversion(self):
+        minver = self.inicfg.get('minversion', None)
+        if minver:
+            ver = minver.split(".")
+            myver = pytest.__version__.split(".")
+            if myver < ver:
+                raise pytest.UsageError(
+                    "%s:%d: requires pytest-%s, actual pytest-%s'" %(
+                    self.inicfg.config.path, self.inicfg.lineof('minversion'),
+                    minver, pytest.__version__))
+
+    def parse(self, args):
+        # parse given cmdline arguments into this config object.
+        # Note that this can only be called once per testing process.
+        assert not hasattr(self, 'args'), (
+                "can only parse cmdline args at most once per Config object")
+        self._preparse(args)
+        self._parser.hints.extend(self.pluginmanager._hints)
+        args = self._parser.parse_setoption(args, self.option)
+        if not args:
+            args.append(py.std.os.getcwd())
+        self.args = args
+
+    def getini(self, name):
+        """ return configuration value from an ini file. If the
+        specified name hasn't been registered through a prior ``parse.addini``
+        call (usually from a plugin), a ValueError is raised. """
+        try:
+            return self._inicache[name]
+        except KeyError:
+            self._inicache[name] = val = self._getini(name)
+            return val
+
+    def _getini(self, name):
+        try:
+            description, type, default = self._parser._inidict[name]
+        except KeyError:
+            raise ValueError("unknown configuration value: %r" %(name,))
+        try:
+            value = self.inicfg[name]
+        except KeyError:
+            if default is not None:
+                return default
+            if type is None:
+                return ''
+            return []
+        if type == "pathlist":
+            dp = py.path.local(self.inicfg.config.path).dirpath()
+            l = []
+            for relpath in py.std.shlex.split(value):
+                l.append(dp.join(relpath, abs=True))
+            return l
+        elif type == "args":
+            return py.std.shlex.split(value)
+        elif type == "linelist":
+            return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
+        else:
+            assert type is None
+            return value
+
+    def _getconftest_pathlist(self, name, path=None):
+        try:
+            mod, relroots = self._conftest.rget_with_confmod(name, path)
+        except KeyError:
+            return None
+        modpath = py.path.local(mod.__file__).dirpath()
+        l = []
+        for relroot in relroots:
+            if not isinstance(relroot, py.path.local):
+                relroot = relroot.replace("/", py.path.local.sep)
+                relroot = modpath.join(relroot, abs=True)
+            l.append(relroot)
+        return l
+
+    def _getconftest(self, name, path=None, check=False):
+        if check:
+            self._checkconftest(name)
+        return self._conftest.rget(name, path)
+
+    def getvalue(self, name, path=None):
+        """ return ``name`` value looked set from command line options.
+
+        (deprecated) if we can't find the option also lookup
+        the name in a matching conftest file.
+        """
+        try:
+            return getattr(self.option, name)
+        except AttributeError:
+            return self._getconftest(name, path, check=False)
+
+    def getvalueorskip(self, name, path=None):
+        """ (deprecated) return getvalue(name) or call
+        py.test.skip if no value exists. """
+        __tracebackhide__ = True
+        try:
+            val = self.getvalue(name, path)
+            if val is None:
+                raise KeyError(name)
+            return val
+        except KeyError:
+            py.test.skip("no %r value found" %(name,))
+
+
+def getcfg(args, inibasenames):
+    args = [x for x in args if str(x)[0] != "-"]
+    if not args:
+        args = [py.path.local()]
+    for arg in args:
+        arg = py.path.local(arg)
+        for base in arg.parts(reverse=True):
+            for inibasename in inibasenames:
+                p = base.join(inibasename)
+                if p.check():
+                    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
+

File _pytest/core.py

+"""
+pytest PluginManager, basic initialization and tracing.
+(c) Holger Krekel 2004-2010
+"""
+import sys, os
+import inspect
+import py
+from _pytest import hookspec # the extension point definitions
+
+assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
+    "%s is too old, remove or upgrade 'py'" % (py.__version__))
+
+default_plugins = (
+ "config mark main terminal runner python pdb unittest capture skipping "
+ "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
+ "junitxml resultlog doctest").split()
+
+class TagTracer:
+    def __init__(self, prefix="[pytest] "):
+        self._tag2proc = {}
+        self.writer = None
+        self.indent = 0
+        self.prefix = prefix
+
+    def get(self, name):
+        return TagTracerSub(self, (name,))
+
+    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" %(self.prefix, indent, content))
+        try:
+            self._tag2proc[tags](tags, args)
+        except KeyError:
+            pass
+
+    def setwriter(self, writer):
+        self.writer = writer
+
+    def setprocessor(self, tags, processor):
+        if isinstance(tags, str):
+            tags = tuple(tags.split(":"))
+        else:
+            assert isinstance(tags, tuple)
+        self._tag2proc[tags] = processor
+
+class TagTracerSub:
+    def __init__(self, root, tags):
+        self.root = root
+        self.tags = tags
+    def __call__(self, *args):
+        self.root.processmessage(self.tags, args)
+    def setmyprocessor(self, processor):
+        self.root.setprocessor(self.tags, processor)
+    def get(self, name):
+        return self.__class__(self.root, self.tags + (name,))
+
+class PluginManager(object):
+    def __init__(self, load=False):
+        self._name2plugin = {}
+        self._plugins = []
+        self._hints = []
+        self.trace = TagTracer().get("pluginmanage")
+        self._plugin_distinfo = []
+        if os.environ.get('PYTEST_DEBUG'):
+            err = sys.stderr
+            encoding = getattr(err, 'encoding', 'utf8')
+            try:
+                err = py.io.dupfile(err, encoding=encoding)
+            except Exception:
+                pass
+            self.trace.root.setwriter(err.write)
+        self.hook = HookRelay([hookspec], pm=self)
+        self.register(self)
+        if load:
+            for spec in default_plugins:
+                self.import_plugin(spec)
+
+    def register(self, plugin, name=None, prepend=False):
+        assert not self.isregistered(plugin), plugin
+        name = name or getattr(plugin, '__name__', str(id(plugin)))
+        if name in self._name2plugin:
+            return False
+        #self.trace("registering", name, plugin)
+        self._name2plugin[name] = plugin
+        self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
+        self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
+        if not prepend:
+            self._plugins.append(plugin)
+        else:
+            self._plugins.insert(0, plugin)
+        return True
+
+    def unregister(self, plugin=None, name=None):
+        if plugin is None:
+            plugin = self.getplugin(name=name)
+        self._plugins.remove(plugin)
+        self.hook.pytest_plugin_unregistered(plugin=plugin)
+        for name, value in list(self._name2plugin.items()):
+            if value == plugin:
+                del self._name2plugin[name]
+
+    def isregistered(self, plugin, name=None):
+        if self.getplugin(name) is not None:
+            return True
+        for val in self._name2plugin.values():
+            if plugin == val:
+                return True
+
+    def addhooks(self, spec):
+        self.hook._addhooks(spec, prefix="pytest_")
+
+    def getplugins(self):
+        return list(self._plugins)
+
+    def skipifmissing(self, name):
+        if not self.hasplugin(name):
+            py.test.skip("plugin %r is missing" % name)
+
+    def hasplugin(self, name):
+        return bool(self.getplugin(name))
+
+    def getplugin(self, name):
+        if name is None:
+            return None
+        try:
+            return self._name2plugin[name]
+        except KeyError:
+            return self._name2plugin.get("_pytest." + name, None)
+
+    # API for bootstrapping
+    #
+    def _envlist(self, varname):
+        val = py.std.os.environ.get(varname, None)
+        if val is not None:
+            return val.split(',')
+        return ()
+
+    def consider_env(self):
+        for spec in self._envlist("PYTEST_PLUGINS"):
+            self.import_plugin(spec)
+
+    def consider_setuptools_entrypoints(self):
+        try:
+            from pkg_resources import iter_entry_points, DistributionNotFound
+        except ImportError:
+            return # XXX issue a warning
+        for ep in iter_entry_points('pytest11'):
+            name = ep.name
+            if name.startswith("pytest_"):
+                name = name[7:]
+            if ep.name in self._name2plugin or name in self._name2plugin:
+                continue
+            try:
+                plugin = ep.load()
+            except DistributionNotFound:
+                continue
+            self._plugin_distinfo.append((ep.dist, plugin))
+            self.register(plugin, name=name)
+
+    def consider_preparse(self, args):
+        for opt1,opt2 in zip(args, args[1:]):
+            if opt1 == "-p":
+                if opt2.startswith("no:"):
+                    name = opt2[3:]
+                    if self.getplugin(name) is not None:
+                        self.unregister(None, name=name)
+                    self._name2plugin[name] = -1
+                else:
+                    if self.getplugin(opt2) is None:
+                        self.import_plugin(opt2)
+
+    def consider_conftest(self, conftestmodule):
+        if self.register(conftestmodule, name=conftestmodule.__file__):
+            self.consider_module(conftestmodule)
+
+    def consider_module(self, mod):
+        attr = getattr(mod, "pytest_plugins", ())
+        if attr:
+            if not isinstance(attr, (list, tuple)):
+                attr = (attr,)
+            for spec in attr:
+                self.import_plugin(spec)
+
+    def import_plugin(self, modname):
+        assert isinstance(modname, str)
+        if self.getplugin(modname) is not None:
+            return
+        try:
+            #self.trace("importing", modname)
+            mod = importplugin(modname)
+        except KeyboardInterrupt:
+            raise
+        except ImportError:
+            if modname.startswith("pytest_"):
+                return self.import_plugin(modname[7:])
+            raise
+        except:
+            e = py.std.sys.exc_info()[1]
+            if not hasattr(py.test, 'skip'):
+                raise
+            elif not isinstance(e, py.test.skip.Exception):
+                raise
+            self._hints.append("skipped plugin %r: %s" %((modname, e.msg)))
+        else:
+            self.register(mod, modname)
+            self.consider_module(mod)
+
+    def pytest_plugin_registered(self, plugin):
+        import pytest
+        dic = self.call_plugin(plugin, "pytest_namespace", {}) or {}
+        if dic:
+            self._setns(pytest, dic)
+        if hasattr(self, '_config'):
+            self.call_plugin(plugin, "pytest_addoption",
+                {'parser': self._config._parser})
+            self.call_plugin(plugin, "pytest_configure",
+                {'config': self._config})
+
+    def _setns(self, obj, dic):
+        import pytest
+        for name, value in dic.items():
+            if isinstance(value, dict):
+                mod = getattr(obj, name, None)
+                if mod is None:
+                    modname = "pytest.%s" % name
+                    mod = py.std.types.ModuleType(modname)
+                    sys.modules[modname] = mod
+                    mod.__all__ = []
+                    setattr(obj, name, mod)
+                obj.__all__.append(name)
+                self._setns(mod, value)
+            else:
+                setattr(obj, name, value)
+                obj.__all__.append(name)
+                #if obj != pytest:
+                #    pytest.__all__.append(name)
+                setattr(pytest, name, value)
+
+    def pytest_terminal_summary(self, terminalreporter):
+        tw = terminalreporter._tw
+        if terminalreporter.config.option.traceconfig:
+            for hint in self._hints:
+                tw.line("hint: %s" % hint)
+
+    def do_addoption(self, parser):
+        mname = "pytest_addoption"
+        methods = reversed(self.listattr(mname))
+        MultiCall(methods, {'parser': parser}).execute()
+
+    def do_configure(self, config):
+        assert not hasattr(self, '_config')
+        self._config = config
+        config.hook.pytest_configure(config=self._config)
+
+    def do_unconfigure(self, config):
+        config = self._config
+        del self._config
+        config.hook.pytest_unconfigure(config=config)
+        config.pluginmanager.unregister(self)
+
+    def notify_exception(self, excinfo):
+        excrepr = excinfo.getrepr(funcargs=True, showlocals=True)
+        res = self.hook.pytest_internalerror(excrepr=excrepr)
+        if not py.builtin.any(res):
+            for line in str(excrepr).split("\n"):
+                sys.stderr.write("INTERNALERROR> %s\n" %line)
+                sys.stderr.flush()
+
+    def listattr(self, attrname, plugins=None):
+        if plugins is None:
+            plugins = self._plugins
+        l = []
+        last = []
+        for plugin in plugins:
+            try:
+                meth = getattr(plugin, attrname)
+                if hasattr(meth, 'tryfirst'):
+                    last.append(meth)
+                elif hasattr(meth, 'trylast'):
+                    l.insert(0, meth)
+                else:
+                    l.append(meth)
+            except AttributeError:
+                continue
+        l.extend(last)
+        return l
+
+    def call_plugin(self, plugin, methname, kwargs):
+        return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
+                kwargs=kwargs, firstresult=True).execute()
+
+
+def importplugin(importspec):
+    name = importspec
+    try:
+        mod = "_pytest." + name
+        return __import__(mod, None, None, '__doc__')
+    except ImportError:
+        #e = py.std.sys.exc_info()[1]
+        #if str(e).find(name) == -1:
+        #    raise
+        pass #
+    return __import__(importspec, None, None, '__doc__')
+
+class MultiCall:
+    """ execute a call into multiple python functions/methods. """
+    def __init__(self, methods, kwargs, firstresult=False):
+        self.methods = list(methods)
+        self.kwargs = kwargs
+        self.results = []
+        self.firstresult = firstresult
+
+    def __repr__(self):
+        status = "%d results, %d meths" % (len(self.results), len(self.methods))
+        return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
+
+    def execute(self):
+        while self.methods:
+            method = self.methods.pop()
+            kwargs = self.getkwargs(method)
+            res = method(**kwargs)
+            if res is not None:
+                self.results.append(res)
+                if self.firstresult:
+                    return res
+        if not self.firstresult:
+            return self.results
+
+    def getkwargs(self, method):
+        kwargs = {}
+        for argname in varnames(method):
+            try:
+                kwargs[argname] = self.kwargs[argname]
+            except KeyError:
+                if argname == "__multicall__":
+                    kwargs[argname] = self
+        return kwargs
+
+def varnames(func):
+    if not inspect.isfunction(func) and not inspect.ismethod(func):
+        func = getattr(func, '__call__', func)
+    ismethod = inspect.ismethod(func)
+    rawcode = py.code.getrawcode(func)
+    try:
+        return rawcode.co_varnames[ismethod:rawcode.co_argcount]
+    except AttributeError:
+        return ()
+
+class HookRelay:
+    def __init__(self, hookspecs, pm, prefix="pytest_"):
+        if not isinstance(hookspecs, list):
+            hookspecs = [hookspecs]
+        self._hookspecs = []
+        self._pm = pm
+        self.trace = pm.trace.root.get("hook")
+        for hookspec in hookspecs:
+            self._addhooks(hookspec, prefix)
+
+    def _addhooks(self, hookspecs, prefix):
+        self._hookspecs.append(hookspecs)
+        added = False
+        for name, method in vars(hookspecs).items():
+            if name.startswith(prefix):
+                firstresult = getattr(method, 'firstresult', False)
+                hc = HookCaller(self, name, firstresult=firstresult)
+                setattr(self, name, hc)
+                added = True
+                #print ("setting new hook", name)
+        if not added:
+            raise ValueError("did not find new %r hooks in %r" %(
+                prefix, hookspecs,))
+
+
+class HookCaller:
+    def __init__(self, hookrelay, name, firstresult):
+        self.hookrelay = hookrelay
+        self.name = name
+        self.firstresult = firstresult
+        self.trace = self.hookrelay.trace
+
+    def __repr__(self):
+        return "<HookCaller %r>" %(self.name,)
+
+    def __call__(self, **kwargs):
+        methods = self.hookrelay._pm.listattr(self.name)
+        return self._docall(methods, kwargs)
+
+    def pcall(self, plugins, **kwargs):
+        methods = self.hookrelay._pm.listattr(self.name, plugins=plugins)
+        return self._docall(methods, kwargs)
+
+    def _docall(self, methods, kwargs):
+        self.trace(self.name, kwargs)
+        self.trace.root.indent += 1
+        mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
+        try:
+            res = mc.execute()
+            if res:
+                self.trace("finish", self.name, "-->", res)
+        finally:
+            self.trace.root.indent -= 1
+        return res
+
+_preinit = []
+
+def _preloadplugins():
+    _preinit.append(PluginManager(load=True))
+
+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. """
+    if args is None:
+        args = sys.argv[1:]
+    elif isinstance(args, py.path.local):
+        args = [str(args)]
+    elif not isinstance(args, (tuple, list)):
+        if not isinstance(args, str):
+            raise ValueError("not a string or argument list: %r" % (args,))
+        args = py.std.shlex.split(args)
+    if _preinit:
+       _pluginmanager = _preinit.pop(0)
+    else: # subsequent calls to main will create a fresh instance
+        _pluginmanager = PluginManager(load=True)
+    hook = _pluginmanager.hook
+    try:
+        if plugins:
+            for plugin in plugins:
+                _pluginmanager.register(plugin)
+        config = hook.pytest_cmdline_parse(
+                pluginmanager=_pluginmanager, args=args)
+        exitstatus = 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 exitstatus
+
+class UsageError(Exception):
+    """ error in py.test usage or invocation"""
+

File _pytest/doctest.py

+""" discover and run doctests in modules and test files."""
+
+import pytest, py
+from py._code.code import TerminalRepr, ReprFileLocation
+
+def pytest_addoption(parser):
+    group = parser.getgroup("collect")
+    group.addoption("--doctest-modules",
+        action="store_true", default=False,
+        help="run doctests in all .py modules",
+        dest="doctestmodules")
+    group.addoption("--doctest-glob",
+        action="store", default="test*.txt", metavar="pat",
+        help="doctests file matching pattern, default: test*.txt",
+        dest="doctestglob")
+
+def pytest_collect_file(path, parent):
+    config = parent.config
+    if path.ext == ".py":
+        if config.option.doctestmodules:
+            return DoctestModule(path, parent)
+    elif (path.ext in ('.txt', '.rst') and parent.session.isinitpath(path)) or \
+        path.check(fnmatch=config.getvalue("doctestglob")):
+        return DoctestTextfile(path, parent)
+
+class ReprFailDoctest(TerminalRepr):
+    def __init__(self, reprlocation, lines):
+        self.reprlocation = reprlocation
+        self.lines = lines
+    def toterminal(self, tw):
+        for line in self.lines:
+            tw.line(line)
+        self.reprlocation.toterminal(tw)
+
+class DoctestItem(pytest.Item):
+    def repr_failure(self, excinfo):
+        doctest = py.std.doctest
+        if excinfo.errisinstance((doctest.DocTestFailure,
+                                  doctest.UnexpectedException)):
+            doctestfailure = excinfo.value
+            example = doctestfailure.example
+            test = doctestfailure.test
+            filename = test.filename
+            lineno = test.lineno + example.lineno + 1
+            message = excinfo.type.__name__
+            reprlocation = ReprFileLocation(filename, lineno, message)
+            checker = py.std.doctest.OutputChecker()
+            REPORT_UDIFF = py.std.doctest.REPORT_UDIFF
+            filelines = py.path.local(filename).readlines(cr=0)
+            i = max(test.lineno, max(0, lineno - 10)) # XXX?
+            lines = []
+            for line in filelines[i:lineno]:
+                lines.append("%03d %s" % (i+1, line))
+                i += 1
+            if excinfo.errisinstance(doctest.DocTestFailure):
+                lines += checker.output_difference(example,
+                        doctestfailure.got, REPORT_UDIFF).split("\n")
+            else:
+                inner_excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
+                lines += ["UNEXPECTED EXCEPTION: %s" %
+                            repr(inner_excinfo.value)]
+
+            return ReprFailDoctest(reprlocation, lines)
+        else:
+            return super(DoctestItem, self).repr_failure(excinfo)
+
+    def reportinfo(self):
+        return self.fspath, None, "[doctest]"
+
+class DoctestTextfile(DoctestItem, pytest.File):
+    def runtest(self):
+        doctest = py.std.doctest
+        failed, tot = doctest.testfile(
+            str(self.fspath), module_relative=False,
+            optionflags=doctest.ELLIPSIS,
+            raise_on_error=True, verbose=0)
+
+class DoctestModule(DoctestItem, pytest.File):
+    def runtest(self):
+        doctest = py.std.doctest
+        if self.fspath.basename == "conftest.py":
+            module = self.config._conftest.importconftest(self.fspath)
+        else:
+            module = self.fspath.pyimport()
+        failed, tot = doctest.testmod(
+            module, raise_on_error=True, verbose=0,
+            optionflags=doctest.ELLIPSIS)

File _pytest/genscript.py

+""" generate a single-file self-contained version of py.test """
+import py
+import pickle
+import zlib
+import base64
+
+def find_toplevel(name):
+    for syspath in py.std.sys.path:
+        base = py.path.local(syspath)
+        lib = base/name
+        if lib.check(dir=1):
+            return lib
+        mod = base.join("%s.py" % name)
+        if mod.check(file=1):
+            return mod
+    raise LookupError(name)
+
+def pkgname(toplevel, rootpath, path):
+    parts = path.parts()[len(rootpath.parts()):]
+    return '.'.join([toplevel] + [x.purebasename for x in parts])
+
+def pkg_to_mapping(name):
+    toplevel = find_toplevel(name)
+    name2src = {}
+    if toplevel.check(file=1): # module
+        name2src[toplevel.purebasename] = toplevel.read()
+    else: # package
+        for pyfile in toplevel.visit('*.py'):
+            pkg = pkgname(name, toplevel, pyfile)
+            name2src[pkg] = pyfile.read()
+    return name2src
+
+def compress_mapping(mapping):
+    data = pickle.dumps(mapping, 2)
+    data = zlib.compress(data, 9)
+    data = base64.encodestring(data)
+    data = data.decode('ascii')
+    return data
+
+
+def compress_packages(names):
+    mapping = {}
+    for name in names:
+        mapping.update(pkg_to_mapping(name))
+    return compress_mapping(mapping)
+
+
+def generate_script(entry, packages):
+    data = compress_packages(packages)
+    tmpl = py.path.local(__file__).dirpath().join('standalonetemplate.py')
+    exe = tmpl.read()
+    exe = exe.replace('@SOURCES@', data)
+    exe = exe.replace('@ENTRY@', entry)
+    return exe
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup("debugconfig")
+    group.addoption("--genscript", action="store", default=None,
+        dest="genscript", metavar="path",
+        help="create standalone py.test script at given target path.")
+
+def pytest_cmdline_main(config):
+    genscript = config.getvalue("genscript")
+    if genscript:
+        script = generate_script(
+            'import py; raise SystemExit(py.test.cmdline.main())',
+            ['py', '_pytest', 'pytest'],
+        )
+
+        genscript = py.path.local(genscript)
+        genscript.write(script)
+        return 0

File _pytest/helpconfig.py

+""" version info, help messages, tracing configuration.  """
+import py
+import pytest
+import inspect, sys
+
+def pytest_addoption(parser):
+    group = parser.getgroup('debugconfig')
+    group.addoption('--version', action="store_true",
+            help="display pytest lib version and import information.")
+    group._addoption("-h", "--help", action="store_true", dest="help",
+            help="show help message and configuration info")
+    group._addoption('-p', action="append", dest="plugins", default = [],
+               metavar="name",
+               help="early-load given plugin (multi-allowed).")
+    group.addoption('--traceconfig',
+               action="store_true", dest="traceconfig", default=False,
+               help="trace considerations of conftest.py files."),
+    group._addoption('--nomagic',
+               action="store_true", dest="nomagic", default=False,
+               help="don't reinterpret asserts, no traceback cutting. ")
+    group.addoption('--debug',
+               action="store_true", dest="debug", default=False,
+               help="generate and show internal debugging information.")
+
+
+def pytest_cmdline_main(config):
+    if config.option.version:
+        p = py.path.local(pytest.__file__)
+        sys.stderr.write("This is py.test version %s, imported from %s\n" %
+            (pytest.__version__, p))
+        plugininfo = getpluginversioninfo(config)
+        if plugininfo:
+            for line in plugininfo:
+                sys.stderr.write(line + "\n")
+        return 0
+    elif config.option.help:
+        config.pluginmanager.do_configure(config)
+        showhelp(config)
+        return 0
+
+def showhelp(config):
+    tw = py.io.TerminalWriter()
+    tw.write(config._parser.optparser.format_help())
+    tw.line()
+    tw.line()
+    #tw.sep( "=", "config file settings")
+    tw.line("[pytest] ini-options in the next "
+            "pytest.ini|tox.ini|setup.cfg file:")
+    tw.line()
+
+    for name in config._parser._ininames:
+        help, type, default = config._parser._inidict[name]
+        if type is None:
+            type = "string"
+        spec = "%s (%s)" % (name, type)
+        line = "  %-24s %s" %(spec, help)
+        tw.line(line[:tw.fullwidth])
+
+    tw.line() ; tw.line()
+    #tw.sep("=")
+    return
+
+    tw.line("conftest.py options:")
+    tw.line()
+    conftestitems = sorted(config._parser._conftestdict.items())
+    for name, help in conftest_options + conftestitems:
+        line = "   %-15s  %s" %(name, help)
+        tw.line(line[:tw.fullwidth])
+    tw.line()
+    #tw.sep( "=")
+
+conftest_options = [
+    ('pytest_plugins', 'list of plugin names to load'),
+]
+
+def getpluginversioninfo(config):
+    lines = []
+    plugininfo = config.pluginmanager._plugin_distinfo
+    if plugininfo:
+        lines.append("setuptools registered plugins:")
+        for dist, plugin in plugininfo:
+            loc = getattr(plugin, '__file__', repr(plugin))
+            content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
+            lines.append("  " + content)
+    return lines
+
+def pytest_report_header(config):
+    lines = []
+    if config.option.debug or config.option.traceconfig:
+        lines.append("using: pytest-%s pylib-%s" %
+            (pytest.__version__,py.__version__))
+
+        verinfo = getpluginversioninfo(config)
+        if verinfo:
+            lines.extend(verinfo)
+            
+    if config.option.traceconfig:
+        lines.append("active plugins:")
+        plugins = []
+        items = config.pluginmanager._name2plugin.items()
+        for name, plugin in items:
+            if hasattr(plugin, '__file__'):
+                r = plugin.__file__
+            else:
+                r = repr(plugin)
+            lines.append("    %-20s: %s" %(name, r))
+    return lines
+
+
+# =====================================================
+# validate plugin syntax and hooks
+# =====================================================
+
+def pytest_plugin_registered(manager, plugin):
+    methods = collectattr(plugin)
+    hooks = {}
+    for hookspec in manager.hook._hookspecs:
+        hooks.update(collectattr(hookspec))
+
+    stringio = py.io.TextIO()
+    def Print(*args):
+        if args:
+            stringio.write(" ".join(map(str, args)))
+        stringio.write("\n")
+
+    fail = False
+    while methods:
+        name, method = methods.popitem()
+        #print "checking", name
+        if isgenerichook(name):
+            continue
+        if name not in hooks:
+            if not getattr(method, 'optionalhook', False):
+                Print("found unknown hook:", name)
+                fail = True
+        else:
+            #print "checking", method
+            method_args = getargs(method)
+            #print "method_args", method_args
+            if '__multicall__' in method_args:
+                method_args.remove('__multicall__')
+            hook = hooks[name]
+            hookargs = getargs(hook)
+            for arg in method_args:
+                if arg not in hookargs:
+                    Print("argument %r not available"  %(arg, ))
+                    Print("actual definition: %s" %(formatdef(method)))
+                    Print("available hook arguments: %s" %
+                            ", ".join(hookargs))
+                    fail = True
+                    break
+            #if not fail:
+            #    print "matching hook:", formatdef(method)
+        if fail:
+            name = getattr(plugin, '__name__', plugin)
+            raise PluginValidationError("%s:\n%s" % (name, stringio.getvalue()))
+
+class PluginValidationError(Exception):
+    """ plugin failed validation. """
+
+def isgenerichook(name):
+    return name == "pytest_plugins" or \
+           name.startswith("pytest_funcarg__")
+
+def getargs(func):
+    args = inspect.getargs(py.code.getrawcode(func))[0]
+    startindex = inspect.ismethod(func) and 1 or 0
+    return args[startindex:]
+
+def collectattr(obj):
+    methods = {}
+    for apiname in dir(obj):
+        if apiname.startswith("pytest_"):
+            methods[apiname] = getattr(obj, apiname)
+    return methods
+
+def formatdef(func):
+    return "%s%s" % (
+        func.__name__,
+        inspect.formatargspec(*inspect.getargspec(func))
+    )
+

File _pytest/hookspec.py

+""" hook specifications for pytest plugins, invoked from main.py and builtin plugins.  """
+
+# -------------------------------------------------------------------------
+# Initialization
+# -------------------------------------------------------------------------
+
+def pytest_addhooks(pluginmanager):
+    """called at plugin load time to allow adding new hooks via a call to
+    pluginmanager.registerhooks(module)."""
+
+
+def pytest_namespace():
+    """return dict of name->object to be made globally available in
+    the py.test/pytest namespace.  This hook is called before command
+    line options are parsed.
+    """
+
+def pytest_cmdline_parse(pluginmanager, args):
+    """return initialized config object, parsing the specified args. """
+pytest_cmdline_parse.firstresult = True
+
+def pytest_cmdline_preparse(config, args):
+    """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(...)``.
+    """
+
+def pytest_cmdline_main(config):
+    """ called for performing the main command line action. The default
+    implementation will invoke the configure hooks and runtest_mainloop. """
+pytest_cmdline_main.firstresult = True
+
+def pytest_configure(config):
+    """ called after command line options have been parsed.
+        and all plugins and initial conftest files been loaded.
+    """
+
+def pytest_unconfigure(config):
+    """ called before test process is exited.  """
+
+def pytest_runtestloop(session):
+    """ called for performing the main runtest loop
+    (after collection finished). """
+pytest_runtestloop.firstresult = True
+
+# -------------------------------------------------------------------------
+# collection hooks
+# -------------------------------------------------------------------------
+
+def pytest_collection(session):
+    """ perform the collection protocol for the given session. """
+pytest_collection.firstresult = True
+
+def pytest_collection_modifyitems(session, config, items):
+    """ called after collection has been performed, may filter or re-order
+    the items in-place."""
+
+def pytest_collection_finish(session):
+    """ called after collection has been performed and modified. """
+
+def pytest_ignore_collect(path, config):
+    """ return True to prevent considering this path for collection.
+    This hook is consulted for all files and directories prior to calling
+    more specific hooks.
+    """
+pytest_ignore_collect.firstresult = True
+
+def pytest_collect_directory(path, parent):
+    """ called before traversing a directory for collection files. """
+pytest_collect_directory.firstresult = True
+
+def pytest_collect_file(path, parent):
+    """ return collection Node or None for the given path. Any new node
+    needs to have the specified ``parent`` as a parent."""
+
+# logging hooks for collection
+def pytest_collectstart(collector):
+    """ collector starts collecting. """
+
+def pytest_itemcollected(item):
+    """ we just collected a test item. """
+
+def pytest_collectreport(report):
+    """ collector finished collecting. """
+
+def pytest_deselected(items):
+    """ called for test items deselected by keyword. """
+
+def pytest_make_collect_report(collector):
+    """ perform ``collector.collect()`` and return a CollectReport. """
+pytest_make_collect_report.firstresult = True
+
+# -------------------------------------------------------------------------
+# Python test function related hooks
+# -------------------------------------------------------------------------
+
+def pytest_pycollect_makemodule(path, parent):
+    """ return a Module collector or None for the given path.
+    This hook will be called for each matching test module path.
+    The pytest_collect_file hook needs to be used if you want to
+    create test modules for files that do not match as a test module.
+    """
+pytest_pycollect_makemodule.firstresult = True
+
+def pytest_pycollect_makeitem(collector, name, obj):
+    """ return custom item/collector for a python object in a module, or None.  """
+pytest_pycollect_makeitem.firstresult = True
+
+def pytest_pyfunc_call(pyfuncitem):
+    """ call underlying test function. """
+pytest_pyfunc_call.firstresult = True
+
+def pytest_generate_tests(metafunc):
+    """ generate (multiple) parametrized calls to a test function."""
+
+# -------------------------------------------------------------------------
+# generic runtest related hooks
+# -------------------------------------------------------------------------
+def pytest_itemstart(item, node=None):
+    """ (deprecated, use pytest_runtest_logstart). """
+
+def pytest_runtest_protocol(item):
+    """ implements the standard runtest_setup/call/teardown protocol including
+    capturing exceptions and calling reporting hooks on the results accordingly.
+
+    :return boolean: True if no further hook implementations should be invoked.
+    """
+pytest_runtest_protocol.firstresult = True
+
+def pytest_runtest_logstart(nodeid, location):
+    """ signal the start of a test run. """
+
+def pytest_runtest_setup(item):
+    """ called before ``pytest_runtest_call(item)``. """
+
+def pytest_runtest_call(item):
+    """ called to execute the test ``item``. """
+
+def pytest_runtest_teardown(item):
+    """ called after ``pytest_runtest_call``. """
+
+def pytest_runtest_makereport(item, call):
+    """ return a :py:class:`_pytest.runner.TestReport` object
+    for the given :py:class:`pytest.Item` and
+    :py:class:`_pytest.runner.CallInfo`.
+    """
+pytest_runtest_makereport.firstresult = True
+
+def pytest_runtest_logreport(report):
+    """ process item test report. """
+
+# special handling for final teardown - somewhat internal for now
+def pytest__teardown_final(session):
+    """ called before test session finishes. """
+pytest__teardown_final.firstresult = True
+
+def pytest__teardown_final_logerror(report, session):
+    """ called if runtest_teardown_final failed. """
+
+# -------------------------------------------------------------------------
+# test session related hooks
+# -------------------------------------------------------------------------
+
+def pytest_sessionstart(session):
+    """ before session.main() is called. """
+
+def pytest_sessionfinish(session, exitstatus):
+    """ whole test run finishes. """
+
+
+# -------------------------------------------------------------------------
+# hooks for customising the assert methods
+# -------------------------------------------------------------------------
+
+def pytest_assertrepr_compare(config, op, left, right):
+    """return explanation for comparisons in failing assert expressions.
+
+    Return None for no custom explanation, otherwise return a list
+    of strings.  The strings will be joined by newlines but any newlines
+    *in* a string will be escaped.  Note that all but the first line will
+    be indented sligthly, the intention is for the first line to be a summary.
+    """
+
+# -------------------------------------------------------------------------
+# hooks for influencing reporting (invoked from _pytest_terminal)
+# -------------------------------------------------------------------------
+
+def pytest_report_header(config):
+    """ return a string to be displayed as header info for terminal reporting."""
+
+def pytest_report_teststatus(report):
+    """ return result-category, shortletter and verbose word for reporting."""
+pytest_report_teststatus.firstresult = True
+
+def pytest_terminal_summary(terminalreporter):
+    """ add additional section in terminal summary reporting. """
+
+# -------------------------------------------------------------------------
+# doctest hooks
+# -------------------------------------------------------------------------
+
+def pytest_doctest_prepare_content(content):
+    """ return processed content for a given doctest"""
+pytest_doctest_prepare_content.firstresult = True
+
+# -------------------------------------------------------------------------
+# error handling and internal debugging hooks
+# -------------------------------------------------------------------------
+
+def pytest_plugin_registered(plugin, manager):
+    """ a new py lib plugin got registered. """
+
+def pytest_plugin_unregistered(plugin):
+    """ a py lib plugin got unregistered. """
+
+def pytest_internalerror(excrepr):
+    """ called for internal errors. """
+
+def pytest_keyboard_interrupt(excinfo):
+    """ called for keyboard interrupt. """

File _pytest/junitxml.py

+""" report test results in JUnit-XML format, for use with Hudson and build integration servers.
+
+Based on initial code from Ross Lawley.
+"""
+
+import py
+import os
+import time
+
+def pytest_addoption(parser):
+    group = parser.getgroup("terminal reporting")
+    group.addoption('--junitxml', action="store", dest="xmlpath",
+           metavar="path", default=None,
+           help="create junit-xml style report file at given path.")
+    group.addoption('--junitprefix', action="store", dest="junitprefix",
+           metavar="str", default=None,
+           help="prepend prefix to classnames in junit-xml output")
+
+def pytest_configure(config):
+    xmlpath = config.option.xmlpath
+    if xmlpath:
+        config._xml = LogXML(xmlpath, config.option.junitprefix)
+        config.pluginmanager.register(config._xml)
+
+def pytest_unconfigure(config):
+    xml = getattr(config, '_xml', None)
+    if xml:
+        del config._xml
+        config.pluginmanager.unregister(xml)
+
+class LogXML(object):
+    def __init__(self, logfile, prefix):
+        self.logfile = logfile
+        self.prefix = prefix
+        self.test_logs = []
+        self.passed = self.skipped = 0
+        self.failed = self.errors = 0
+        self._durations = {}
+
+    def _opentestcase(self, report):
+        names = report.nodeid.split("::")
+        names[0] = names[0].replace("/", '.')
+        names = tuple(names)
+        d = {'time': self._durations.pop(names, "0")}
+        names = [x.replace(".py", "") for x in names if x != "()"]
+        classnames = names[:-1]
+        if self.prefix:
+            classnames.insert(0, self.prefix)
+        d['classname'] = ".".join(classnames)
+        d['name'] = py.xml.escape(names[-1])
+        attrs = ['%s="%s"' % item for item in sorted(d.items())]
+        self.test_logs.append("\n<testcase %s>" % " ".join(attrs))
+
+    def _closetestcase(self):
+        self.test_logs.append("</testcase>")
+
+    def appendlog(self, fmt, *args):
+        args = tuple([py.xml.escape(arg) for arg in args])
+        self.test_logs.append(fmt % args)
+
+    def append_pass(self, report):
+        self.passed += 1
+        self._opentestcase(report)
+        self._closetestcase()
+
+    def append_failure(self, report):
+        self._opentestcase(report)
+        #msg = str(report.longrepr.reprtraceback.extraline)
+        if "xfail" in report.keywords:
+            self.appendlog(
+                '<skipped message="xfail-marked test passes unexpectedly"/>')
+            self.skipped += 1
+        else:
+            self.appendlog('<failure message="test failure">%s</failure>',
+                report.longrepr)
+            self.failed += 1
+        self._closetestcase()
+
+    def append_collect_failure(self, report):
+        self._opentestcase(report)
+        #msg = str(report.longrepr.reprtraceback.extraline)
+        self.appendlog('<failure message="collection failure">%s</failure>',
+            report.longrepr)
+        self._closetestcase()
+        self.errors += 1
+
+    def append_collect_skipped(self, report):
+        self._opentestcase(report)
+        #msg = str(report.longrepr.reprtraceback.extraline)
+        self.appendlog('<skipped message="collection skipped">%s</skipped>',
+            report.longrepr)
+        self._closetestcase()
+        self.skipped += 1
+
+    def append_error(self, report):
+        self._opentestcase(report)
+        self.appendlog('<error message="test setup failure">%s</error>',
+            report.longrepr)
+        self._closetestcase()
+        self.errors += 1
+
+    def append_skipped(self, report):
+        self._opentestcase(report)
+        if "xfail" in report.keywords:
+            self.appendlog(
+                '<skipped message="expected test failure">%s</skipped>',
+                report.keywords['xfail'])
+        else:
+            self.appendlog("<skipped/>")
+        self._closetestcase()
+        self.skipped += 1
+
+    def pytest_runtest_logreport(self, report):
+        if report.passed:
+            self.append_pass(report)
+        elif report.failed:
+            if report.when != "call":
+                self.append_error(report)
+            else:
+                self.append_failure(report)
+        elif report.skipped:
+            self.append_skipped(report)
+
+    def pytest_runtest_call(self, item, __multicall__):
+        names = tuple(item.listnames())
+        start = time.time()
+        try:
+            return __multicall__.execute()
+        finally:
+            self._durations[names] = time.time() - start
+
+    def pytest_collectreport(self, report):