From ae63605ac0afa9bfddaf27c7a69b3545b4036e39 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 30 Dec 2009 10:42:01 +0100 Subject: [PATCH] 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 --- CHANGELOG | 2 ++ py/impl/test/collect.py | 5 ++++- py/impl/test/config.py | 2 +- py/impl/test/funcargs.py | 5 +---- py/impl/test/pycollect.py | 13 ++++++++++-- testing/plugin/test_pytest_runner.py | 31 ++++++++++++++++++++++++++++ testing/pytest/test_funcargs.py | 15 ++++++++++++++ 7 files changed, 65 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e577f912d..3c4f95966 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,8 @@ Changes between 1.X and 1.1.1 - 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 on matching conftest.py files, i.e. ones which are exactly below the filesystem path of an item diff --git a/py/impl/test/collect.py b/py/impl/test/collect.py index c47eb3890..84a7b768a 100644 --- a/py/impl/test/collect.py +++ b/py/impl/test/collect.py @@ -19,7 +19,7 @@ class HookProxy: raise AttributeError(name) hookmethod = getattr(self.node.config.hook, name) def call_matching_hooks(**kwargs): - plugins = self.node.config.getmatchingplugins(self.node.fspath) + plugins = self.node._getplugins() return hookmethod.pcall(plugins, **kwargs) return call_matching_hooks @@ -43,6 +43,9 @@ class Node(object): self.fspath = getattr(parent, 'fspath', None) self.ihook = HookProxy(self) + def _getplugins(self): + return self.config._getmatchingplugins(self.fspath) + def _checkcollectable(self): if not hasattr(self, 'fspath'): self.parent._memocollect() # to reraise exception diff --git a/py/impl/test/config.py b/py/impl/test/config.py index 9b174d13f..6a3937e72 100644 --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -45,7 +45,7 @@ class Config(object): self.trace("loaded conftestmodule %r" %(conftestmodule,)) self.pluginmanager.consider_conftest(conftestmodule) - def getmatchingplugins(self, fspath): + def _getmatchingplugins(self, fspath): conftests = self._conftest._conftestpath2mod.values() plugins = [x for x in self.pluginmanager.getplugins() if x not in conftests] diff --git a/py/impl/test/funcargs.py b/py/impl/test/funcargs.py index 87ab44c1c..520615b82 100644 --- a/py/impl/test/funcargs.py +++ b/py/impl/test/funcargs.py @@ -93,10 +93,7 @@ class FuncargRequest: self.fspath = pyfuncitem.fspath if hasattr(pyfuncitem, '_requestparam'): self.param = pyfuncitem._requestparam - self._plugins = self.config.getmatchingplugins(self.fspath) - self._plugins.append(self.module) - if self.instance is not None: - self._plugins.append(self.instance) + self._plugins = pyfuncitem._getplugins(withpy=True) self._funcargs = self._pyfuncitem.funcargs.copy() self._name2factory = {} self._currentarg = None diff --git a/py/impl/test/pycollect.py b/py/impl/test/pycollect.py index 420c9c3c9..48f99b6e5 100644 --- a/py/impl/test/pycollect.py +++ b/py/impl/test/pycollect.py @@ -34,6 +34,15 @@ class PyobjMixin(object): return property(fget, fset, None, "underlying python object") 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): 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, cls=cls, module=module) gentesthook = self.config.hook.pytest_generate_tests - plugins = self.config.getmatchingplugins(self.fspath) + [module] - gentesthook.pcall(plugins, metafunc=metafunc) + gentesthook.pcall(self._getplugins(withpy=True), metafunc=metafunc) if not metafunc._calls: return self.Function(name, parent=self) return funcargs.FunctionCollector(name=name, @@ -327,6 +335,7 @@ class Function(FunctionMixin, py.test.collect.Item): self.funcargs = {} if callobj is not _dummy: self._obj = callobj + self.function = getattr(self.obj, 'im_func', self.obj) def _isyieldedfunction(self): return self._args is not None diff --git a/testing/plugin/test_pytest_runner.py b/testing/plugin/test_pytest_runner.py index 114553f5e..9099c345a 100644 --- a/testing/plugin/test_pytest_runner.py +++ b/testing/plugin/test_pytest_runner.py @@ -285,3 +285,34 @@ def test_callinfo(): assert not hasattr(ci, 'result') assert ci.excinfo 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*" + ]) diff --git a/testing/pytest/test_funcargs.py b/testing/pytest/test_funcargs.py index 420dd5827..a2807166e 100644 --- a/testing/pytest/test_funcargs.py +++ b/testing/pytest/test_funcargs.py @@ -483,6 +483,21 @@ class TestGenfuncFunctional: "*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): sub1 = testdir.mkpydir("sub1")