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