implement fixture information stored on the parentnode of functions
to be reused by metafunc mechanics and Function setup
This commit is contained in:
parent
4541456a96
commit
021c087701
2
IMPL.txt
2
IMPL.txt
|
@ -30,7 +30,7 @@ object model
|
|||
---------------------
|
||||
|
||||
As part of the metafunc-protocol parents of Function nodes get a
|
||||
FuncFixtureInfos() object, containing helping/caching for
|
||||
ParentFixtures() object, containing helping/caching for
|
||||
for a function. .getfixtureinfo(func) returns a FixtureInfo with these
|
||||
attributes:
|
||||
|
||||
|
|
|
@ -309,7 +309,11 @@ class PyCollector(PyobjMixin, pytest.Collector):
|
|||
clscol = self.getparent(Class)
|
||||
cls = clscol and clscol.obj or None
|
||||
transfer_markers(funcobj, cls, module)
|
||||
metafunc = Metafunc(funcobj, parentnode=self, cls=cls, module=module)
|
||||
if not hasattr(self, "_fixturemapper"):
|
||||
self._fixturemapper = FixtureMapper(self)
|
||||
fixtureinfo = self._fixturemapper.getfixtureinfo(funcobj, cls)
|
||||
metafunc = Metafunc(funcobj, fixtureinfo, self.config,
|
||||
cls=cls, module=module)
|
||||
gentesthook = self.config.hook.pytest_generate_tests
|
||||
extra = [module]
|
||||
if cls is not None:
|
||||
|
@ -326,6 +330,32 @@ class PyCollector(PyobjMixin, pytest.Collector):
|
|||
callspec=callspec, callobj=funcobj,
|
||||
keywords={callspec.id:True})
|
||||
|
||||
class FixtureMapper:
|
||||
def __init__(self, node):
|
||||
self.node = node
|
||||
self.fm = node.session._fixturemanager
|
||||
self._name2fixtureinfo = {}
|
||||
|
||||
def getfixtureinfo(self, func, cls):
|
||||
try:
|
||||
return self._name2fixtureinfo[func]
|
||||
except KeyError:
|
||||
pass
|
||||
argnames = getfuncargnames(func, int(cls is not None))
|
||||
usefixtures = getattr(func, "usefixtures", None)
|
||||
if usefixtures is not None:
|
||||
argnames = usefixtures.args + argnames
|
||||
names_closure, arg2fixturedeflist = self.fm.getfixtureclosure(
|
||||
argnames, self.node)
|
||||
fixtureinfo = FuncFixtureInfo(names_closure, arg2fixturedeflist)
|
||||
self._name2fixtureinfo[func] = fixtureinfo
|
||||
return fixtureinfo
|
||||
|
||||
class FuncFixtureInfo:
|
||||
def __init__(self, names_closure, name2fixturedeflist):
|
||||
self.names_closure = names_closure
|
||||
self.name2fixturedeflist = name2fixturedeflist
|
||||
|
||||
def transfer_markers(funcobj, cls, mod):
|
||||
# XXX this should rather be code in the mark plugin or the mark
|
||||
# plugin should merge with the python plugin.
|
||||
|
@ -611,19 +641,12 @@ class FuncargnamesCompatAttr:
|
|||
return self.fixturenames
|
||||
|
||||
class Metafunc(FuncargnamesCompatAttr):
|
||||
def __init__(self, function, parentnode, cls=None, module=None):
|
||||
self.config = parentnode.config
|
||||
def __init__(self, function, fixtureinfo, config, cls=None, module=None):
|
||||
self.config = config
|
||||
self.module = module
|
||||
self.function = function
|
||||
self.parentnode = parentnode
|
||||
argnames = getfuncargnames(function, startindex=int(cls is not None))
|
||||
try:
|
||||
fm = parentnode.session._fixturemanager
|
||||
except AttributeError:
|
||||
self.fixturenames = argnames
|
||||
else:
|
||||
self.fixturenames, self._arg2fixturedeflist = fm.getfixtureclosure(
|
||||
argnames, parentnode)
|
||||
self.fixturenames = fixtureinfo.names_closure
|
||||
self._arg2fixturedeflist = fixtureinfo.name2fixturedeflist
|
||||
self.cls = cls
|
||||
self.module = module
|
||||
self._calls = []
|
||||
|
@ -878,7 +901,7 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
|
|||
Python test function.
|
||||
"""
|
||||
_genid = None
|
||||
def __init__(self, name, parent=None, args=None, config=None,
|
||||
def __init__(self, name, parent, args=None, config=None,
|
||||
callspec=None, callobj=_dummy, keywords=None, session=None):
|
||||
super(Function, self).__init__(name, parent, config=config,
|
||||
session=session)
|
||||
|
@ -908,9 +931,9 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
|
|||
|
||||
# contstruct a list of all neccessary fixtures for this test function
|
||||
try:
|
||||
usefixtures = list(self.markers.usefixtures.args)
|
||||
usefixtures = self.markers.usefixtures.args
|
||||
except AttributeError:
|
||||
usefixtures = []
|
||||
usefixtures = ()
|
||||
self.fixturenames = (self.session._fixturemanager.getdefaultfixtures() +
|
||||
usefixtures + self._getfuncargnames())
|
||||
|
||||
|
@ -1377,16 +1400,16 @@ class FixtureManager:
|
|||
self.pytest_plugin_registered(plugin)
|
||||
|
||||
def getdefaultfixtures(self):
|
||||
""" return a list of default fixture names (XXX for the given file path). """
|
||||
""" return a tuple of default fixture names (XXX for the given file path). """
|
||||
try:
|
||||
return self._defaultfixtures
|
||||
except AttributeError:
|
||||
defaultfixtures = list(self.config.getini("usefixtures"))
|
||||
defaultfixtures = tuple(self.config.getini("usefixtures"))
|
||||
# make sure the self._autofixtures list is sorted
|
||||
# by scope, scopenum 0 is session
|
||||
self._autofixtures.sort(
|
||||
key=lambda x: self.arg2fixturedeflist[x][-1].scopenum)
|
||||
defaultfixtures.extend(self._autofixtures)
|
||||
defaultfixtures = defaultfixtures + tuple(self._autofixtures)
|
||||
self._defaultfixtures = defaultfixtures
|
||||
return defaultfixtures
|
||||
|
||||
|
@ -1573,8 +1596,8 @@ def getfuncargnames(function, startindex=None):
|
|||
getattr(function, '__defaults__', None)) or ()
|
||||
numdefaults = len(defaults)
|
||||
if numdefaults:
|
||||
return argnames[startindex:-numdefaults]
|
||||
return argnames[startindex:]
|
||||
return tuple(argnames[startindex:-numdefaults])
|
||||
return tuple(argnames[startindex:])
|
||||
|
||||
# algorithm for sorting on a per-parametrized resource setup basis
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ class TestCaseFunction(pytest.Function):
|
|||
_excinfo = None
|
||||
|
||||
def _getfuncargnames(self):
|
||||
return []
|
||||
return ()
|
||||
|
||||
def setup(self):
|
||||
self._testcase = self.parent.obj(self.name)
|
||||
|
|
|
@ -535,17 +535,17 @@ def test_getfuncargnames():
|
|||
def f(): pass
|
||||
assert not funcargs.getfuncargnames(f)
|
||||
def g(arg): pass
|
||||
assert funcargs.getfuncargnames(g) == ['arg']
|
||||
assert funcargs.getfuncargnames(g) == ('arg',)
|
||||
def h(arg1, arg2="hello"): pass
|
||||
assert funcargs.getfuncargnames(h) == ['arg1']
|
||||
assert funcargs.getfuncargnames(h) == ('arg1',)
|
||||
def h(arg1, arg2, arg3="hello"): pass
|
||||
assert funcargs.getfuncargnames(h) == ['arg1', 'arg2']
|
||||
assert funcargs.getfuncargnames(h) == ('arg1', 'arg2')
|
||||
class A:
|
||||
def f(self, arg1, arg2="hello"):
|
||||
pass
|
||||
assert funcargs.getfuncargnames(A().f) == ['arg1']
|
||||
assert funcargs.getfuncargnames(A().f) == ('arg1',)
|
||||
if sys.version_info < (3,0):
|
||||
assert funcargs.getfuncargnames(A.f) == ['arg1']
|
||||
assert funcargs.getfuncargnames(A.f) == ('arg1',)
|
||||
|
||||
|
||||
class TestFillFixtures:
|
||||
|
@ -910,9 +910,16 @@ class TestRequestCachedSetup:
|
|||
|
||||
class TestMetafunc:
|
||||
def Metafunc(self, func):
|
||||
class parent:
|
||||
config = None
|
||||
return funcargs.Metafunc(func, parentnode=parent)
|
||||
# the unit tests of this class check if things work correctly
|
||||
# on the funcarg level, so we don't need a full blown
|
||||
# initiliazation
|
||||
class FixtureInfo:
|
||||
name2fixturedeflist = None
|
||||
def __init__(self, names):
|
||||
self.names_closure = names
|
||||
names = funcargs.getfuncargnames(func)
|
||||
fixtureinfo = FixtureInfo(names)
|
||||
return funcargs.Metafunc(func, fixtureinfo, None)
|
||||
|
||||
def test_no_funcargs(self, testdir):
|
||||
def function(): pass
|
||||
|
@ -1393,6 +1400,20 @@ class TestMetafuncFunctional:
|
|||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=5)
|
||||
|
||||
def test_usemarkers_seen_in_generate_tests(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert "abc" in metafunc.fixturenames
|
||||
metafunc.parametrize("abc", [1])
|
||||
|
||||
@pytest.mark.usefixtures("abc")
|
||||
def test_function():
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_conftest_funcargs_only_available_in_subdir(testdir):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
sub2 = testdir.mkpydir("sub2")
|
||||
|
|
Loading…
Reference in New Issue