* refine and rename pycollect related hooks

* refine runtest/test function call protocol

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-06-15 15:15:40 +02:00
parent a59d602bce
commit 771438fde5
11 changed files with 86 additions and 51 deletions

View File

@ -91,12 +91,11 @@ def pytest_configure(config):
def pytest_unconfigure(config): def pytest_unconfigure(config):
gr_twisted.switch(None) gr_twisted.switch(None)
def pytest_pyfunc_call(pyfuncitem, *args, **kwargs): def pytest_pyfunc_call(pyfuncitem):
args = args or pyfuncitem._args # generator tests
# XXX1 kwargs? # XXX1 kwargs?
# XXX2 we want to delegate actual call to next plugin # XXX2 we want to delegate actual call to next plugin
# (which may want to produce test coverage, etc.) # (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: if res:
res.raiseException() res.raiseException()
return True # indicates that we performed the function call return True # indicates that we performed the function call

View File

@ -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 .. _`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:: 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 .. _`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 Included default plugins
============================= =============================
@ -114,6 +134,8 @@ Additionally you can check out some more contributed plugins here
.. _`collection process`: .. _`collection process`:
.. _`collection node`:
Test Collection process Test Collection process
====================================================== ======================================================

View File

@ -425,11 +425,11 @@ class Item(Node):
def run(self): def run(self):
""" deprecated, here because subclasses might call it. """ """ 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. """ """ deprecated, here because subclasses might call it. """
return obj(*args) return obj()
def reportinfo(self): def reportinfo(self):
return self.fspath, None, "" return self.fspath, None, ""

View File

