generalize hook calling from collection nodes but stop short
of allowing general hooks in python test modules. It'd be easily possible (a 1-line change) but considering it i refrained from it because the collector API is a bit too low level. pytest_generate_tests and funcarg factories have a limited directly useful interface and are thus less confusing - those are taking advantage of hook discovery in python test modules. --HG-- branch : trunk
This commit is contained in:
parent
631dfe9f13
commit
ae63605ac0
|
@ -9,6 +9,8 @@ Changes between 1.X and 1.1.1
|
||||||
|
|
||||||
- new "pytestconfig" funcarg allows access to test config object
|
- new "pytestconfig" funcarg allows access to test config object
|
||||||
|
|
||||||
|
- allow pytest_generate_tests to be defined in classes as well
|
||||||
|
|
||||||
- collection/item node specific runtest/collect hooks are only called exactly
|
- collection/item node specific runtest/collect hooks are only called exactly
|
||||||
on matching conftest.py files, i.e. ones which are exactly below
|
on matching conftest.py files, i.e. ones which are exactly below
|
||||||
the filesystem path of an item
|
the filesystem path of an item
|
||||||
|
|
|
@ -19,7 +19,7 @@ class HookProxy:
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
hookmethod = getattr(self.node.config.hook, name)
|
hookmethod = getattr(self.node.config.hook, name)
|
||||||
def call_matching_hooks(**kwargs):
|
def call_matching_hooks(**kwargs):
|
||||||
plugins = self.node.config.getmatchingplugins(self.node.fspath)
|
plugins = self.node._getplugins()
|
||||||
return hookmethod.pcall(plugins, **kwargs)
|
return hookmethod.pcall(plugins, **kwargs)
|
||||||
return call_matching_hooks
|
return call_matching_hooks
|
||||||
|
|
||||||
|
@ -43,6 +43,9 @@ class Node(object):
|
||||||
self.fspath = getattr(parent, 'fspath', None)
|
self.fspath = getattr(parent, 'fspath', None)
|
||||||
self.ihook = HookProxy(self)
|
self.ihook = HookProxy(self)
|
||||||
|
|
||||||
|
def _getplugins(self):
|
||||||
|
return self.config._getmatchingplugins(self.fspath)
|
||||||
|
|
||||||
def _checkcollectable(self):
|
def _checkcollectable(self):
|
||||||
if not hasattr(self, 'fspath'):
|
if not hasattr(self, 'fspath'):
|
||||||
self.parent._memocollect() # to reraise exception
|
self.parent._memocollect() # to reraise exception
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Config(object):
|
||||||
self.trace("loaded conftestmodule %r" %(conftestmodule,))
|
self.trace("loaded conftestmodule %r" %(conftestmodule,))
|
||||||
self.pluginmanager.consider_conftest(conftestmodule)
|
self.pluginmanager.consider_conftest(conftestmodule)
|
||||||
|
|
||||||
def getmatchingplugins(self, fspath):
|
def _getmatchingplugins(self, fspath):
|
||||||
conftests = self._conftest._conftestpath2mod.values()
|
conftests = self._conftest._conftestpath2mod.values()
|
||||||
plugins = [x for x in self.pluginmanager.getplugins()
|
plugins = [x for x in self.pluginmanager.getplugins()
|
||||||
if x not in conftests]
|
if x not in conftests]
|
||||||
|
|
|
@ -93,10 +93,7 @@ class FuncargRequest:
|
||||||
self.fspath = pyfuncitem.fspath
|
self.fspath = pyfuncitem.fspath
|
||||||
if hasattr(pyfuncitem, '_requestparam'):
|
if hasattr(pyfuncitem, '_requestparam'):
|
||||||
self.param = pyfuncitem._requestparam
|
self.param = pyfuncitem._requestparam
|
||||||
self._plugins = self.config.getmatchingplugins(self.fspath)
|
self._plugins = pyfuncitem._getplugins(withpy=True)
|
||||||
self._plugins.append(self.module)
|
|
||||||
if self.instance is not None:
|
|
||||||
self._plugins.append(self.instance)
|
|
||||||
self._funcargs = self._pyfuncitem.funcargs.copy()
|
self._funcargs = self._pyfuncitem.funcargs.copy()
|
||||||
self._name2factory = {}
|
self._name2factory = {}
|
||||||
self._currentarg = None
|
self._currentarg = None
|
||||||
|
|
|
@ -34,6 +34,15 @@ class PyobjMixin(object):
|
||||||
return property(fget, fset, None, "underlying python object")
|
return property(fget, fset, None, "underlying python object")
|
||||||
obj = obj()
|
obj = obj()
|
||||||
|
|
||||||
|
def _getplugins(self, withpy=False):
|
||||||
|
plugins = self.config._getmatchingplugins(self.fspath)
|
||||||
|
if withpy:
|
||||||
|
plugins.append(self.getparent(py.test.collect.Module).obj)
|
||||||
|
inst = self.getparent(py.test.collect.Instance)
|
||||||
|
if inst is not None:
|
||||||
|
plugins.append(inst.obj)
|
||||||
|
return plugins
|
||||||
|
|
||||||
def _getobj(self):
|
def _getobj(self):
|
||||||
return getattr(self.parent.obj, self.name)
|
return getattr(self.parent.obj, self.name)
|
||||||
|
|
||||||
|
@ -138,8 +147,7 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
|
||||||
metafunc = funcargs.Metafunc(funcobj, config=self.config,
|
metafunc = funcargs.Metafunc(funcobj, config=self.config,
|
||||||
cls=cls, module=module)
|
cls=cls, module=module)
|
||||||
gentesthook = self.config.hook.pytest_generate_tests
|
gentesthook = self.config.hook.pytest_generate_tests
|
||||||
plugins = self.config.getmatchingplugins(self.fspath) + [module]
|
gentesthook.pcall(self._getplugins(withpy=True), metafunc=metafunc)
|
||||||
gentesthook.pcall(plugins, metafunc=metafunc)
|
|
||||||
if not metafunc._calls:
|
if not metafunc._calls:
|
||||||
return self.Function(name, parent=self)
|
return self.Function(name, parent=self)
|
||||||
return funcargs.FunctionCollector(name=name,
|
return funcargs.FunctionCollector(name=name,
|
||||||
|
@ -327,6 +335,7 @@ class Function(FunctionMixin, py.test.collect.Item):
|
||||||
self.funcargs = {}
|
self.funcargs = {}
|
||||||
if callobj is not _dummy:
|
if callobj is not _dummy:
|
||||||
self._obj = callobj
|
self._obj = callobj
|
||||||
|
self.function = getattr(self.obj, 'im_func', self.obj)
|
||||||
|
|
||||||
def _isyieldedfunction(self):
|
def _isyieldedfunction(self):
|
||||||
return self._args is not None
|
return self._args is not None
|
||||||
|
|
|
@ -285,3 +285,34 @@ def test_callinfo():
|
||||||
assert not hasattr(ci, 'result')
|
assert not hasattr(ci, 'result')
|
||||||
assert ci.excinfo
|
assert ci.excinfo
|
||||||
assert "exc" in repr(ci)
|
assert "exc" in repr(ci)
|
||||||
|
|
||||||
|
# design question: do we want general hooks in python files?
|
||||||
|
# following passes if withpy defaults to True in pycoll.PyObjMix._getplugins()
|
||||||
|
@py.test.mark.xfail
|
||||||
|
def test_runtest_in_module_ordering(testdir):
|
||||||
|
p1 = testdir.makepyfile("""
|
||||||
|
def pytest_runtest_setup(item): # runs after class-level!
|
||||||
|
item.function.mylist.append("module")
|
||||||
|
class TestClass:
|
||||||
|
def pytest_runtest_setup(self, item):
|
||||||
|
assert not hasattr(item.function, 'mylist')
|
||||||
|
item.function.mylist = ['class']
|
||||||
|
def pytest_funcarg__mylist(self, request):
|
||||||
|
return request.function.mylist
|
||||||
|
def pytest_runtest_call(self, item, __multicall__):
|
||||||
|
try:
|
||||||
|
__multicall__.execute()
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
def test_hello1(self, mylist):
|
||||||
|
assert mylist == ['class', 'module'], mylist
|
||||||
|
raise ValueError()
|
||||||
|
def test_hello2(self, mylist):
|
||||||
|
assert mylist == ['class', 'module'], mylist
|
||||||
|
def pytest_runtest_teardown(item):
|
||||||
|
del item.function.mylist
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p1)
|
||||||
|
assert result.stdout.fnmatch_lines([
|
||||||
|
"*2 passed*"
|
||||||
|
])
|
||||||
|
|
|
@ -483,6 +483,21 @@ class TestGenfuncFunctional:
|
||||||
"*1 failed, 1 passed*"
|
"*1 failed, 1 passed*"
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_generate_tests_in_class(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
class TestClass:
|
||||||
|
def pytest_generate_tests(self, metafunc):
|
||||||
|
metafunc.addcall(funcargs={'hello': 'world'}, id="hello")
|
||||||
|
|
||||||
|
def test_myfunc(self, hello):
|
||||||
|
assert hello == "world"
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest("-v", p)
|
||||||
|
assert result.stdout.fnmatch_lines([
|
||||||
|
"*test_myfunc*hello*PASS*",
|
||||||
|
"*1 passed*"
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
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")
|
||||||
|
|
Loading…
Reference in New Issue