implement fixture information stored on the parentnode of functions

to be reused by metafunc mechanics and Function setup
This commit is contained in:
holger krekel 2012-10-16 13:47:59 +02:00
parent 4541456a96
commit 021c087701
4 changed files with 74 additions and 30 deletions

View File

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

View File

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

View File

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

View File

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