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:
holger krekel 2012-10-06 21:01:13 +02:00
parent 55a8bfd174
commit d3893dd5d1
2 changed files with 47 additions and 12 deletions

View File

@ -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)

View File

@ -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")