summaryrefslogtreecommitdiff
path: root/lib/spack/external/_pytest/main.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/external/_pytest/main.py')
-rw-r--r--lib/spack/external/_pytest/main.py245
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: