diff options
Diffstat (limited to 'lib/spack/external/_pytest/fixtures.py')
-rw-r--r-- | lib/spack/external/_pytest/fixtures.py | 229 |
1 files changed, 116 insertions, 113 deletions
diff --git a/lib/spack/external/_pytest/fixtures.py b/lib/spack/external/_pytest/fixtures.py index 28bcd4d8d7..98317a4889 100644 --- a/lib/spack/external/_pytest/fixtures.py +++ b/lib/spack/external/_pytest/fixtures.py @@ -1,22 +1,36 @@ -import sys +from __future__ import absolute_import, division, print_function -from py._code.code import FormattedExcinfo +import inspect +import sys +import warnings import py -import pytest -import warnings +from py._code.code import FormattedExcinfo -import inspect import _pytest +from _pytest import nodes from _pytest._code.code import TerminalRepr from _pytest.compat import ( NOTSET, exc_clear, _format_args, getfslineno, get_real_func, is_generator, isclass, getimfunc, getlocation, getfuncargnames, + safe_getattr, + FuncargnamesCompatAttr, ) +from _pytest.outcomes import fail, TEST_OUTCOME + + +from ordereddict_backport import OrderedDict + def pytest_sessionstart(session): + import _pytest.python + scopename2class.update({ + 'class': _pytest.python.Class, + 'module': _pytest.python.Module, + 'function': _pytest.main.Item, + }) session._fixturemanager = FixtureManager(session) @@ -29,6 +43,7 @@ scope2props["class"] = scope2props["module"] + ("cls",) scope2props["instance"] = scope2props["class"] + ("instance", ) scope2props["function"] = scope2props["instance"] + ("function", "keywords") + def scopeproperty(name=None, doc=None): def decoratescope(func): scopename = name or func.__name__ @@ -43,19 +58,6 @@ def scopeproperty(name=None, doc=None): return decoratescope -def pytest_namespace(): - scopename2class.update({ - 'class': pytest.Class, - 'module': pytest.Module, - 'function': pytest.Item, - }) - return { - 'fixture': fixture, - 'yield_fixture': yield_fixture, - 'collect': {'_fillfuncargs': fillfixtures} - } - - def get_scope_node(node, scope): cls = scopename2class.get(scope) if cls is None: @@ -73,7 +75,7 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): # XXX we can probably avoid this algorithm if we modify CallSpec2 # to directly care for creating the fixturedefs within its methods. if not metafunc._calls[0].funcargs: - return # this function call does not have direct parametrization + return # this function call does not have direct parametrization # collect funcargs of all callspecs into a list of values arg2params = {} arg2scope = {} @@ -103,36 +105,32 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): if scope != "function": node = get_scope_node(collector, scope) if node is None: - assert scope == "class" and isinstance(collector, pytest.Module) + assert scope == "class" and isinstance(collector, _pytest.python.Module) # use module-level collector for class-scope (for now) node = collector if node and argname in node._name2pseudofixturedef: arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]] else: - fixturedef = FixtureDef(fixturemanager, '', argname, - get_direct_param_fixture_func, - arg2scope[argname], - valuelist, False, False) + fixturedef = FixtureDef(fixturemanager, '', argname, + get_direct_param_fixture_func, + arg2scope[argname], + valuelist, False, False) arg2fixturedefs[argname] = [fixturedef] if node is not None: node._name2pseudofixturedef[argname] = fixturedef - def getfixturemarker(obj): """ return fixturemarker or None if it doesn't exist or raised exceptions.""" try: return getattr(obj, "_pytestfixturefunction", None) - except KeyboardInterrupt: - raise - except Exception: + except TEST_OUTCOME: # some objects raise errors like request (from flask import request) # we don't expect them to be fixture functions return None - def get_parametrized_fixture_keys(item, scopenum): """ return list of keys for all parametrized arguments which match the specified scope. """ @@ -142,10 +140,10 @@ def get_parametrized_fixture_keys(item, scopenum): except AttributeError: pass else: - # cs.indictes.items() is random order of argnames but - # then again different functions (items) can change order of - # arguments so it doesn't matter much probably - for argname, param_index in cs.indices.items(): + # cs.indices.items() is random order of argnames. Need to + # sort this so that different calls to + # get_parametrized_fixture_keys will be deterministic. + for argname, param_index in sorted(cs.indices.items()): if cs._arg2scopenum[argname] != scopenum: continue if scopenum == 0: # session @@ -167,20 +165,21 @@ def reorder_items(items): for scopenum in range(0, scopenum_function): argkeys_cache[scopenum] = d = {} for item in items: - keys = set(get_parametrized_fixture_keys(item, scopenum)) + keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum)) if keys: d[item] = keys return reorder_items_atscope(items, set(), argkeys_cache, 0) + def reorder_items_atscope(items, ignore, argkeys_cache, scopenum): if scopenum >= scopenum_function or len(items) < 3: return items items_done = [] while 1: items_before, items_same, items_other, newignore = \ - slice_items(items, ignore, argkeys_cache[scopenum]) + slice_items(items, ignore, argkeys_cache[scopenum]) items_before = reorder_items_atscope( - items_before, ignore, argkeys_cache,scopenum+1) + items_before, ignore, argkeys_cache, scopenum + 1) if items_same is None: # nothing to reorder in this scope assert items_other is None @@ -201,9 +200,9 @@ def slice_items(items, ignore, scoped_argkeys_cache): for i, item in enumerate(it): argkeys = scoped_argkeys_cache.get(item) if argkeys is not None: - argkeys = argkeys.difference(ignore) - if argkeys: # found a slicing key - slicing_argkey = argkeys.pop() + newargkeys = OrderedDict.fromkeys(k for k in argkeys if k not in ignore) + if newargkeys: # found a slicing key + slicing_argkey, _ = newargkeys.popitem() items_before = items[:i] items_same = [item] items_other = [] @@ -211,7 +210,7 @@ def slice_items(items, ignore, scoped_argkeys_cache): for item in it: argkeys = scoped_argkeys_cache.get(item) if argkeys and slicing_argkey in argkeys and \ - slicing_argkey not in ignore: + slicing_argkey not in ignore: items_same.append(item) else: items_other.append(item) @@ -221,17 +220,6 @@ def slice_items(items, ignore, scoped_argkeys_cache): return items, None, None, None - -class FuncargnamesCompatAttr: - """ helper class so that Metafunc, Function and FixtureRequest - don't need to each define the "funcargnames" compatibility attribute. - """ - @property - def funcargnames(self): - """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" - return self.fixturenames - - def fillfixtures(function): """ fill missing funcargs for a test function. """ try: @@ -254,10 +242,10 @@ def fillfixtures(function): request._fillfixtures() - def get_direct_param_fixture_func(request): return request.param + class FuncFixtureInfo: def __init__(self, argnames, names_closure, name2fixturedefs): self.argnames = argnames @@ -296,7 +284,6 @@ class FixtureRequest(FuncargnamesCompatAttr): """ underlying collection node (depends on current request scope)""" return self._getscopeitem(self.scope) - def _getnextfixturedef(self, argname): fixturedefs = self._arg2fixturedefs.get(argname, None) if fixturedefs is None: @@ -318,7 +305,6 @@ class FixtureRequest(FuncargnamesCompatAttr): """ the pytest config object associated with this request. """ return self._pyfuncitem.config - @scopeproperty() def function(self): """ test function object if the request has a per-function scope. """ @@ -327,7 +313,7 @@ class FixtureRequest(FuncargnamesCompatAttr): @scopeproperty("class") def cls(self): """ class (can be None) where the test function was collected. """ - clscol = self._pyfuncitem.getparent(pytest.Class) + clscol = self._pyfuncitem.getparent(_pytest.python.Class) if clscol: return clscol.obj @@ -345,7 +331,7 @@ class FixtureRequest(FuncargnamesCompatAttr): @scopeproperty() def module(self): """ python module object where the test function was collected. """ - return self._pyfuncitem.getparent(pytest.Module).obj + return self._pyfuncitem.getparent(_pytest.python.Module).obj @scopeproperty() def fspath(self): @@ -414,7 +400,7 @@ class FixtureRequest(FuncargnamesCompatAttr): :arg extrakey: added to internal caching key of (funcargname, scope). """ if not hasattr(self.config, '_setupcache'): - self.config._setupcache = {} # XXX weakref? + self.config._setupcache = {} # XXX weakref? cachekey = (self.fixturename, self._getscopeitem(scope), extrakey) cache = self.config._setupcache try: @@ -445,7 +431,8 @@ class FixtureRequest(FuncargnamesCompatAttr): from _pytest import deprecated warnings.warn( deprecated.GETFUNCARGVALUE, - DeprecationWarning) + DeprecationWarning, + stacklevel=2) return self.getfixturevalue(argname) def _get_active_fixturedef(self, argname): @@ -470,13 +457,13 @@ class FixtureRequest(FuncargnamesCompatAttr): def _get_fixturestack(self): current = self - l = [] + values = [] while 1: fixturedef = getattr(current, "_fixturedef", None) if fixturedef is None: - l.reverse() - return l - l.append(fixturedef) + values.reverse() + return values + values.append(fixturedef) current = current._parent_request def _getfixturevalue(self, fixturedef): @@ -508,7 +495,7 @@ class FixtureRequest(FuncargnamesCompatAttr): source_lineno, ) ) - pytest.fail(msg) + fail(msg) else: # indices might not be set if old-style metafunc.addcall() was used param_index = funcitem.callspec.indices.get(argname, 0) @@ -541,11 +528,11 @@ class FixtureRequest(FuncargnamesCompatAttr): if scopemismatch(invoking_scope, requested_scope): # try to report something helpful lines = self._factorytraceback() - pytest.fail("ScopeMismatch: You tried to access the %r scoped " - "fixture %r with a %r scoped request object, " - "involved factories\n%s" %( - (requested_scope, argname, invoking_scope, "\n".join(lines))), - pytrace=False) + fail("ScopeMismatch: You tried to access the %r scoped " + "fixture %r with a %r scoped request object, " + "involved factories\n%s" % ( + (requested_scope, argname, invoking_scope, "\n".join(lines))), + pytrace=False) def _factorytraceback(self): lines = [] @@ -554,7 +541,7 @@ class FixtureRequest(FuncargnamesCompatAttr): fs, lineno = getfslineno(factory) p = self._pyfuncitem.session.fspath.bestrelpath(fs) args = _format_args(factory) - lines.append("%s:%d: def %s%s" %( + lines.append("%s:%d: def %s%s" % ( p, lineno, factory.__name__, args)) return lines @@ -570,12 +557,13 @@ class FixtureRequest(FuncargnamesCompatAttr): return node def __repr__(self): - return "<FixtureRequest for %r>" %(self.node) + return "<FixtureRequest for %r>" % (self.node) class SubRequest(FixtureRequest): """ a sub request for handling getting a fixture from a test function/fixture. """ + def __init__(self, request, scope, param, param_index, fixturedef): self._parent_request = request self.fixturename = fixturedef.argname @@ -584,9 +572,8 @@ class SubRequest(FixtureRequest): self.param_index = param_index self.scope = scope self._fixturedef = fixturedef - self.addfinalizer = fixturedef.addfinalizer self._pyfuncitem = request._pyfuncitem - self._fixture_values = request._fixture_values + self._fixture_values = request._fixture_values self._fixture_defs = request._fixture_defs self._arg2fixturedefs = request._arg2fixturedefs self._arg2index = request._arg2index @@ -595,6 +582,9 @@ class SubRequest(FixtureRequest): def __repr__(self): return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem) + def addfinalizer(self, finalizer): + self._fixturedef.addfinalizer(finalizer) + class ScopeMismatchError(Exception): """ A fixture function tries to use a different fixture function which @@ -626,6 +616,7 @@ def scope2index(scope, descr, where=None): class FixtureLookupError(LookupError): """ could not return a requested Fixture (missing or invalid). """ + def __init__(self, argname, request, msg=None): self.argname = argname self.request = request @@ -648,9 +639,9 @@ class FixtureLookupError(LookupError): lines, _ = inspect.getsourcelines(get_real_func(function)) except (IOError, IndexError, TypeError): error_msg = "file %s, line %s: source code not available" - addline(error_msg % (fspath, lineno+1)) + addline(error_msg % (fspath, lineno + 1)) else: - addline("file %s, line %s" % (fspath, lineno+1)) + addline("file %s, line %s" % (fspath, lineno + 1)) for i, line in enumerate(lines): line = line.rstrip() addline(" " + line) @@ -666,7 +657,7 @@ class FixtureLookupError(LookupError): if faclist and name not in available: available.append(name) msg = "fixture %r not found" % (self.argname,) - msg += "\n available fixtures: %s" %(", ".join(sorted(available)),) + msg += "\n available fixtures: %s" % (", ".join(sorted(available)),) msg += "\n use 'pytest --fixtures [testpath]' for help on them." return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname) @@ -692,15 +683,16 @@ class FixtureLookupErrorRepr(TerminalRepr): tw.line('{0} {1}'.format(FormattedExcinfo.flow_marker, line.strip()), red=True) tw.line() - tw.line("%s:%d" % (self.filename, self.firstlineno+1)) + tw.line("%s:%d" % (self.filename, self.firstlineno + 1)) def fail_fixturefunc(fixturefunc, msg): fs, lineno = getfslineno(fixturefunc) - location = "%s:%s" % (fs, lineno+1) + location = "%s:%s" % (fs, lineno + 1) source = _pytest._code.Source(fixturefunc) - pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, - pytrace=False) + fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, + pytrace=False) + def call_fixture_func(fixturefunc, request, kwargs): yieldctx = is_generator(fixturefunc) @@ -715,7 +707,7 @@ def call_fixture_func(fixturefunc, request, kwargs): pass else: fail_fixturefunc(fixturefunc, - "yield_fixture function has more than one 'yield'") + "yield_fixture function has more than one 'yield'") request.addfinalizer(teardown) else: @@ -725,6 +717,7 @@ def call_fixture_func(fixturefunc, request, kwargs): class FixtureDef: """ A container for a factory definition. """ + def __init__(self, fixturemanager, baseid, argname, func, scope, params, unittest=False, ids=None): self._fixturemanager = fixturemanager @@ -749,10 +742,19 @@ class FixtureDef: self._finalizer.append(finalizer) def finish(self): + exceptions = [] try: while self._finalizer: - func = self._finalizer.pop() - func() + try: + func = self._finalizer.pop() + func() + except: # noqa + exceptions.append(sys.exc_info()) + if exceptions: + e = exceptions[0] + del exceptions # ensure we don't keep all frames alive because of the traceback + py.builtin._reraise(*e) + finally: ihook = self._fixturemanager.session.ihook ihook.pytest_fixture_post_finalizer(fixturedef=self) @@ -790,6 +792,7 @@ class FixtureDef: return ("<FixtureDef name=%r scope=%r baseid=%r >" % (self.argname, self.scope, self.baseid)) + def pytest_fixture_setup(fixturedef, request): """ Execution of fixture setup. """ kwargs = {} @@ -815,7 +818,7 @@ def pytest_fixture_setup(fixturedef, request): my_cache_key = request.param_index try: result = call_fixture_func(fixturefunc, request, kwargs) - except Exception: + except TEST_OUTCOME: fixturedef.cached_result = (None, my_cache_key, sys.exc_info()) raise fixturedef.cached_result = (result, my_cache_key, None) @@ -833,17 +836,16 @@ class FixtureFunctionMarker: def __call__(self, function): if isclass(function): raise ValueError( - "class fixtures not supported (may be in the future)") + "class fixtures not supported (may be in the future)") function._pytestfixturefunction = self return function - def fixture(scope="function", params=None, autouse=False, ids=None, name=None): """ (return a) decorator to mark a fixture factory function. - This decorator can be used (with or or without parameters) to define - a fixture function. The name of the fixture function can later be + This decorator can be used (with or without parameters) to define a + fixture function. The name of the fixture function can later be referenced to cause its invocation ahead of running tests: test modules or classes can use the pytest.mark.usefixtures(fixturename) marker. Test functions can directly use fixture names as input @@ -862,25 +864,25 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None): reference is needed to activate the fixture. :arg ids: list of string ids each corresponding to the params - so that they are part of the test id. If no ids are provided - they will be generated automatically from the params. + so that they are part of the test id. If no ids are provided + they will be generated automatically from the params. :arg name: the name of the fixture. This defaults to the name of the - decorated function. If a fixture is used in the same module in - which it is defined, the function name of the fixture will be - shadowed by the function arg that requests the fixture; one way - to resolve this is to name the decorated function - ``fixture_<fixturename>`` and then use - ``@pytest.fixture(name='<fixturename>')``. + decorated function. If a fixture is used in the same module in + which it is defined, the function name of the fixture will be + shadowed by the function arg that requests the fixture; one way + to resolve this is to name the decorated function + ``fixture_<fixturename>`` and then use + ``@pytest.fixture(name='<fixturename>')``. Fixtures can optionally provide their values to test functions using a ``yield`` statement, instead of ``return``. In this case, the code block after the ``yield`` statement is executed as teardown code regardless of the test outcome. A fixture function must yield exactly once. """ - if callable(scope) and params is None and autouse == False: + if callable(scope) and params is None and autouse is False: # direct decoration return FixtureFunctionMarker( - "function", params, autouse, name=name)(scope) + "function", params, autouse, name=name)(scope) if params is not None and not isinstance(params, (list, tuple)): params = list(params) return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name) @@ -895,7 +897,7 @@ def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=N if callable(scope) and params is None and not autouse: # direct decoration return FixtureFunctionMarker( - "function", params, autouse, ids=ids, name=name)(scope) + "function", params, autouse, ids=ids, name=name)(scope) else: return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name) @@ -954,14 +956,9 @@ class FixtureManager: self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))] session.config.pluginmanager.register(self, "funcmanage") - def getfixtureinfo(self, node, func, cls, funcargs=True): if funcargs and not hasattr(node, "nofuncargs"): - if cls is not None: - startindex = 1 - else: - startindex = None - argnames = getfuncargnames(func, startindex) + argnames = getfuncargnames(func, cls=cls) else: argnames = () usefixtures = getattr(func, "usefixtures", None) @@ -985,8 +982,8 @@ class FixtureManager: # by their test id) if p.basename.startswith("conftest.py"): nodeid = p.dirpath().relto(self.config.rootdir) - if p.sep != "/": - nodeid = nodeid.replace(p.sep, "/") + if p.sep != nodes.SEP: + nodeid = nodeid.replace(p.sep, nodes.SEP) self.parsefactories(plugin, nodeid) def _getautousenames(self, nodeid): @@ -996,7 +993,7 @@ class FixtureManager: if nodeid.startswith(baseid): if baseid: i = len(baseid) - nextchar = nodeid[i:i+1] + nextchar = nodeid[i:i + 1] if nextchar and nextchar not in ":/": continue autousenames.extend(basenames) @@ -1041,9 +1038,14 @@ class FixtureManager: if faclist: fixturedef = faclist[-1] if fixturedef.params is not None: - func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]]) + parametrize_func = getattr(metafunc.function, 'parametrize', None) + func_params = getattr(parametrize_func, 'args', [[None]]) + func_kwargs = getattr(parametrize_func, 'kwargs', {}) # skip directly parametrized arguments - argnames = func_params[0] + if "argnames" in func_kwargs: + argnames = parametrize_func.kwargs["argnames"] + else: + argnames = func_params[0] if not isinstance(argnames, (tuple, list)): argnames = [x.strip() for x in argnames.split(",") if x.strip()] if argname not in func_params and argname not in argnames: @@ -1068,7 +1070,9 @@ class FixtureManager: self._holderobjseen.add(holderobj) autousenames = [] for name in dir(holderobj): - obj = getattr(holderobj, name, None) + # The attribute can be an arbitrary descriptor, so the attribute + # access below can raise. safe_getatt() ignores such exceptions. + obj = safe_getattr(holderobj, name, None) # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) # or are "@pytest.fixture" marked marker = getfixturemarker(obj) @@ -1079,7 +1083,7 @@ class FixtureManager: continue marker = defaultfuncargprefixmarker from _pytest import deprecated - self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name)) + self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name), nodeid=nodeid) name = name[len(self._argprefix):] elif not isinstance(marker, FixtureFunctionMarker): # magic globals with __getattr__ might have got us a wrong @@ -1129,6 +1133,5 @@ class FixtureManager: def _matchfactories(self, fixturedefs, nodeid): for fixturedef in fixturedefs: - if nodeid.startswith(fixturedef.baseid): + if nodes.ischildnode(fixturedef.baseid, nodeid): yield fixturedef - |