Implement invocation-scoped fixtures
This commit is contained in:
parent
29289b472f
commit
775100881a
|
@ -161,7 +161,7 @@ def capsys(request):
|
||||||
captured output available via ``capsys.readouterr()`` method calls
|
captured output available via ``capsys.readouterr()`` method calls
|
||||||
which return a ``(out, err)`` tuple.
|
which return a ``(out, err)`` tuple.
|
||||||
"""
|
"""
|
||||||
if "capfd" in request._funcargs:
|
if "capfd" in request.fixturenames:
|
||||||
raise request.raiseerror(error_capsysfderror)
|
raise request.raiseerror(error_capsysfderror)
|
||||||
request.node._capfuncarg = c = CaptureFixture(SysCapture, request)
|
request.node._capfuncarg = c = CaptureFixture(SysCapture, request)
|
||||||
return c
|
return c
|
||||||
|
@ -172,7 +172,7 @@ def capfd(request):
|
||||||
captured output available via ``capfd.readouterr()`` method calls
|
captured output available via ``capfd.readouterr()`` method calls
|
||||||
which return a ``(out, err)`` tuple.
|
which return a ``(out, err)`` tuple.
|
||||||
"""
|
"""
|
||||||
if "capsys" in request._funcargs:
|
if "capsys" in request.fixturenames:
|
||||||
request.raiseerror(error_capsysfderror)
|
request.raiseerror(error_capsysfderror)
|
||||||
if not hasattr(os, 'dup'):
|
if not hasattr(os, 'dup'):
|
||||||
pytest.skip("capfd funcarg needs os.dup")
|
pytest.skip("capfd funcarg needs os.dup")
|
||||||
|
|
|
@ -1838,34 +1838,51 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
self.fixturename = None
|
self.fixturename = None
|
||||||
#: Scope string, one of "function", "class", "module", "session"
|
#: Scope string, one of "function", "class", "module", "session"
|
||||||
self.scope = "function"
|
self.scope = "function"
|
||||||
self._funcargs = {}
|
# rename both attributes below because their key has changed; better an attribute error
|
||||||
self._fixturedefs = {}
|
# than subtle key misses; also backward incompatibility
|
||||||
|
self._fixture_values = {} # (argname, scope) -> fixture value
|
||||||
|
self._fixture_defs = {} # (argname, scope) -> FixtureDef
|
||||||
fixtureinfo = pyfuncitem._fixtureinfo
|
fixtureinfo = pyfuncitem._fixtureinfo
|
||||||
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
|
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
|
||||||
self._arg2index = {}
|
self._arg2index = {}
|
||||||
self.fixturenames = fixtureinfo.names_closure
|
|
||||||
self._fixturemanager = pyfuncitem.session._fixturemanager
|
self._fixturemanager = pyfuncitem.session._fixturemanager
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fixturenames(self):
|
||||||
|
# backward incompatible note: now a readonly property
|
||||||
|
return list(self._pyfuncitem._fixtureinfo.names_closure)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def node(self):
|
def node(self):
|
||||||
""" underlying collection node (depends on current request scope)"""
|
""" underlying collection node (depends on current request scope)"""
|
||||||
return self._getscopeitem(self.scope)
|
return self._getscopeitem(self.scope)
|
||||||
|
|
||||||
|
|
||||||
def _getnextfixturedef(self, argname):
|
def _getnextfixturedef(self, argname, scope):
|
||||||
|
def trygetfixturedefs(argname):
|
||||||
fixturedefs = self._arg2fixturedefs.get(argname, None)
|
fixturedefs = self._arg2fixturedefs.get(argname, None)
|
||||||
|
if fixturedefs is None:
|
||||||
|
fixturedefs = self._arg2fixturedefs.get(argname + ':' + scope, None)
|
||||||
|
return fixturedefs
|
||||||
|
|
||||||
|
fixturedefs = trygetfixturedefs(argname)
|
||||||
if fixturedefs is None:
|
if fixturedefs is None:
|
||||||
# we arrive here because of a a dynamic call to
|
# we arrive here because of a a dynamic call to
|
||||||
# getfixturevalue(argname) usage which was naturally
|
# getfixturevalue(argname) usage which was naturally
|
||||||
# not known at parsing/collection time
|
# not known at parsing/collection time
|
||||||
fixturedefs = self._fixturemanager.getfixturedefs(
|
parentid = self._pyfuncitem.parent.nodeid
|
||||||
argname, self._pyfuncitem.parent.nodeid)
|
fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
|
||||||
|
if fixturedefs:
|
||||||
self._arg2fixturedefs[argname] = fixturedefs
|
self._arg2fixturedefs[argname] = fixturedefs
|
||||||
|
fixturedefs_by_argname = self._fixturemanager.getfixturedefs_multiple_scopes(argname, parentid)
|
||||||
|
if fixturedefs_by_argname:
|
||||||
|
self._arg2fixturedefs.update(fixturedefs_by_argname)
|
||||||
|
fixturedefs = trygetfixturedefs(argname)
|
||||||
# fixturedefs list is immutable so we maintain a decreasing index
|
# fixturedefs list is immutable so we maintain a decreasing index
|
||||||
index = self._arg2index.get(argname, 0) - 1
|
index = self._arg2index.get((argname, scope), 0) - 1
|
||||||
if fixturedefs is None or (-index > len(fixturedefs)):
|
if fixturedefs is None or (-index > len(fixturedefs)):
|
||||||
raise FixtureLookupError(argname, self)
|
raise FixtureLookupError(argname, self)
|
||||||
self._arg2index[argname] = index
|
self._arg2index[(argname, scope)] = index
|
||||||
return fixturedefs[index]
|
return fixturedefs[index]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -2004,10 +2021,10 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
|
|
||||||
def _get_active_fixturedef(self, argname):
|
def _get_active_fixturedef(self, argname):
|
||||||
try:
|
try:
|
||||||
return self._fixturedefs[argname]
|
return self._fixture_defs[(argname, self.scope)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
try:
|
try:
|
||||||
fixturedef = self._getnextfixturedef(argname)
|
fixturedef = self._getnextfixturedef(argname, self.scope)
|
||||||
except FixtureLookupError:
|
except FixtureLookupError:
|
||||||
if argname == "request":
|
if argname == "request":
|
||||||
class PseudoFixtureDef:
|
class PseudoFixtureDef:
|
||||||
|
@ -2018,8 +2035,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
# remove indent to prevent the python3 exception
|
# remove indent to prevent the python3 exception
|
||||||
# from leaking into the call
|
# from leaking into the call
|
||||||
result = self._getfixturevalue(fixturedef)
|
result = self._getfixturevalue(fixturedef)
|
||||||
self._funcargs[argname] = result
|
self._fixture_values[(argname, self.scope)] = result
|
||||||
self._fixturedefs[argname] = fixturedef
|
self._fixture_defs[(argname, self.scope)] = fixturedef
|
||||||
return fixturedef
|
return fixturedef
|
||||||
|
|
||||||
def _get_fixturestack(self):
|
def _get_fixturestack(self):
|
||||||
|
@ -2140,11 +2157,10 @@ class SubRequest(FixtureRequest):
|
||||||
self._fixturedef = fixturedef
|
self._fixturedef = fixturedef
|
||||||
self.addfinalizer = fixturedef.addfinalizer
|
self.addfinalizer = fixturedef.addfinalizer
|
||||||
self._pyfuncitem = request._pyfuncitem
|
self._pyfuncitem = request._pyfuncitem
|
||||||
self._funcargs = request._funcargs
|
self._fixture_values = request._fixture_values
|
||||||
self._fixturedefs = request._fixturedefs
|
self._fixture_defs = request._fixture_defs
|
||||||
self._arg2fixturedefs = request._arg2fixturedefs
|
self._arg2fixturedefs = request._arg2fixturedefs
|
||||||
self._arg2index = request._arg2index
|
self._arg2index = request._arg2index
|
||||||
self.fixturenames = request.fixturenames
|
|
||||||
self._fixturemanager = request._fixturemanager
|
self._fixturemanager = request._fixturemanager
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -2184,7 +2200,7 @@ class FixtureLookupError(LookupError):
|
||||||
fspath, lineno = getfslineno(function)
|
fspath, lineno = getfslineno(function)
|
||||||
try:
|
try:
|
||||||
lines, _ = inspect.getsourcelines(get_real_func(function))
|
lines, _ = inspect.getsourcelines(get_real_func(function))
|
||||||
except (IOError, IndexError):
|
except (IOError, IndexError, TypeError):
|
||||||
error_msg = "file %s, line %s: source code not available"
|
error_msg = "file %s, line %s: source code not available"
|
||||||
addline(error_msg % (fspath, lineno+1))
|
addline(error_msg % (fspath, lineno+1))
|
||||||
else:
|
else:
|
||||||
|
@ -2198,9 +2214,9 @@ class FixtureLookupError(LookupError):
|
||||||
if msg is None:
|
if msg is None:
|
||||||
fm = self.request._fixturemanager
|
fm = self.request._fixturemanager
|
||||||
available = []
|
available = []
|
||||||
for name, fixturedef in fm._arg2fixturedefs.items():
|
|
||||||
parentid = self.request._pyfuncitem.parent.nodeid
|
parentid = self.request._pyfuncitem.parent.nodeid
|
||||||
faclist = list(fm._matchfactories(fixturedef, parentid))
|
for name, fixturedefs in fm._arg2fixturedefs.items():
|
||||||
|
faclist = list(fm._matchfactories(fixturedefs, parentid))
|
||||||
if faclist:
|
if faclist:
|
||||||
available.append(name)
|
available.append(name)
|
||||||
msg = "fixture %r not found" % (self.argname,)
|
msg = "fixture %r not found" % (self.argname,)
|
||||||
|
@ -2348,6 +2364,11 @@ class FixtureManager:
|
||||||
if fixturedefs:
|
if fixturedefs:
|
||||||
arg2fixturedefs[argname] = fixturedefs
|
arg2fixturedefs[argname] = fixturedefs
|
||||||
merge(fixturedefs[-1].argnames)
|
merge(fixturedefs[-1].argnames)
|
||||||
|
fixturedefs_by_argname = self.getfixturedefs_multiple_scopes(argname, parentid)
|
||||||
|
if fixturedefs_by_argname:
|
||||||
|
arg2fixturedefs.update(fixturedefs_by_argname)
|
||||||
|
for fixturedefs in fixturedefs_by_argname.values():
|
||||||
|
merge(fixturedefs[-1].argnames)
|
||||||
return fixturenames_closure, arg2fixturedefs
|
return fixturenames_closure, arg2fixturedefs
|
||||||
|
|
||||||
def pytest_generate_tests(self, metafunc):
|
def pytest_generate_tests(self, metafunc):
|
||||||
|
@ -2402,21 +2423,31 @@ class FixtureManager:
|
||||||
if marker.name:
|
if marker.name:
|
||||||
name = marker.name
|
name = marker.name
|
||||||
assert not name.startswith(self._argprefix), name
|
assert not name.startswith(self._argprefix), name
|
||||||
fixturedef = FixtureDef(self, nodeid, name, obj,
|
|
||||||
marker.scope, marker.params,
|
def new_fixture_def(name, scope):
|
||||||
|
fixture_def = FixtureDef(self, nodeid, name, obj,
|
||||||
|
scope, marker.params,
|
||||||
unittest=unittest, ids=marker.ids)
|
unittest=unittest, ids=marker.ids)
|
||||||
|
|
||||||
faclist = self._arg2fixturedefs.setdefault(name, [])
|
faclist = self._arg2fixturedefs.setdefault(name, [])
|
||||||
if fixturedef.has_location:
|
if fixture_def.has_location:
|
||||||
faclist.append(fixturedef)
|
faclist.append(fixture_def)
|
||||||
else:
|
else:
|
||||||
# fixturedefs with no location are at the front
|
# fixturedefs with no location are at the front
|
||||||
# so this inserts the current fixturedef after the
|
# so this inserts the current fixturedef after the
|
||||||
# existing fixturedefs from external plugins but
|
# existing fixturedefs from external plugins but
|
||||||
# before the fixturedefs provided in conftests.
|
# before the fixturedefs provided in conftests.
|
||||||
i = len([f for f in faclist if not f.has_location])
|
i = len([f for f in faclist if not f.has_location])
|
||||||
faclist.insert(i, fixturedef)
|
faclist.insert(i, fixture_def)
|
||||||
if marker.autouse:
|
if marker.autouse:
|
||||||
autousenames.append(name)
|
autousenames.append(name)
|
||||||
|
|
||||||
|
if marker.scope == 'invocation':
|
||||||
|
for new_scope in scopes:
|
||||||
|
new_fixture_def(name + ':{0}'.format(new_scope), new_scope)
|
||||||
|
else:
|
||||||
|
new_fixture_def(name, marker.scope)
|
||||||
|
|
||||||
if autousenames:
|
if autousenames:
|
||||||
self._nodeid_and_autousenames.append((nodeid or '', autousenames))
|
self._nodeid_and_autousenames.append((nodeid or '', autousenames))
|
||||||
|
|
||||||
|
@ -2433,6 +2464,18 @@ class FixtureManager:
|
||||||
if nodeid.startswith(fixturedef.baseid):
|
if nodeid.startswith(fixturedef.baseid):
|
||||||
yield fixturedef
|
yield fixturedef
|
||||||
|
|
||||||
|
def getfixturedefs_multiple_scopes(self, argname, nodeid):
|
||||||
|
prefix = argname + ':'
|
||||||
|
fixturedefs_by_argname = dict((k, v) for k, v in self._arg2fixturedefs.items()
|
||||||
|
if k.startswith(prefix))
|
||||||
|
if fixturedefs_by_argname:
|
||||||
|
result = {}
|
||||||
|
for argname, fixturedefs in fixturedefs_by_argname.items():
|
||||||
|
result[argname] = tuple(self._matchfactories(fixturedefs, nodeid))
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def fail_fixturefunc(fixturefunc, msg):
|
def fail_fixturefunc(fixturefunc, msg):
|
||||||
fs, lineno = getfslineno(fixturefunc)
|
fs, lineno = getfslineno(fixturefunc)
|
||||||
|
@ -2518,7 +2561,7 @@ class FixtureDef:
|
||||||
assert not hasattr(self, "cached_result")
|
assert not hasattr(self, "cached_result")
|
||||||
|
|
||||||
ihook = self._fixturemanager.session.ihook
|
ihook = self._fixturemanager.session.ihook
|
||||||
ihook.pytest_fixture_setup(fixturedef=self, request=request)
|
return ihook.pytest_fixture_setup(fixturedef=self, request=request)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ("<FixtureDef name=%r scope=%r baseid=%r >" %
|
return ("<FixtureDef name=%r scope=%r baseid=%r >" %
|
||||||
|
|
Loading…
Reference in New Issue