@ -34,9 +34,7 @@ def pytest_deselected(items):
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# collection hooks # 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): def pytest_collect_file(path, parent):
""" return Collection node or None. """ """ return Collection node or None. """
@ -50,25 +48,37 @@ pytest_collect_recurse.firstresult = True
def pytest_collect_directory(path, parent): def pytest_collect_directory(path, parent):
""" return Collection node or None. """ """ 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): def pytest_collectstart(collector):
""" collector starts collecting. """ """ collector starts collecting. """
def pytest_collectreport(rep): def pytest_collectreport(rep):
""" collector finished collecting. """ """ 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? # XXX rename to item_collected()? meaning in distribution context?
def pytest_itemstart(item, node=None): def pytest_itemstart(item, node=None):
""" test item gets collected. """ """ 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): def pytest_runtest_setup(item):
""" called before pytest_runtest_call(). """ """ called before pytest_runtest_call(). """
@ -83,10 +93,6 @@ def pytest_runtest_protocol(item):
""" run given test item and return test report. """ """ run given test item and return test report. """
pytest_runtest_protocol.firstresult = True 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): def pytest_runtest_makereport(item, call):
""" make ItemTestReport for the specified test outcome. """ """ make ItemTestReport for the specified test outcome. """
pytest_runtest_makereport.firstresult = True pytest_runtest_makereport.firstresult = True
@ -95,7 +101,7 @@ def pytest_runtest_logreport(rep):
""" process item test report. """ """ process item test report. """
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# reporting hooks (invoked from pytest_terminal.py) # generic reporting hooks (invoked from pytest_terminal.py)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
def pytest_report_teststatus(rep): def pytest_report_teststatus(rep):
""" return shortletter and verbose word. """ """ return shortletter and verbose word. """
@ -117,6 +123,7 @@ def pytest_doctest_prepare_content(content):
""" return processed content for a given doctest""" """ return processed content for a given doctest"""
pytest_doctest_prepare_content.firstresult = True pytest_doctest_prepare_content.firstresult = True
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# misc hooks # misc hooks
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -133,8 +140,6 @@ def pytest_internalerror(excrepr):
def pytest_trace(category, msg): def pytest_trace(category, msg):
""" called for debug info. """ """ called for debug info. """
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# distributed testing # distributed testing
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -2,9 +2,14 @@
import py import py
def pytest_pyfunc_call(__call__, pyfuncitem, args, kwargs): def pytest_pyfunc_call(__call__, pyfuncitem):
if not __call__.execute(firstresult=True): 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): def pytest_collect_file(path, parent):
ext = path.ext ext = path.ext

View File

@ -29,8 +29,8 @@ class Execnetcleanup:
l.append(gw) l.append(gw)
#for gw in l: #for gw in l:
# gw.join() # gw.join()
#
def pytest_pyfunc_call(self, __call__, pyfuncitem, args, kwargs): def pytest_pyfunc_call(self, __call__, pyfuncitem):
if self._gateways is not None: if self._gateways is not None:
gateways = self._gateways[:] gateways = self._gateways[:]
res = __call__.execute(firstresult=True) res = __call__.execute(firstresult=True)

View File

@ -22,8 +22,8 @@ def pytest_funcarg__capfd(request):
request.addfinalizer(capture.finalize) request.addfinalizer(capture.finalize)
return capture return capture
def pytest_pyfunc_call(pyfuncitem, args, kwargs): def pytest_pyfunc_call(pyfuncitem):
for funcarg, value in kwargs.items(): for funcarg, value in pyfuncitem.funcargs.items():
if funcarg == "capsys" or funcarg == "capfd": if funcarg == "capsys" or funcarg == "capfd":
value.reset() value.reset()

View File

@ -271,7 +271,7 @@ class LinkCheckerMaker(py.test.collect.Collector):
if tryfn.startswith('http:') or tryfn.startswith('https'): if tryfn.startswith('http:') or tryfn.startswith('https'):
if self.config.getvalue("urlcheck"): if self.config.getvalue("urlcheck"):
yield CheckLink(name, parent=self, yield CheckLink(name, parent=self,
args=(tryfn, path, lineno, timeout), callobj=urlcheck) args=(tryfn, path, lineno, timeout), checkfunc=urlcheck)
elif tryfn.startswith('webcal:'): elif tryfn.startswith('webcal:'):
continue continue
else: else:
@ -282,16 +282,19 @@ class LinkCheckerMaker(py.test.collect.Collector):
checkfn = tryfn checkfn = tryfn
if checkfn.strip() and (1 or checkfn.endswith('.html')): if checkfn.strip() and (1 or checkfn.endswith('.html')):
yield CheckLink(name, parent=self, yield CheckLink(name, parent=self,
args=(tryfn, path, lineno), callobj=localrefcheck) args=(tryfn, path, lineno), checkfunc=localrefcheck)
class CheckLink(py.test.collect.Function): class CheckLink(py.test.collect.Item):
def reportinfo(self, basedir=None): def __init__(self, name, parent, args, checkfunc):
return (self.fspath, self._args[2], "checklink: %s" % self._args[0]) super(CheckLink, self).__init__(name, parent)
self.args = args
self.checkfunc = checkfunc
def setup(self): def runtest(self):
pass return self.checkfunc(*self.args)
def teardown(self):
pass def reportinfo(self, basedir=None):
return (self.fspath, self.args[2], "checklink: %s" % self.args[0])
def urlcheck(tryfn, path, lineno, TIMEOUT_URLOPEN): def urlcheck(tryfn, path, lineno, TIMEOUT_URLOPEN):
old = py.std.socket.getdefaulttimeout() old = py.std.socket.getdefaulttimeout()

View File

@ -13,7 +13,7 @@ this code is somewhat derived from Guido Wesdorps
""" """
import py 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): if py.std.inspect.isclass(obj) and issubclass(obj, py.std.unittest.TestCase):
return UnitTestCase(name, parent=collector) return UnitTestCase(name, parent=collector)

View File

@ -119,9 +119,9 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
return self.join(name) return self.join(name)
def makeitem(self, name, obj): 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) collector=self, name=name, obj=obj)
if res: if res is not None:
return res return res
if (self.classnamefilter(name)) and \ if (self.classnamefilter(name)) and \
py.std.inspect.isclass(obj): py.std.inspect.isclass(obj):
@ -314,11 +314,11 @@ class Function(FunctionMixin, py.test.collect.Item):
and executing a Python callable test object. and executing a Python callable test object.
""" """
_genid = None _genid = None
def __init__(self, name, parent=None, args=(), def __init__(self, name, parent=None, args=None,
callspec=None, callobj=_dummy): callspec=None, callobj=_dummy):
super(Function, self).__init__(name, parent) super(Function, self).__init__(name, parent)
self._args = args self._args = args
if args: if self._isyieldedfunction():
assert not callspec, "yielded functions (deprecated) cannot have funcargs" assert not callspec, "yielded functions (deprecated) cannot have funcargs"
else: else:
if callspec is not None: if callspec is not None:
@ -331,6 +331,9 @@ class Function(FunctionMixin, py.test.collect.Item):
if callobj is not _dummy: if callobj is not _dummy:
self._obj = callobj self._obj = callobj
def _isyieldedfunction(self):
return self._args is not None
def readkeywords(self): def readkeywords(self):
d = super(Function, self).readkeywords() d = super(Function, self).readkeywords()
d.update(self.obj.func_dict) d.update(self.obj.func_dict)
@ -338,9 +341,7 @@ class Function(FunctionMixin, py.test.collect.Item):
def runtest(self): def runtest(self):
""" execute the underlying test function. """ """ execute the underlying test function. """
kwargs = getattr(self, 'funcargs', {}) self.config.hook.pytest_pyfunc_call(pyfuncitem=self)
self.config.hook.pytest_pyfunc_call(
pyfuncitem=self, args=self._args, kwargs=kwargs)
def setup(self): def setup(self):
super(Function, self).setup() super(Function, self).setup()

View File

@ -264,10 +264,10 @@ class TestFunction:
item = testdir.getitem("def test_func(): raise ValueError") item = testdir.getitem("def test_func(): raise ValueError")
config = item.config config = item.config
class MyPlugin1: class MyPlugin1:
def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs): def pytest_pyfunc_call(self, pyfuncitem):
raise ValueError raise ValueError
class MyPlugin2: class MyPlugin2:
def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs): def pytest_pyfunc_call(self, pyfuncitem):
return True return True
config.pluginmanager.register(MyPlugin1()) config.pluginmanager.register(MyPlugin1())
config.pluginmanager.register(MyPlugin2()) config.pluginmanager.register(MyPlugin2())