diff --git a/contrib/pytest_twisted/__init__.py b/contrib/pytest_twisted/__init__.py index 81116bc6f..40a6783b7 100644 --- a/contrib/pytest_twisted/__init__.py +++ b/contrib/pytest_twisted/__init__.py @@ -91,12 +91,11 @@ def pytest_configure(config): def pytest_unconfigure(config): gr_twisted.switch(None) -def pytest_pyfunc_call(pyfuncitem, *args, **kwargs): - args = args or pyfuncitem._args # generator tests +def pytest_pyfunc_call(pyfuncitem): # XXX1 kwargs? # XXX2 we want to delegate actual call to next plugin # (which may want to produce test coverage, etc.) - res = gr_twisted.switch(lambda: pyfuncitem.obj(*args)) + res = gr_twisted.switch(lambda: pyfuncitem.call()) if res: res.raiseException() return True # indicates that we performed the function call diff --git a/doc/test/extend.txt b/doc/test/extend.txt index a5c21aa62..830b7ae4b 100644 --- a/doc/test/extend.txt +++ b/doc/test/extend.txt @@ -46,8 +46,8 @@ for early mismatch reporting and minimizes version incompatibilites. .. _`original definition of the hook`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/hookspec.py -"runtest" hooks -------------------- +generic "runtest" hooks +------------------------------ Each test item is usually executed by calling the following three hooks:: @@ -101,6 +101,26 @@ The call object contains information about a performed call:: .. _`pytest_terminal plugin`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/pytest_terminal.py +generic collection hooks +------------------------------ + +XXX + +Python module and test function hooks +------------------------------------------- + +For influencing the collection of objects in Python modules +you can use the following hook: + + pytest_pycollect_makeitem(collector, name, obj) + +This hook will be called for each Python object in a collected +Python module. The return value is a custom `collection node`_. + +.. XXX or ``False`` if you want to indicate that the given item should not be collected. + + + Included default plugins ============================= @@ -114,6 +134,8 @@ Additionally you can check out some more contributed plugins here .. _`collection process`: +.. _`collection node`: + Test Collection process ====================================================== diff --git a/py/test/collect.py b/py/test/collect.py index b6af621e8..a8e35a8f4 100644 --- a/py/test/collect.py +++ b/py/test/collect.py @@ -425,11 +425,11 @@ class Item(Node): def run(self): """ deprecated, here because subclasses might call it. """ - return self.execute(self.obj, *self._args) + return self.execute(self.obj) - def execute(self, obj, *args): + def execute(self, obj): """ deprecated, here because subclasses might call it. """ - return obj(*args) + return obj() def reportinfo(self): return self.fspath, None, "" diff --git a/py/test/plugin/hookspec.py b/py/test/plugin/hookspec.py index ee93e58e5..5b398dcda 100644 --- a/py/test/plugin/hookspec.py +++ b/py/test/plugin/hookspec.py @@ -34,9 +34,7 @@ def pytest_deselected(items): # ------------------------------------------------------------------------------ # collection hooks # ------------------------------------------------------------------------------ -def pytest_make_collect_report(collector): - """ perform a collection and return a collection. """ -pytest_make_collect_report.firstresult = True + def pytest_collect_file(path, parent): """ return Collection node or None. """ @@ -50,25 +48,37 @@ pytest_collect_recurse.firstresult = True def pytest_collect_directory(path, parent): """ return Collection node or None. """ -def pytest_pycollect_obj(collector, name, obj): - """ return custom item/collector for a python object in a module, or None. """ -pytest_pycollect_obj.firstresult = True - -def pytest_generate_tests(metafunc): - """ generate (multiple) parametrized calls to a test function.""" - def pytest_collectstart(collector): """ collector starts collecting. """ def pytest_collectreport(rep): """ collector finished collecting. """ +def pytest_make_collect_report(collector): + """ perform a collection and return a collection. """ +pytest_make_collect_report.firstresult = True + # XXX rename to item_collected()? meaning in distribution context? def pytest_itemstart(item, node=None): """ test item gets collected. """ # ------------------------------------------------------------------------------ -# runtest related hooks +# Python test function related hooks +# ------------------------------------------------------------------------------ + +def pytest_pycollect_makeitem(collector, name, obj): + """ return custom item/collector for a python object in a module, or None. """ +pytest_pycollect_makeitem.firstresult = True + +def pytest_pyfunc_call(pyfuncitem): + """ perform function call with the given function arguments. """ +pytest_pyfunc_call.firstresult = True + +def pytest_generate_tests(metafunc): + """ generate (multiple) parametrized calls to a test function.""" + +# ------------------------------------------------------------------------------ +# generic runtest related hooks # ------------------------------------------------------------------------------ def pytest_runtest_setup(item): """ called before pytest_runtest_call(). """ @@ -83,10 +93,6 @@ def pytest_runtest_protocol(item): """ run given test item and return test report. """ pytest_runtest_protocol.firstresult = True -def pytest_pyfunc_call(pyfuncitem, args, kwargs): - """ return True if we consumed/did the call to the python function item. """ -pytest_pyfunc_call.firstresult = True - def pytest_runtest_makereport(item, call): """ make ItemTestReport for the specified test outcome. """ pytest_runtest_makereport.firstresult = True @@ -95,7 +101,7 @@ def pytest_runtest_logreport(rep): """ process item test report. """ # ------------------------------------------------------------------------------ -# reporting hooks (invoked from pytest_terminal.py) +# generic reporting hooks (invoked from pytest_terminal.py) # ------------------------------------------------------------------------------ def pytest_report_teststatus(rep): """ return shortletter and verbose word. """ @@ -117,6 +123,7 @@ def pytest_doctest_prepare_content(content): """ return processed content for a given doctest""" pytest_doctest_prepare_content.firstresult = True + # ------------------------------------------------------------------------------ # misc hooks # ------------------------------------------------------------------------------ @@ -133,8 +140,6 @@ def pytest_internalerror(excrepr): def pytest_trace(category, msg): """ called for debug info. """ - - # ------------------------------------------------------------------------------ # distributed testing # ------------------------------------------------------------------------------ diff --git a/py/test/plugin/pytest_default.py b/py/test/plugin/pytest_default.py index 8be551096..d3b872c0a 100644 --- a/py/test/plugin/pytest_default.py +++ b/py/test/plugin/pytest_default.py @@ -2,9 +2,14 @@ import py -def pytest_pyfunc_call(__call__, pyfuncitem, args, kwargs): +def pytest_pyfunc_call(__call__, pyfuncitem): if not __call__.execute(firstresult=True): - pyfuncitem.obj(*args, **kwargs) + testfunction = pyfuncitem.obj + if pyfuncitem._isyieldedfunction(): + testfunction(*pyfuncitem._args) + else: + funcargs = pyfuncitem.funcargs + testfunction(**funcargs) def pytest_collect_file(path, parent): ext = path.ext diff --git a/py/test/plugin/pytest_execnetcleanup.py b/py/test/plugin/pytest_execnetcleanup.py index 10a1a37a9..1a6dbe6df 100644 --- a/py/test/plugin/pytest_execnetcleanup.py +++ b/py/test/plugin/pytest_execnetcleanup.py @@ -29,8 +29,8 @@ class Execnetcleanup: l.append(gw) #for gw in l: # gw.join() - # - def pytest_pyfunc_call(self, __call__, pyfuncitem, args, kwargs): + + def pytest_pyfunc_call(self, __call__, pyfuncitem): if self._gateways is not None: gateways = self._gateways[:] res = __call__.execute(firstresult=True) diff --git a/py/test/plugin/pytest_iocapture.py b/py/test/plugin/pytest_iocapture.py index c723bf092..c7c667d49 100644 --- a/py/test/plugin/pytest_iocapture.py +++ b/py/test/plugin/pytest_iocapture.py @@ -22,8 +22,8 @@ def pytest_funcarg__capfd(request): request.addfinalizer(capture.finalize) return capture -def pytest_pyfunc_call(pyfuncitem, args, kwargs): - for funcarg, value in kwargs.items(): +def pytest_pyfunc_call(pyfuncitem): + for funcarg, value in pyfuncitem.funcargs.items(): if funcarg == "capsys" or funcarg == "capfd": value.reset() diff --git a/py/test/plugin/pytest_restdoc.py b/py/test/plugin/pytest_restdoc.py index 03cf8c39b..9b0e91b55 100644 --- a/py/test/plugin/pytest_restdoc.py +++ b/py/test/plugin/pytest_restdoc.py @@ -271,7 +271,7 @@ class LinkCheckerMaker(py.test.collect.Collector): if tryfn.startswith('http:') or tryfn.startswith('https'): if self.config.getvalue("urlcheck"): yield CheckLink(name, parent=self, - args=(tryfn, path, lineno, timeout), callobj=urlcheck) + args=(tryfn, path, lineno, timeout), checkfunc=urlcheck) elif tryfn.startswith('webcal:'): continue else: @@ -282,16 +282,19 @@ class LinkCheckerMaker(py.test.collect.Collector): checkfn = tryfn if checkfn.strip() and (1 or checkfn.endswith('.html')): yield CheckLink(name, parent=self, - args=(tryfn, path, lineno), callobj=localrefcheck) + args=(tryfn, path, lineno), checkfunc=localrefcheck) -class CheckLink(py.test.collect.Function): - def reportinfo(self, basedir=None): - return (self.fspath, self._args[2], "checklink: %s" % self._args[0]) +class CheckLink(py.test.collect.Item): + def __init__(self, name, parent, args, checkfunc): + super(CheckLink, self).__init__(name, parent) + self.args = args + self.checkfunc = checkfunc - def setup(self): - pass - def teardown(self): - pass + def runtest(self): + return self.checkfunc(*self.args) + + def reportinfo(self, basedir=None): + return (self.fspath, self.args[2], "checklink: %s" % self.args[0]) def urlcheck(tryfn, path, lineno, TIMEOUT_URLOPEN): old = py.std.socket.getdefaulttimeout() diff --git a/py/test/plugin/pytest_unittest.py b/py/test/plugin/pytest_unittest.py index ad024ecdc..a9916f24f 100644 --- a/py/test/plugin/pytest_unittest.py +++ b/py/test/plugin/pytest_unittest.py @@ -13,7 +13,7 @@ this code is somewhat derived from Guido Wesdorps """ import py -def pytest_pycollect_obj(collector, name, obj): +def pytest_pycollect_makeitem(collector, name, obj): if py.std.inspect.isclass(obj) and issubclass(obj, py.std.unittest.TestCase): return UnitTestCase(name, parent=collector) diff --git a/py/test/pycollect.py b/py/test/pycollect.py index 67eebc67e..eb010f66a 100644 --- a/py/test/pycollect.py +++ b/py/test/pycollect.py @@ -119,9 +119,9 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector): return self.join(name) def makeitem(self, name, obj): - res = self.config.hook.pytest_pycollect_obj( + res = self.config.hook.pytest_pycollect_makeitem( collector=self, name=name, obj=obj) - if res: + if res is not None: return res if (self.classnamefilter(name)) and \ py.std.inspect.isclass(obj): @@ -314,11 +314,11 @@ class Function(FunctionMixin, py.test.collect.Item): and executing a Python callable test object. """ _genid = None - def __init__(self, name, parent=None, args=(), + def __init__(self, name, parent=None, args=None, callspec=None, callobj=_dummy): super(Function, self).__init__(name, parent) self._args = args - if args: + if self._isyieldedfunction(): assert not callspec, "yielded functions (deprecated) cannot have funcargs" else: if callspec is not None: @@ -331,6 +331,9 @@ class Function(FunctionMixin, py.test.collect.Item): if callobj is not _dummy: self._obj = callobj + def _isyieldedfunction(self): + return self._args is not None + def readkeywords(self): d = super(Function, self).readkeywords() d.update(self.obj.func_dict) @@ -338,9 +341,7 @@ class Function(FunctionMixin, py.test.collect.Item): def runtest(self): """ execute the underlying test function. """ - kwargs = getattr(self, 'funcargs', {}) - self.config.hook.pytest_pyfunc_call( - pyfuncitem=self, args=self._args, kwargs=kwargs) + self.config.hook.pytest_pyfunc_call(pyfuncitem=self) def setup(self): super(Function, self).setup() diff --git a/py/test/testing/test_pycollect.py b/py/test/testing/test_pycollect.py index c52a3cf31..2a6c44463 100644 --- a/py/test/testing/test_pycollect.py +++ b/py/test/testing/test_pycollect.py @@ -264,10 +264,10 @@ class TestFunction: item = testdir.getitem("def test_func(): raise ValueError") config = item.config class MyPlugin1: - def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs): + def pytest_pyfunc_call(self, pyfuncitem): raise ValueError class MyPlugin2: - def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs): + def pytest_pyfunc_call(self, pyfuncitem): return True config.pluginmanager.register(MyPlugin1()) config.pluginmanager.register(MyPlugin2())