allow metafunc.parametrize(scope=...) calls to override the scope of a Fixture function definition. This is useful for cases where you want to dynamically
set scope and parametrization for a fixture instead of statically declaring it on the fixture function.
This commit is contained in:
parent
55a8bfd174
commit
d3893dd5d1
|
@ -628,7 +628,7 @@ class Metafunc(FuncargnamesCompatAttr):
|
||||||
self._arg2scopenum = {}
|
self._arg2scopenum = {}
|
||||||
|
|
||||||
def parametrize(self, argnames, argvalues, indirect=False, ids=None,
|
def parametrize(self, argnames, argvalues, indirect=False, ids=None,
|
||||||
scope="function"):
|
scope=None):
|
||||||
""" Add new invocations to the underlying test function using the list
|
""" Add new invocations to the underlying test function using the list
|
||||||
of argvalues for the given argnames. Parametrization is performed
|
of argvalues for the given argnames. Parametrization is performed
|
||||||
during the collection phase. If you need to setup expensive resources
|
during the collection phase. If you need to setup expensive resources
|
||||||
|
@ -648,6 +648,11 @@ class Metafunc(FuncargnamesCompatAttr):
|
||||||
:arg ids: list of string ids each corresponding to the argvalues so
|
:arg ids: list of string ids each corresponding to the argvalues so
|
||||||
that they are part of the test id. If no ids are provided they will
|
that they are part of the test id. If no ids are provided they will
|
||||||
be generated automatically from the argvalues.
|
be generated automatically from the argvalues.
|
||||||
|
|
||||||
|
:arg scope: if specified: denotes the scope of the parameters.
|
||||||
|
The scope is used for sorting tests by parameters. It will
|
||||||
|
also override any fixture-function defined scope, allowing
|
||||||
|
to set a dynamic scope from test context and configuration.
|
||||||
"""
|
"""
|
||||||
if not isinstance(argnames, (tuple, list)):
|
if not isinstance(argnames, (tuple, list)):
|
||||||
argnames = (argnames,)
|
argnames = (argnames,)
|
||||||
|
@ -656,7 +661,7 @@ class Metafunc(FuncargnamesCompatAttr):
|
||||||
argvalues = [(_notexists,) * len(argnames)]
|
argvalues = [(_notexists,) * len(argnames)]
|
||||||
|
|
||||||
if scope is None:
|
if scope is None:
|
||||||
scope = "function"
|
scope = "subfunction"
|
||||||
scopenum = scopes.index(scope)
|
scopenum = scopes.index(scope)
|
||||||
if not indirect:
|
if not indirect:
|
||||||
#XXX should we also check for the opposite case?
|
#XXX should we also check for the opposite case?
|
||||||
|
@ -893,9 +898,9 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
|
||||||
setattr(self.markers, name, val)
|
setattr(self.markers, name, val)
|
||||||
|
|
||||||
# contstruct a list of all neccessary fixtures for this test function
|
# contstruct a list of all neccessary fixtures for this test function
|
||||||
if hasattr(self.markers, "usefixtures"):
|
try:
|
||||||
usefixtures = list(self.markers.usefixtures.args)
|
usefixtures = list(self.markers.usefixtures.args)
|
||||||
else:
|
except AttributeError:
|
||||||
usefixtures = []
|
usefixtures = []
|
||||||
self.fixturenames = (self.session._fixturemanager.getdefaultfixtures() +
|
self.fixturenames = (self.session._fixturemanager.getdefaultfixtures() +
|
||||||
usefixtures + self._getfuncargnames())
|
usefixtures + self._getfuncargnames())
|
||||||
|
@ -938,10 +943,7 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
|
||||||
fs, lineno = self._getfslineno()
|
fs, lineno = self._getfslineno()
|
||||||
pytest.skip("got empty parameter set, function %s at %s:%d" %(
|
pytest.skip("got empty parameter set, function %s at %s:%d" %(
|
||||||
self.function.__name__, fs, lineno))
|
self.function.__name__, fs, lineno))
|
||||||
|
|
||||||
super(Function, self).setup()
|
super(Function, self).setup()
|
||||||
#if hasattr(self, "_request"):
|
|
||||||
# self._request._callsetup()
|
|
||||||
fillfixtures(self)
|
fillfixtures(self)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
@ -1103,7 +1105,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
def _fillfixtures(self):
|
def _fillfixtures(self):
|
||||||
item = self._pyfuncitem
|
item = self._pyfuncitem
|
||||||
fixturenames = getattr(item, "fixturenames", self.fixturenames)
|
fixturenames = getattr(item, "fixturenames", self.fixturenames)
|
||||||
|
|
||||||
for argname in fixturenames:
|
for argname in fixturenames:
|
||||||
if argname not in item.funcargs:
|
if argname not in item.funcargs:
|
||||||
item.funcargs[argname] = self.getfuncargvalue(argname)
|
item.funcargs[argname] = self.getfuncargvalue(argname)
|
||||||
|
@ -1146,7 +1147,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def getfuncargvalue(self, argname):
|
def getfuncargvalue(self, argname):
|
||||||
""" Retrieve a function argument by name for this test
|
""" Retrieve a fixture function argument by name for this test
|
||||||
function invocation. This allows one function argument factory
|
function invocation. This allows one function argument factory
|
||||||
to call another function argument factory. If there are two
|
to call another function argument factory. If there are two
|
||||||
funcarg factories for the same test function argument the first
|
funcarg factories for the same test function argument the first
|
||||||
|
@ -1181,8 +1182,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
if fixturedef.active:
|
if fixturedef.active:
|
||||||
return fixturedef.cached_result
|
return fixturedef.cached_result
|
||||||
|
|
||||||
# prepare request scope and param attributes before
|
# prepare request _currentarg and param attributes before
|
||||||
# calling into factory
|
# calling into fixture function
|
||||||
argname = fixturedef.argname
|
argname = fixturedef.argname
|
||||||
node = self._pyfuncitem
|
node = self._pyfuncitem
|
||||||
mp = monkeypatch()
|
mp = monkeypatch()
|
||||||
|
@ -1193,7 +1194,18 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
mp.setattr(self, 'param', param, raising=False)
|
mp.setattr(self, 'param', param, raising=False)
|
||||||
|
|
||||||
|
# if a parametrize invocation set a scope it will override
|
||||||
|
# the static scope defined with the fixture function
|
||||||
scope = fixturedef.scope
|
scope = fixturedef.scope
|
||||||
|
try:
|
||||||
|
paramscopenum = node.callspec._arg2scopenum[argname]
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if paramscopenum != scopenum_subfunction:
|
||||||
|
scope = scopes[paramscopenum]
|
||||||
|
|
||||||
if scope is not None:
|
if scope is not None:
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
if scopemismatch(self.scope, scope):
|
if scopemismatch(self.scope, scope):
|
||||||
|
@ -1251,7 +1263,8 @@ class ScopeMismatchError(Exception):
|
||||||
which has a lower scope (e.g. a Session one calls a function one)
|
which has a lower scope (e.g. a Session one calls a function one)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
scopes = "session module class function".split()
|
scopes = "session module class function subfunction".split()
|
||||||
|
scopenum_subfunction = scopes.index("subfunction")
|
||||||
def scopemismatch(currentscope, newscope):
|
def scopemismatch(currentscope, newscope):
|
||||||
return scopes.index(newscope) > scopes.index(currentscope)
|
return scopes.index(newscope) > scopes.index(currentscope)
|
||||||
|
|
||||||
|
|
|
@ -1370,6 +1370,28 @@ class TestMetafuncFunctional:
|
||||||
"*1 passed*"
|
"*1 passed*"
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(("scope", "length"),
|
||||||
|
[("module", 2), ("function", 4)])
|
||||||
|
def test_parametrize_scope_overrides(self, testdir, scope, length):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
l = []
|
||||||
|
def pytest_generate_tests(metafunc):
|
||||||
|
if "arg" in metafunc.funcargnames:
|
||||||
|
metafunc.parametrize("arg", [1,2], indirect=True,
|
||||||
|
scope=%r)
|
||||||
|
def pytest_funcarg__arg(request):
|
||||||
|
l.append(request.param)
|
||||||
|
return request.param
|
||||||
|
def test_hello(arg):
|
||||||
|
assert arg in (1,2)
|
||||||
|
def test_world(arg):
|
||||||
|
assert arg in (1,2)
|
||||||
|
def test_checklength():
|
||||||
|
assert len(l) == %d
|
||||||
|
""" % (scope, length))
|
||||||
|
reprec = testdir.inline_run()
|
||||||
|
reprec.assertoutcome(passed=5)
|
||||||
|
|
||||||
def test_conftest_funcargs_only_available_in_subdir(testdir):
|
def test_conftest_funcargs_only_available_in_subdir(testdir):
|
||||||
sub1 = testdir.mkpydir("sub1")
|
sub1 = testdir.mkpydir("sub1")
|
||||||
|
|
Loading…
Reference in New Issue