diff options
Diffstat (limited to 'lib/spack/external/_pytest/main.py')
-rw-r--r-- | lib/spack/external/_pytest/main.py | 245 |
1 files changed, 159 insertions, 86 deletions
diff --git a/lib/spack/external/_pytest/main.py b/lib/spack/external/_pytest/main.py index 52876c12a4..eacae8dab1 100644 --- a/lib/spack/external/_pytest/main.py +++ b/lib/spack/external/_pytest/main.py @@ -1,18 +1,21 @@ """ core implementation of testing process: init, session, runtest loop. """ +from __future__ import absolute_import, division, print_function + import functools import os import sys import _pytest +from _pytest import nodes import _pytest._code import py -import pytest try: from collections import MutableMapping as MappingMixin except ImportError: from UserDict import DictMixin as MappingMixin -from _pytest.config import directory_arg +from _pytest.config import directory_arg, UsageError, hookimpl +from _pytest.outcomes import exit from _pytest.runner import collect_one_node tracebackcutdir = py.path.local(_pytest.__file__).dirpath() @@ -25,63 +28,73 @@ EXIT_INTERNALERROR = 3 EXIT_USAGEERROR = 4 EXIT_NOTESTSCOLLECTED = 5 + def pytest_addoption(parser): parser.addini("norecursedirs", "directory patterns to avoid for recursion", - type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg']) - parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.", - type="args", default=[]) - #parser.addini("dirpatterns", + type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv']) + parser.addini("testpaths", "directories to search for tests when no files or directories are given in the " + "command line.", + type="args", default=[]) + # parser.addini("dirpatterns", # "patterns specifying possible locations of test files", # type="linelist", default=["**/test_*.txt", # "**/test_*.py", "**/*_test.py"] - #) + # ) group = parser.getgroup("general", "running and selection options") group._addoption('-x', '--exitfirst', action="store_const", - dest="maxfail", const=1, - help="exit instantly on first error or failed test."), + dest="maxfail", const=1, + help="exit instantly on first error or failed test."), group._addoption('--maxfail', metavar="num", - action="store", type=int, dest="maxfail", default=0, - help="exit after first num failures or errors.") + 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.") + help="marks not registered in configuration file raise errors.") group._addoption("-c", metavar="file", type=str, dest="inifilename", - help="load configuration from `file` instead of trying to locate one of the implicit configuration files.") + help="load configuration from `file` instead of trying to locate one of the implicit " + "configuration files.") group._addoption("--continue-on-collection-errors", action="store_true", - default=False, dest="continue_on_collection_errors", - help="Force test execution even if collection errors occur.") + default=False, dest="continue_on_collection_errors", + help="Force test execution even if collection errors occur.") group = parser.getgroup("collect", "collection") group.addoption('--collectonly', '--collect-only', action="store_true", - help="only collect tests, don't execute them."), + help="only collect tests, don't execute them."), group.addoption('--pyargs', action="store_true", - help="try to interpret all arguments as python packages.") + help="try to interpret all arguments as python packages.") group.addoption("--ignore", action="append", metavar="path", - help="ignore path during collection (multi-allowed).") + help="ignore path during collection (multi-allowed).") # when changing this to --conf-cut-dir, config.py Conftest.setinitial # needs upgrading as well group.addoption('--confcutdir', dest="confcutdir", default=None, - metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"), - help="only load conftest.py's relative to specified dir.") + metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"), + help="only load conftest.py's relative to specified dir.") group.addoption('--noconftest', action="store_true", - dest="noconftest", default=False, - help="Don't load any conftest.py files.") + dest="noconftest", default=False, + help="Don't load any conftest.py files.") group.addoption('--keepduplicates', '--keep-duplicates', action="store_true", - dest="keepduplicates", default=False, - help="Keep duplicate tests.") + dest="keepduplicates", default=False, + help="Keep duplicate tests.") + group.addoption('--collect-in-virtualenv', action='store_true', + dest='collect_in_virtualenv', default=False, + help="Don't ignore tests in a local virtualenv directory") group = parser.getgroup("debugconfig", - "test session debugging and configuration") + "test session debugging and configuration") group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir", - help="base temporary directory for this test run.") + help="base temporary directory for this test run.") def pytest_namespace(): - collect = dict(Item=Item, Collector=Collector, File=File, Session=Session) - return dict(collect=collect) + """keeping this one works around a deeper startup issue in pytest + + i tried to find it for a while but the amount of time turned unsustainable, + so i put a hack in to revisit later + """ + return {} def pytest_configure(config): - pytest.config = config # compatibiltiy + __import__('pytest').config = config # compatibiltiy def wrap_session(config, doit): @@ -96,17 +109,16 @@ def wrap_session(config, doit): config.hook.pytest_sessionstart(session=session) initstate = 2 session.exitstatus = doit(config, session) or 0 - except pytest.UsageError: + except UsageError: raise except KeyboardInterrupt: excinfo = _pytest._code.ExceptionInfo() - if initstate < 2 and isinstance( - excinfo.value, pytest.exit.Exception): + if initstate < 2 and isinstance(excinfo.value, exit.Exception): sys.stderr.write('{0}: {1}\n'.format( excinfo.typename, excinfo.value.msg)) config.hook.pytest_keyboard_interrupt(excinfo=excinfo) session.exitstatus = EXIT_INTERRUPTED - except: + except: # noqa excinfo = _pytest._code.ExceptionInfo() config.notify_exception(excinfo, config.option) session.exitstatus = EXIT_INTERNALERROR @@ -123,9 +135,11 @@ def wrap_session(config, doit): config._ensure_unconfigure() return session.exitstatus + def pytest_cmdline_main(config): return wrap_session(config, _main) + def _main(config, session): """ default command line protocol for initialization, session, running tests and reporting. """ @@ -137,9 +151,11 @@ def _main(config, session): elif session.testscollected == 0: return EXIT_NOTESTSCOLLECTED + def pytest_collection(session): return session.perform_collect() + def pytest_runtestloop(session): if (session.testsfailed and not session.config.option.continue_on_collection_errors): @@ -150,21 +166,36 @@ def pytest_runtestloop(session): return True for i, item in enumerate(session.items): - nextitem = session.items[i+1] if i+1 < len(session.items) else None + nextitem = session.items[i + 1] if i + 1 < len(session.items) else None item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) if session.shouldstop: raise session.Interrupted(session.shouldstop) return True + +def _in_venv(path): + """Attempts to detect if ``path`` is the root of a Virtual Environment by + checking for the existence of the appropriate activate script""" + bindir = path.join('Scripts' if sys.platform.startswith('win') else 'bin') + if not bindir.exists(): + return False + activates = ('activate', 'activate.csh', 'activate.fish', + 'Activate', 'Activate.bat', 'Activate.ps1') + return any([fname.basename in activates for fname in bindir.listdir()]) + + def pytest_ignore_collect(path, config): - p = path.dirpath() - ignore_paths = config._getconftest_pathlist("collect_ignore", path=p) + ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath()) ignore_paths = ignore_paths or [] excludeopt = config.getoption("ignore") if excludeopt: ignore_paths.extend([py.path.local(x) for x in excludeopt]) - if path in ignore_paths: + if py.path.local(path) in ignore_paths: + return True + + allow_in_venv = config.getoption("collect_in_virtualenv") + if _in_venv(path) and not allow_in_venv: return True # Skip duplicate paths. @@ -190,14 +221,22 @@ class FSHookProxy: self.__dict__[name] = x return x -def compatproperty(name): - def fget(self): - import warnings - warnings.warn("This usage is deprecated, please use pytest.{0} instead".format(name), - PendingDeprecationWarning, stacklevel=2) - return getattr(pytest, name) - return property(fget) +class _CompatProperty(object): + def __init__(self, name): + self.name = name + + def __get__(self, obj, owner): + if obj is None: + return self + + # TODO: reenable in the features branch + # warnings.warn( + # "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format( + # name=self.name, owner=type(owner).__name__), + # PendingDeprecationWarning, stacklevel=2) + return getattr(__import__('pytest'), self.name) + class NodeKeywords(MappingMixin): def __init__(self, node): @@ -269,24 +308,28 @@ class Node(object): """ fspath sensitive hook proxy used to call pytest hooks""" return self.session.gethookproxy(self.fspath) - Module = compatproperty("Module") - Class = compatproperty("Class") - Instance = compatproperty("Instance") - Function = compatproperty("Function") - File = compatproperty("File") - Item = compatproperty("Item") + Module = _CompatProperty("Module") + Class = _CompatProperty("Class") + Instance = _CompatProperty("Instance") + Function = _CompatProperty("Function") + File = _CompatProperty("File") + Item = _CompatProperty("Item") def _getcustomclass(self, name): - cls = getattr(self, name) - if cls != getattr(pytest, name): - py.log._apiwarn("2.0", "use of node.%s is deprecated, " - "use pytest_pycollect_makeitem(...) to create custom " - "collection nodes" % name) + maybe_compatprop = getattr(type(self), name) + if isinstance(maybe_compatprop, _CompatProperty): + return getattr(__import__('pytest'), name) + else: + cls = getattr(self, name) + # TODO: reenable in the features branch + # warnings.warn("use of node.%s is deprecated, " + # "use pytest_pycollect_makeitem(...) to create custom " + # "collection nodes" % name, category=DeprecationWarning) 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)) def warn(self, code, message): """ generate a warning with the given code and message for this @@ -295,9 +338,6 @@ class Node(object): fslocation = getattr(self, "location", None) if fslocation is None: fslocation = getattr(self, "fspath", None) - else: - fslocation = "%s:%s" % (fslocation[0], fslocation[1] + 1) - self.ihook.pytest_logwarning.call_historic(kwargs=dict( code=code, message=message, nodeid=self.nodeid, fslocation=fslocation)) @@ -335,7 +375,7 @@ class Node(object): res = function() except py.builtin._sysex: raise - except: + except: # noqa failure = sys.exc_info() setattr(self, exattrname, failure) raise @@ -358,9 +398,9 @@ class Node(object): ``marker`` can be a string or pytest.mark.* instance. """ - from _pytest.mark import MarkDecorator + from _pytest.mark import MarkDecorator, MARK_GEN if isinstance(marker, py.builtin._basestring): - marker = MarkDecorator(marker) + marker = getattr(MARK_GEN, marker) elif not isinstance(marker, MarkDecorator): raise ValueError("is not a string or pytest.mark.* Marker") self.keywords[marker.name] = marker @@ -410,7 +450,7 @@ class Node(object): return excinfo.value.formatrepr() tbfilter = True if self.config.option.fulltrace: - style="long" + style = "long" else: tb = _pytest._code.Traceback([excinfo.traceback[-1]]) self._prunetraceback(excinfo) @@ -438,6 +478,7 @@ class Node(object): repr_failure = _repr_failure_py + class Collector(Node): """ Collector instances create children through collect() and thus iteratively build a tree. @@ -459,10 +500,6 @@ class Collector(Node): return str(exc.args[0]) return self._repr_failure_py(excinfo, style="short") - def _memocollect(self): - """ internal helper method to cache results of calling collect(). """ - return self._memoizedcall('_collected', lambda: list(self.collect())) - def _prunetraceback(self, excinfo): if hasattr(self, 'fspath'): traceback = excinfo.traceback @@ -471,27 +508,38 @@ class Collector(Node): ntraceback = ntraceback.cut(excludepath=tracebackcutdir) excinfo.traceback = ntraceback.filter() + class FSCollector(Collector): def __init__(self, fspath, parent=None, config=None, session=None): - fspath = py.path.local(fspath) # xxx only for test_resultlog.py? + fspath = py.path.local(fspath) # xxx only for test_resultlog.py? name = fspath.basename if parent is not None: rel = fspath.relto(parent.fspath) if rel: name = rel - name = name.replace(os.sep, "/") + name = name.replace(os.sep, nodes.SEP) super(FSCollector, self).__init__(name, parent, config, session) self.fspath = fspath + def _check_initialpaths_for_relpath(self): + for initialpath in self.session._initialpaths: + if self.fspath.common(initialpath) == initialpath: + return self.fspath.relto(initialpath.dirname) + def _makeid(self): relpath = self.fspath.relto(self.config.rootdir) - if os.sep != "/": - relpath = relpath.replace(os.sep, "/") + + if not relpath: + relpath = self._check_initialpaths_for_relpath() + if os.sep != nodes.SEP: + relpath = relpath.replace(os.sep, nodes.SEP) return relpath + class File(FSCollector): """ base class for collecting tests from a file. """ + class Item(Node): """ a basic test invocation item. Note that for a single function there might be multiple test invocation items. @@ -503,6 +551,21 @@ class Item(Node): self._report_sections = [] def add_report_section(self, when, key, content): + """ + Adds a new report section, similar to what's done internally to add stdout and + stderr captured output:: + + item.add_report_section("call", "stdout", "report section contents") + + :param str when: + One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``. + :param str key: + Name of the section, can be customized at will. Pytest uses ``"stdout"`` and + ``"stderr"`` internally. + + :param str content: + The full contents as a string. + """ if content: self._report_sections.append((when, key, content)) @@ -526,12 +589,15 @@ class Item(Node): self._location = location return location + class NoMatch(Exception): """ raised if matching cannot locate a matching names. """ + class Interrupted(KeyboardInterrupt): """ signals an interrupted test run. """ - __module__ = 'builtins' # for py3 + __module__ = 'builtins' # for py3 + class Session(FSCollector): Interrupted = Interrupted @@ -550,12 +616,12 @@ class Session(FSCollector): def _makeid(self): return "" - @pytest.hookimpl(tryfirst=True) + @hookimpl(tryfirst=True) def pytest_collectstart(self): if self.shouldstop: raise self.Interrupted(self.shouldstop) - @pytest.hookimpl(tryfirst=True) + @hookimpl(tryfirst=True) def pytest_runtest_logreport(self, report): if report.failed and not hasattr(report, 'wasxfail'): self.testsfailed += 1 @@ -586,8 +652,9 @@ class Session(FSCollector): hook = self.config.hook try: items = self._perform_collect(args, genitems) + self.config.pluginmanager.check_pending() hook.pytest_collection_modifyitems(session=self, - config=self.config, items=items) + config=self.config, items=items) finally: hook.pytest_collection_finish(session=self) self.testscollected = len(items) @@ -614,8 +681,8 @@ class Session(FSCollector): for arg, exc in self._notfound: line = "(no name %r in any of %r)" % (arg, exc.args[0]) errors.append("not found: %s\n%s" % (arg, line)) - #XXX: test this - raise pytest.UsageError(*errors) + # XXX: test this + raise UsageError(*errors) if not genitems: return rep.result else: @@ -643,7 +710,7 @@ class Session(FSCollector): names = self._parsearg(arg) path = names.pop(0) if path.check(dir=1): - assert not names, "invalid arg %r" %(arg,) + 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): for x in self._collectfile(path): @@ -702,9 +769,11 @@ class Session(FSCollector): path = self.config.invocation_dir.join(relpath, abs=True) if not path.check(): if self.config.option.pyargs: - raise pytest.UsageError("file or package not found: " + arg + " (missing __init__.py?)") + raise UsageError( + "file or package not found: " + arg + + " (missing __init__.py?)") else: - raise pytest.UsageError("file not found: " + arg) + raise UsageError("file not found: " + arg) parts[0] = path return parts @@ -727,11 +796,11 @@ class Session(FSCollector): nextnames = names[1:] resultnodes = [] for node in matching: - if isinstance(node, pytest.Item): + if isinstance(node, Item): if not names: resultnodes.append(node) continue - assert isinstance(node, pytest.Collector) + assert isinstance(node, Collector) rep = collect_one_node(node) if rep.passed: has_matched = False @@ -744,16 +813,20 @@ class Session(FSCollector): if not has_matched and len(rep.result) == 1 and x.name == "()": nextnames.insert(0, name) resultnodes.extend(self.matchnodes([x], nextnames)) - node.ihook.pytest_collectreport(report=rep) + else: + # report collection failures here to avoid failing to run some test + # specified in the command line because the module could not be + # imported (#134) + node.ihook.pytest_collectreport(report=rep) return resultnodes def genitems(self, node): self.trace("genitems", node) - if isinstance(node, pytest.Item): + if isinstance(node, Item): node.ihook.pytest_itemcollected(item=node) yield node else: - assert isinstance(node, pytest.Collector) + assert isinstance(node, Collector) rep = collect_one_node(node) if rep.passed: for subnode in rep.result: |