simplify/integrate fixturemapper into FixtureManager
also fix jstests test failures
This commit is contained in:
parent
dc4e205876
commit
98513b995a
|
@ -1,2 +1,2 @@
|
||||||
#
|
#
|
||||||
__version__ = '2.3.0.dev24'
|
__version__ = '2.3.0.dev25'
|
||||||
|
|
|
@ -250,18 +250,6 @@ class PyobjMixin(PyobjContext):
|
||||||
return fspath, lineno, modpath
|
return fspath, lineno, modpath
|
||||||
|
|
||||||
class PyCollector(PyobjMixin, pytest.Collector):
|
class PyCollector(PyobjMixin, pytest.Collector):
|
||||||
def _fixturemapper():
|
|
||||||
def get(self):
|
|
||||||
try:
|
|
||||||
return self._fixturemapper_memo
|
|
||||||
except AttributeError:
|
|
||||||
self._fixturemapper_memo = FixtureMapper(self, funcargs=False)
|
|
||||||
return self._fixturemapper_memo
|
|
||||||
def set(self, val):
|
|
||||||
assert not hasattr(self, "_fixturemapper_memo")
|
|
||||||
self._fixturemapper_memo = val
|
|
||||||
return property(get, set)
|
|
||||||
_fixturemapper = _fixturemapper()
|
|
||||||
|
|
||||||
def funcnamefilter(self, name):
|
def funcnamefilter(self, name):
|
||||||
for prefix in self.config.getini("python_functions"):
|
for prefix in self.config.getini("python_functions"):
|
||||||
|
@ -305,9 +293,8 @@ 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)
|
||||||
if not hasattr(self, "_fixturemapper_memo"):
|
fm = self.session._fixturemanager
|
||||||
self._fixturemapper = FixtureMapper(self)
|
fixtureinfo = fm.getfixtureinfo(self, funcobj, cls)
|
||||||
fixtureinfo = self._fixturemapper.getfixtureinfo(funcobj, cls)
|
|
||||||
metafunc = Metafunc(funcobj, fixtureinfo, self.config,
|
metafunc = Metafunc(funcobj, fixtureinfo, self.config,
|
||||||
cls=cls, module=module)
|
cls=cls, module=module)
|
||||||
gentesthook = self.config.hook.pytest_generate_tests
|
gentesthook = self.config.hook.pytest_generate_tests
|
||||||
|
@ -326,64 +313,6 @@ class PyCollector(PyobjMixin, pytest.Collector):
|
||||||
callspec=callspec, callobj=funcobj,
|
callspec=callspec, callobj=funcobj,
|
||||||
keywords={callspec.id:True})
|
keywords={callspec.id:True})
|
||||||
|
|
||||||
class FixtureMapper:
|
|
||||||
"""
|
|
||||||
pytest fixtures definitions and information is stored and managed
|
|
||||||
from this class.
|
|
||||||
|
|
||||||
During collection fm.parsefactories() is called multiple times to parse
|
|
||||||
fixture function definitions into FixtureDef objects and internal
|
|
||||||
data structures.
|
|
||||||
|
|
||||||
During collection of test functions, metafunc-mechanics instantiate
|
|
||||||
a FuncFixtureInfo object which is cached in a FixtureMapper instance
|
|
||||||
which itself lives on the parent collector. This FuncFixtureInfo object
|
|
||||||
is later retrieved by Function nodes which themselves offer a fixturenames
|
|
||||||
attribute.
|
|
||||||
|
|
||||||
The FuncFixtureInfo object holds information about fixtures and FixtureDefs
|
|
||||||
relevant for a particular function. An initial list of fixtures is
|
|
||||||
assembled like this:
|
|
||||||
|
|
||||||
- ini-defined usefixtures
|
|
||||||
- autouse-marked fixtures along the collection chain up from the function
|
|
||||||
- usefixtures markers at module/class/function level
|
|
||||||
- test function funcargs
|
|
||||||
|
|
||||||
Subsequently the funcfixtureinfo.fixturenames attribute is computed
|
|
||||||
as the closure of the fixtures needed to setup the initial fixtures,
|
|
||||||
i. e. fixtures needed by fixture functions themselves are appended
|
|
||||||
to the fixturenames list.
|
|
||||||
|
|
||||||
Upon the test-setup phases all fixturenames are instantiated, retrieved
|
|
||||||
by a lookup on a FixtureMapper().
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, node, funcargs=True):
|
|
||||||
self.node = node
|
|
||||||
self._name2fixtureinfo = {}
|
|
||||||
self.hasfuncargs = funcargs
|
|
||||||
|
|
||||||
def getfixtureinfo(self, func, cls):
|
|
||||||
try:
|
|
||||||
return self._name2fixtureinfo[func]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if self.hasfuncargs:
|
|
||||||
argnames = getfuncargnames(func, int(cls is not None))
|
|
||||||
else:
|
|
||||||
argnames = ()
|
|
||||||
usefixtures = getattr(func, "usefixtures", None)
|
|
||||||
initialnames = argnames
|
|
||||||
if usefixtures is not None:
|
|
||||||
initialnames = usefixtures.args + initialnames
|
|
||||||
fm = self.node.session._fixturemanager
|
|
||||||
names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames,
|
|
||||||
self.node)
|
|
||||||
fixtureinfo = FuncFixtureInfo(argnames, names_closure,
|
|
||||||
arg2fixturedefs)
|
|
||||||
self._name2fixtureinfo[func] = fixtureinfo
|
|
||||||
return fixtureinfo
|
|
||||||
|
|
||||||
class FuncFixtureInfo:
|
class FuncFixtureInfo:
|
||||||
def __init__(self, argnames, names_closure, name2fixturedefs):
|
def __init__(self, argnames, names_closure, name2fixturedefs):
|
||||||
|
@ -599,11 +528,22 @@ def fillfixtures(function):
|
||||||
try:
|
try:
|
||||||
request = function._request
|
request = function._request
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# the special jstests class with a custom .obj
|
# XXX this special code path is only expected to execute
|
||||||
fi = FixtureMapper(function).getfixtureinfo(function.obj, None)
|
# with the oejskit plugin. It uses classes with funcargs
|
||||||
|
# and we thus have to work a bit to allow this.
|
||||||
|
fm = function.session._fixturemanager
|
||||||
|
fi = fm.getfixtureinfo(function.parent, function.obj, None)
|
||||||
function._fixtureinfo = fi
|
function._fixtureinfo = fi
|
||||||
request = function._request = FixtureRequest(function)
|
request = function._request = FixtureRequest(function)
|
||||||
request._fillfixtures()
|
request._fillfixtures()
|
||||||
|
# prune out funcargs for jstests
|
||||||
|
newfuncargs = {}
|
||||||
|
for name in fi.argnames:
|
||||||
|
newfuncargs[name] = function.funcargs[name]
|
||||||
|
function.funcargs = newfuncargs
|
||||||
|
else:
|
||||||
|
request._fillfixtures()
|
||||||
|
|
||||||
|
|
||||||
_notexists = object()
|
_notexists = object()
|
||||||
|
|
||||||
|
@ -953,8 +893,9 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
|
||||||
for name, val in keywords.items():
|
for name, val in keywords.items():
|
||||||
setattr(self.markers, name, val)
|
setattr(self.markers, name, val)
|
||||||
|
|
||||||
fi = self.parent._fixturemapper.getfixtureinfo(self.obj, self.cls)
|
fm = self.session._fixturemanager
|
||||||
self._fixtureinfo = fi
|
self._fixtureinfo = fi = fm.getfixtureinfo(self.parent,
|
||||||
|
self.obj, self.cls)
|
||||||
self.fixturenames = fi.names_closure
|
self.fixturenames = fi.names_closure
|
||||||
if self._isyieldedfunction():
|
if self._isyieldedfunction():
|
||||||
assert not callspec, (
|
assert not callspec, (
|
||||||
|
@ -1395,6 +1336,37 @@ class FixtureLookupErrorRepr(TerminalRepr):
|
||||||
tw.line("%s:%d" % (self.filename, self.firstlineno+1))
|
tw.line("%s:%d" % (self.filename, self.firstlineno+1))
|
||||||
|
|
||||||
class FixtureManager:
|
class FixtureManager:
|
||||||
|
"""
|
||||||
|
pytest fixtures definitions and information is stored and managed
|
||||||
|
from this class.
|
||||||
|
|
||||||
|
During collection fm.parsefactories() is called multiple times to parse
|
||||||
|
fixture function definitions into FixtureDef objects and internal
|
||||||
|
data structures.
|
||||||
|
|
||||||
|
During collection of test functions, metafunc-mechanics instantiate
|
||||||
|
a FuncFixtureInfo object which is cached per node/func-name.
|
||||||
|
This FuncFixtureInfo object is later retrieved by Function nodes
|
||||||
|
which themselves offer a fixturenames attribute.
|
||||||
|
|
||||||
|
The FuncFixtureInfo object holds information about fixtures and FixtureDefs
|
||||||
|
relevant for a particular function. An initial list of fixtures is
|
||||||
|
assembled like this:
|
||||||
|
|
||||||
|
- ini-defined usefixtures
|
||||||
|
- autouse-marked fixtures along the collection chain up from the function
|
||||||
|
- usefixtures markers at module/class/function level
|
||||||
|
- test function funcargs
|
||||||
|
|
||||||
|
Subsequently the funcfixtureinfo.fixturenames attribute is computed
|
||||||
|
as the closure of the fixtures needed to setup the initial fixtures,
|
||||||
|
i. e. fixtures needed by fixture functions themselves are appended
|
||||||
|
to the fixturenames list.
|
||||||
|
|
||||||
|
Upon the test-setup phases all fixturenames are instantiated, retrieved
|
||||||
|
by a lookup of their FuncFixtureInfo.
|
||||||
|
"""
|
||||||
|
|
||||||
_argprefix = "pytest_funcarg__"
|
_argprefix = "pytest_funcarg__"
|
||||||
FixtureLookupError = FixtureLookupError
|
FixtureLookupError = FixtureLookupError
|
||||||
FixtureLookupErrorRepr = FixtureLookupErrorRepr
|
FixtureLookupErrorRepr = FixtureLookupErrorRepr
|
||||||
|
@ -1409,6 +1381,34 @@ class FixtureManager:
|
||||||
self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
|
self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
|
||||||
session.config.pluginmanager.register(self, "funcmanage")
|
session.config.pluginmanager.register(self, "funcmanage")
|
||||||
|
|
||||||
|
self._nodename2fixtureinfo = {}
|
||||||
|
|
||||||
|
def getfixtureinfo(self, node, func, cls):
|
||||||
|
key = (node, func.__name__)
|
||||||
|
try:
|
||||||
|
return self._nodename2fixtureinfo[key]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
if not hasattr(node, "nofuncargs"):
|
||||||
|
if cls is not None:
|
||||||
|
startindex = 1
|
||||||
|
else:
|
||||||
|
startindex = None
|
||||||
|
argnames = getfuncargnames(func, startindex)
|
||||||
|
else:
|
||||||
|
argnames = ()
|
||||||
|
usefixtures = getattr(func, "usefixtures", None)
|
||||||
|
initialnames = argnames
|
||||||
|
if usefixtures is not None:
|
||||||
|
initialnames = usefixtures.args + initialnames
|
||||||
|
fm = node.session._fixturemanager
|
||||||
|
names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames,
|
||||||
|
node)
|
||||||
|
fixtureinfo = FuncFixtureInfo(argnames, names_closure,
|
||||||
|
arg2fixturedefs)
|
||||||
|
self._nodename2fixtureinfo[key] = fixtureinfo
|
||||||
|
return fixtureinfo
|
||||||
|
|
||||||
### XXX this hook should be called for historic events like pytest_configure
|
### XXX this hook should be called for historic events like pytest_configure
|
||||||
### so that we don't have to do the below pytest_configure hook
|
### so that we don't have to do the below pytest_configure hook
|
||||||
def pytest_plugin_registered(self, plugin):
|
def pytest_plugin_registered(self, plugin):
|
||||||
|
|
|
@ -20,6 +20,8 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
return UnitTestCase(name, parent=collector)
|
return UnitTestCase(name, parent=collector)
|
||||||
|
|
||||||
class UnitTestCase(pytest.Class):
|
class UnitTestCase(pytest.Class):
|
||||||
|
nofuncargs = True # marker for fixturemanger.getfixtureinfo()
|
||||||
|
# to declare that our children do not support funcargs
|
||||||
|
|
||||||
def collect(self):
|
def collect(self):
|
||||||
self.session._fixturemanager.parsefactories(self, unittest=True)
|
self.session._fixturemanager.parsefactories(self, unittest=True)
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -24,7 +24,7 @@ def main():
|
||||||
name='pytest',
|
name='pytest',
|
||||||
description='py.test: simple powerful testing with Python',
|
description='py.test: simple powerful testing with Python',
|
||||||
long_description = long_description,
|
long_description = long_description,
|
||||||
version='2.3.0.dev24',
|
version='2.3.0.dev25',
|
||||||
url='http://pytest.org',
|
url='http://pytest.org',
|
||||||
license='MIT license',
|
license='MIT license',
|
||||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||||
|
|
|
@ -280,7 +280,6 @@ class TestFunction:
|
||||||
config = testdir.parseconfigure()
|
config = testdir.parseconfigure()
|
||||||
session = testdir.Session(config)
|
session = testdir.Session(config)
|
||||||
session._fixturemanager = FixtureManager(session)
|
session._fixturemanager = FixtureManager(session)
|
||||||
session._fixturemapper = funcargs.FixtureMapper(session, funcargs=False)
|
|
||||||
def func1():
|
def func1():
|
||||||
pass
|
pass
|
||||||
def func2():
|
def func2():
|
||||||
|
@ -1440,29 +1439,58 @@ def test_conftest_funcargs_only_available_in_subdir(testdir):
|
||||||
"*2 passed*"
|
"*2 passed*"
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_funcarg_non_pycollectobj(testdir): # rough jstests usage
|
class TestOEJSKITSpecials:
|
||||||
testdir.makeconftest("""
|
def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage
|
||||||
import pytest
|
testdir.makeconftest("""
|
||||||
def pytest_pycollect_makeitem(collector, name, obj):
|
import pytest
|
||||||
if name == "MyClass":
|
def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
return MyCollector(name, parent=collector)
|
if name == "MyClass":
|
||||||
class MyCollector(pytest.Collector):
|
return MyCollector(name, parent=collector)
|
||||||
def reportinfo(self):
|
class MyCollector(pytest.Collector):
|
||||||
return self.fspath, 3, "xyz"
|
def reportinfo(self):
|
||||||
""")
|
return self.fspath, 3, "xyz"
|
||||||
modcol = testdir.getmodulecol("""
|
""")
|
||||||
def pytest_funcarg__arg1(request):
|
modcol = testdir.getmodulecol("""
|
||||||
return 42
|
def pytest_funcarg__arg1(request):
|
||||||
class MyClass:
|
return 42
|
||||||
pass
|
class MyClass:
|
||||||
""")
|
pass
|
||||||
# this hook finds funcarg factories
|
""")
|
||||||
rep = modcol.ihook.pytest_make_collect_report(collector=modcol)
|
# this hook finds funcarg factories
|
||||||
clscol = rep.result[0]
|
rep = modcol.ihook.pytest_make_collect_report(collector=modcol)
|
||||||
clscol.obj = lambda arg1: None
|
clscol = rep.result[0]
|
||||||
clscol.funcargs = {}
|
clscol.obj = lambda arg1: None
|
||||||
funcargs.fillfixtures(clscol)
|
clscol.funcargs = {}
|
||||||
assert clscol.funcargs['arg1'] == 42
|
funcargs.fillfixtures(clscol)
|
||||||
|
assert clscol.funcargs['arg1'] == 42
|
||||||
|
|
||||||
|
def test_autouse_fixture(self, testdir): # rough jstests usage
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import pytest
|
||||||
|
def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
|
if name == "MyClass":
|
||||||
|
return MyCollector(name, parent=collector)
|
||||||
|
class MyCollector(pytest.Collector):
|
||||||
|
def reportinfo(self):
|
||||||
|
return self.fspath, 3, "xyz"
|
||||||
|
""")
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def hello():
|
||||||
|
pass
|
||||||
|
def pytest_funcarg__arg1(request):
|
||||||
|
return 42
|
||||||
|
class MyClass:
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
# this hook finds funcarg factories
|
||||||
|
rep = modcol.ihook.pytest_make_collect_report(collector=modcol)
|
||||||
|
clscol = rep.result[0]
|
||||||
|
clscol.obj = lambda: None
|
||||||
|
clscol.funcargs = {}
|
||||||
|
funcargs.fillfixtures(clscol)
|
||||||
|
assert not clscol.funcargs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue