diff --git a/doc/test/extend.txt b/doc/test/extend.txt index 9ede74163..a5c21aa62 100644 --- a/doc/test/extend.txt +++ b/doc/test/extend.txt @@ -2,7 +2,6 @@ Extending and customizating py.test ================================================ -.. _`original definition of the hook`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/api.py .. _`local plugin`: @@ -45,6 +44,8 @@ strict checking on all hook functions. Function and argument names need to match exactly the `original definition of the hook`_. This allows 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 ------------------- diff --git a/py/_com.py b/py/_com.py index 48d13b17d..0f383df61 100644 --- a/py/_com.py +++ b/py/_com.py @@ -104,7 +104,7 @@ class Hooks: registry = py._com.comregistry self.registry = registry for name, method in vars(hookspecs).items(): - if name[:2] != "__": + if name[:1] != "_": firstresult = getattr(method, 'firstresult', False) mm = HookCall(registry, name, firstresult=firstresult) setattr(self, name, mm) diff --git a/py/test/plugin/api.py b/py/test/plugin/api.py deleted file mode 100644 index 9f71b0ac6..000000000 --- a/py/test/plugin/api.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -API definitions for pytest plugin hooks -""" - -class PluginHooks: - # ------------------------------------------------------------------------------ - # Command line and configuration hooks - # ------------------------------------------------------------------------------ - def pytest_addoption(self, parser): - """ called before commandline parsing. """ - - def pytest_configure(self, config): - """ called after command line options have been parsed. - and all plugins and initial conftest files been loaded. - ``config`` provides access to all such configuration values. - """ - - def pytest_unconfigure(self, config): - """ called before test process is exited. - """ - - # ------------------------------------------------------------------------------ - # test Session related hooks - # ------------------------------------------------------------------------------ - - def pytest_sessionstart(self, session): - """ before session.main() is called. """ - - def pytest_sessionfinish(self, session, exitstatus, excrepr=None): - """ whole test run finishes. """ - - def pytest_deselected(self, items): - """ collected items that were deselected (by keyword). """ - - # ------------------------------------------------------------------------------ - # collection hooks - # ------------------------------------------------------------------------------ - def pytest_make_collect_report(self, collector): - """ perform a collection and return a collection. """ - pytest_make_collect_report.firstresult = True - - def pytest_collect_file(self, path, parent): - """ return Collection node or None. """ - - def pytest_collect_recurse(self, path, parent): - """ return True/False to cause/prevent recursion into given directory. - return None if you do not want to make the decision. - """ - pytest_collect_recurse.firstresult = True - - def pytest_collect_directory(self, path, parent): - """ return Collection node or None. """ - - def pytest_pycollect_obj(self, 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(self, metafunc): - """ generate (multiple) parametrized calls to a test function.""" - - def pytest_collectstart(self, collector): - """ collector starts collecting. """ - - def pytest_collectreport(self, rep): - """ collector finished collecting. """ - - # XXX rename to item_collected()? meaning in distribution context? - def pytest_itemstart(self, item, node=None): - """ test item gets collected. """ - - # ------------------------------------------------------------------------------ - # runtest related hooks - # ------------------------------------------------------------------------------ - def pytest_runtest_setup(self, item): - """ called before pytest_runtest_call(). """ - - def pytest_runtest_call(self, item): - """ execute test item. """ - - def pytest_runtest_teardown(self, item): - """ called after pytest_runtest_call(). """ - - def pytest_runtest_protocol(self, item): - """ run given test item and return test report. """ - pytest_runtest_protocol.firstresult = True - - def pytest_pyfunc_call(self, 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(self, item, call): - """ make ItemTestReport for the specified test outcome. """ - pytest_runtest_makereport.firstresult = True - - def pytest_runtest_logreport(self, rep): - """ process item test report. """ - - # ------------------------------------------------------------------------------ - # reporting hooks (invoked from pytest_terminal.py) - # ------------------------------------------------------------------------------ - def pytest_report_teststatus(self, rep): - """ return shortletter and verbose word. """ - pytest_report_teststatus.firstresult = True - - def pytest_terminal_summary(self, terminalreporter): - """ add additional section in terminal summary reporting. """ - - def pytest_report_iteminfo(self, item): - """ return (fspath, lineno, name) for the item. - the information is used for result display and to sort tests - """ - pytest_report_iteminfo.firstresult = True - - # ------------------------------------------------------------------------------ - # doctest hooks - # ------------------------------------------------------------------------------ - def pytest_doctest_prepare_content(self, content): - """ return processed content for a given doctest""" - pytest_doctest_prepare_content.firstresult = True - - # ------------------------------------------------------------------------------ - # misc hooks - # ------------------------------------------------------------------------------ - - def pytest_plugin_registered(self, plugin): - """ a new py lib plugin got registered. """ - - def pytest_plugin_unregistered(self, plugin): - """ a py lib plugin got unregistered. """ - - def pytest_internalerror(self, excrepr): - """ called for internal errors. """ - - def pytest_trace(self, category, msg): - """ called for debug info. """ - - - - # ------------------------------------------------------------------------------ - # distributed testing - # ------------------------------------------------------------------------------ - - def pytest_testnodeready(self, node): - """ Test Node is ready to operate. """ - - def pytest_testnodedown(self, node, error): - """ Test Node is down. """ - - def pytest_rescheduleitems(self, items): - """ reschedule Items from a node that went down. """ - - def pytest_looponfailinfo(self, failreports, rootdirs): - """ info for repeating failing tests. """ - diff --git a/py/test/plugin/hookspec.py b/py/test/plugin/hookspec.py new file mode 100644 index 000000000..ee93e58e5 --- /dev/null +++ b/py/test/plugin/hookspec.py @@ -0,0 +1,153 @@ +""" +py.test hooks / extension points +""" + +# ------------------------------------------------------------------------------ +# Command line and configuration hooks +# ------------------------------------------------------------------------------ +def pytest_addoption(parser): + """ called before commandline parsing. """ + +def pytest_configure(config): + """ called after command line options have been parsed. + and all plugins and initial conftest files been loaded. + ``config`` provides access to all such configuration values. + """ + +def pytest_unconfigure(config): + """ called before test process is exited. + """ + +# ------------------------------------------------------------------------------ +# test Session related hooks +# ------------------------------------------------------------------------------ + +def pytest_sessionstart(session): + """ before session.main() is called. """ + +def pytest_sessionfinish(session, exitstatus, excrepr=None): + """ whole test run finishes. """ + +def pytest_deselected(items): + """ collected items that were deselected (by keyword). """ + +# ------------------------------------------------------------------------------ +# 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. """ + +def pytest_collect_recurse(path, parent): + """ return True/False to cause/prevent recursion into given directory. + return None if you do not want to make the decision. + """ +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. """ + +# XXX rename to item_collected()? meaning in distribution context? +def pytest_itemstart(item, node=None): + """ test item gets collected. """ + +# ------------------------------------------------------------------------------ +# runtest related hooks +# ------------------------------------------------------------------------------ +def pytest_runtest_setup(item): + """ called before pytest_runtest_call(). """ + +def pytest_runtest_call(item): + """ execute test item. """ + +def pytest_runtest_teardown(item): + """ called after pytest_runtest_call(). """ + +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 + +def pytest_runtest_logreport(rep): + """ process item test report. """ + +# ------------------------------------------------------------------------------ +# reporting hooks (invoked from pytest_terminal.py) +# ------------------------------------------------------------------------------ +def pytest_report_teststatus(rep): + """ return shortletter and verbose word. """ +pytest_report_teststatus.firstresult = True + +def pytest_terminal_summary(terminalreporter): + """ add additional section in terminal summary reporting. """ + +def pytest_report_iteminfo(item): + """ return (fspath, lineno, name) for the item. + the information is used for result display and to sort tests + """ +pytest_report_iteminfo.firstresult = True + +# ------------------------------------------------------------------------------ +# doctest hooks +# ------------------------------------------------------------------------------ +def pytest_doctest_prepare_content(content): + """ return processed content for a given doctest""" +pytest_doctest_prepare_content.firstresult = True + +# ------------------------------------------------------------------------------ +# misc hooks +# ------------------------------------------------------------------------------ + +def pytest_plugin_registered(plugin): + """ a new py lib plugin got registered. """ + +def pytest_plugin_unregistered(plugin): + """ a py lib plugin got unregistered. """ + +def pytest_internalerror(excrepr): + """ called for internal errors. """ + +def pytest_trace(category, msg): + """ called for debug info. """ + + + +# ------------------------------------------------------------------------------ +# distributed testing +# ------------------------------------------------------------------------------ + +def pytest_testnodeready(node): + """ Test Node is ready to operate. """ + +def pytest_testnodedown(node, error): + """ Test Node is down. """ + +def pytest_rescheduleitems(items): + """ reschedule Items from a node that went down. """ + +def pytest_looponfailinfo(failreports, rootdirs): + """ info for repeating failing tests. """ + diff --git a/py/test/plugin/pytest__pytest.py b/py/test/plugin/pytest__pytest.py index 5934b5ca0..c207a011d 100644 --- a/py/test/plugin/pytest__pytest.py +++ b/py/test/plugin/pytest__pytest.py @@ -57,7 +57,8 @@ class HookRecorder: def _makecallparser(self, method): name = method.__name__ args, varargs, varkw, default = py.std.inspect.getargspec(method) - assert args[0] == "self" + if args[0] != "self": + args.insert(0, 'self') fspec = py.std.inspect.formatargspec(args, varargs, varkw, default) # we use exec because we want to have early type # errors on wrong input arguments, using diff --git a/py/test/plugin/pytest_pytester.py b/py/test/plugin/pytest_pytester.py index e7010843f..7669f2f0d 100644 --- a/py/test/plugin/pytest_pytester.py +++ b/py/test/plugin/pytest_pytester.py @@ -6,7 +6,7 @@ import py import os import inspect from py.__.test.config import Config as pytestConfig -import api +import hookspec pytest_plugins = '_pytest' @@ -83,7 +83,7 @@ class TmpTestdir: raise ValueError("obj %r provides no comregistry" %(obj,)) assert isinstance(registry, py._com.Registry) reprec = ReportRecorder(registry) - reprec.hookrecorder = self._pytest.gethookrecorder(api.PluginHooks, registry) + reprec.hookrecorder = self._pytest.gethookrecorder(hookspec, registry) reprec.hook = reprec.hookrecorder.hook return reprec diff --git a/py/test/pluginmanager.py b/py/test/pluginmanager.py index ef141ec33..0b4999ab8 100644 --- a/py/test/pluginmanager.py +++ b/py/test/pluginmanager.py @@ -2,7 +2,7 @@ managing loading and interacting with pytest plugins. """ import py -from py.__.test.plugin import api +from py.__.test.plugin import hookspec def check_old_use(mod, modname): clsname = modname[len('pytest_'):].capitalize() + "Plugin" @@ -19,7 +19,7 @@ class PluginManager(object): self.impname2plugin = {} self.hook = py._com.Hooks( - hookspecs=api.PluginHooks, + hookspecs=hookspec, registry=self.comregistry) def _getpluginname(self, plugin, name): @@ -105,7 +105,7 @@ class PluginManager(object): # check plugin hooks # ===================================================== methods = collectattr(plugin) - hooks = collectattr(api.PluginHooks) + hooks = collectattr(hookspec) stringio = py.std.StringIO.StringIO() def Print(*args): if args: