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 = {}
|
||||
|
||||
def parametrize(self, argnames, argvalues, indirect=False, ids=None,
|
||||
scope="function"):
|
||||
scope=None):
|
||||
""" Add new invocations to the underlying test function using the list
|
||||
of argvalues for the given argnames. Parametrization is performed
|
||||
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
|
||||
that they are part of the test id. If no ids are provided they will
|
||||
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)):
|
||||
argnames = (argnames,)
|
||||
|
@ -656,7 +661,7 @@ class Metafunc(FuncargnamesCompatAttr):
|
|||
argvalues = [(_notexists,) * len(argnames)]
|
||||
|
||||
if scope is None:
|
||||
scope = "function"
|
||||
scope = "subfunction"
|
||||
scopenum = scopes.index(scope)
|
||||
if not indirect:
|
||||
#XXX should we also check for the opposite case?
|
||||
|
@ -893,9 +898,9 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
|
|||
setattr(self.markers, name, val)
|
||||
|
||||
# contstruct a list of all neccessary fixtures for this test function
|
||||
if hasattr(self.markers, "usefixtures"):
|
||||
try:
|
||||
usefixtures = list(self.markers.usefixtures.args)
|
||||
else:
|
||||
except AttributeError:
|
||||
usefixtures = []
|
||||
self.fixturenames = (self.session._fixturemanager.getdefaultfixtures() +
|
||||
usefixtures + self._getfuncargnames())
|
||||
|
@ -938,10 +943,7 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
|
|||
fs, lineno = self._getfslineno()
|
||||
pytest.skip("got empty parameter set, function %s at %s:%d" %(
|
||||
self.function.__name__, fs, lineno))
|
||||
|
||||
super(Function, self).setup()
|
||||
#if hasattr(self, "_request"):
|
||||
# self._request._callsetup()
|
||||
fillfixtures(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
|
@ -1103,7 +1105,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
|||
def _fillfixtures(self):
|
||||
item = self._pyfuncitem
|
||||
fixturenames = getattr(item, "fixturenames", self.fixturenames)
|
||||
|
||||
for argname in fixturenames:
|
||||
if argname not in item.funcargs:
|
||||
item.funcargs[argname] = self.getfuncargvalue(argname)
|
||||
|
@ -1146,7 +1147,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
|||
return val
|
||||
|
||||
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
|
||||
to call another function argument factory. If there are two
|
||||
funcarg factories for the same test function argument the first
|
||||
|
@ -1181,8 +1182,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
|||
if fixturedef.active:
|
||||
return fixturedef.cached_result
|
||||
|
||||
# prepare request scope and param attributes before
|
||||
# calling into factory
|
||||
# prepare request _currentarg and param attributes before
|
||||
# calling into fixture function
|
||||
argname = fixturedef.argname
|
||||
node = self._pyfuncitem
|
||||
mp = monkeypatch()
|
||||
|
@ -1193,7 +1194,18 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
|||
pass
|
||||
else:
|
||||
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
|
||||
try:
|
||||
paramscopenum = node.callspec._arg2scopenum[argname]
|
||||
except (KeyError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
if paramscopenum != scopenum_subfunction:
|
||||
scope = scopes[paramscopenum]
|
||||
|
||||
if scope is not None:
|
||||
__tracebackhide__ = True
|
||||
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)
|
||||
"""
|
||||
|
||||
scopes = "session module class function".split()
|
||||
scopes = "session module class function subfunction".split()
|
||||
scopenum_subfunction = scopes.index("subfunction")
|
||||
def scopemismatch(currentscope, newscope):
|
||||
return scopes.index(newscope) > scopes.index(currentscope)
|
||||
|
||||
|
|
|
@ -1370,6 +1370,28 @@ class TestMetafuncFunctional:
|
|||
"*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):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
|
|
Loading…
Reference in New Issue