adapt to pluggy naming, rename pytest.hookspec_opts to pytest.hookspec,s ame with hookimpl_opts

--HG--
branch : pluggy1
This commit is contained in:
holger krekel 2015-05-06 10:08:08 +02:00
parent 5ea7f0342b
commit bddc88f09e
20 changed files with 81 additions and 75 deletions

View File

@ -27,8 +27,8 @@
details like especially the pluginmanager.add_shutdown() API. details like especially the pluginmanager.add_shutdown() API.
Thanks Holger Krekel. Thanks Holger Krekel.
- pluginmanagement: introduce ``pytest.hookimpl_opts`` and - pluginmanagement: introduce ``pytest.hookimpl`` and
``pytest.hookspec_opts`` decorators for setting impl/spec ``pytest.hookspec`` decorators for setting impl/spec
specific parameters. This substitutes the previous specific parameters. This substitutes the previous
now deprecated use of ``pytest.mark`` which is meant to now deprecated use of ``pytest.mark`` which is meant to
contain markers for test functions only. contain markers for test functions only.

View File

@ -29,7 +29,7 @@ def pytest_addoption(parser):
help="shortcut for --capture=no.") help="shortcut for --capture=no.")
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_load_initial_conftests(early_config, parser, args): def pytest_load_initial_conftests(early_config, parser, args):
ns = early_config.known_args_namespace ns = early_config.known_args_namespace
pluginmanager = early_config.pluginmanager pluginmanager = early_config.pluginmanager
@ -101,7 +101,7 @@ class CaptureManager:
if capfuncarg is not None: if capfuncarg is not None:
capfuncarg.close() capfuncarg.close()
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_make_collect_report(self, collector): def pytest_make_collect_report(self, collector):
if isinstance(collector, pytest.File): if isinstance(collector, pytest.File):
self.resumecapture() self.resumecapture()
@ -115,13 +115,13 @@ class CaptureManager:
else: else:
yield yield
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_runtest_setup(self, item): def pytest_runtest_setup(self, item):
self.resumecapture() self.resumecapture()
yield yield
self.suspendcapture_item(item, "setup") self.suspendcapture_item(item, "setup")
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(self, item): def pytest_runtest_call(self, item):
self.resumecapture() self.resumecapture()
self.activate_funcargs(item) self.activate_funcargs(item)
@ -129,17 +129,17 @@ class CaptureManager:
#self.deactivate_funcargs() called from suspendcapture() #self.deactivate_funcargs() called from suspendcapture()
self.suspendcapture_item(item, "call") self.suspendcapture_item(item, "call")
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_runtest_teardown(self, item): def pytest_runtest_teardown(self, item):
self.resumecapture() self.resumecapture()
yield yield
self.suspendcapture_item(item, "teardown") self.suspendcapture_item(item, "teardown")
@pytest.hookimpl_opts(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_keyboard_interrupt(self, excinfo): def pytest_keyboard_interrupt(self, excinfo):
self.reset_capturings() self.reset_capturings()
@pytest.hookimpl_opts(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_internalerror(self, excinfo): def pytest_internalerror(self, excinfo):
self.reset_capturings() self.reset_capturings()

View File

@ -8,11 +8,11 @@ import warnings
import py import py
# DON't import pytest here because it causes import cycle troubles # DON't import pytest here because it causes import cycle troubles
import sys, os import sys, os
from _pytest import hookspec # the extension point definitions import _pytest.hookspec # the extension point definitions
from pluggy import PluginManager, HookimplDecorator, HookspecDecorator from pluggy import PluginManager, HookimplMarker, HookspecMarker
hookimpl_opts = HookimplDecorator("pytest") hookimpl = HookimplMarker("pytest")
hookspec_opts = HookspecDecorator("pytest") hookspec = HookspecMarker("pytest")
# pytest startup # pytest startup
# #
@ -121,7 +121,7 @@ class PytestPluginManager(PluginManager):
self._conftestpath2mod = {} self._conftestpath2mod = {}
self._confcutdir = None self._confcutdir = None
self.add_hookspecs(hookspec) self.add_hookspecs(_pytest.hookspec)
self.register(self) self.register(self)
if os.environ.get('PYTEST_DEBUG'): if os.environ.get('PYTEST_DEBUG'):
err = sys.stderr err = sys.stderr
@ -184,7 +184,7 @@ class PytestPluginManager(PluginManager):
return self.get_plugin(name) return self.get_plugin(name)
def pytest_configure(self, config): def pytest_configure(self, config):
# XXX now that the pluginmanager exposes hookimpl_opts(tryfirst...) # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
# we should remove tryfirst/trylast as markers # we should remove tryfirst/trylast as markers
config.addinivalue_line("markers", config.addinivalue_line("markers",
"tryfirst: mark a hook implementation function such that the " "tryfirst: mark a hook implementation function such that the "
@ -827,7 +827,7 @@ class Config(object):
if not hasattr(self.option, opt.dest): if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default) setattr(self.option, opt.dest, opt.default)
@hookimpl_opts(trylast=True) @hookimpl(trylast=True)
def pytest_load_initial_conftests(self, early_config): def pytest_load_initial_conftests(self, early_config):
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)

View File

@ -22,7 +22,7 @@ def pytest_addoption(parser):
help="store internal tracing debug information in 'pytestdebug.log'.") help="store internal tracing debug information in 'pytestdebug.log'.")
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_cmdline_parse(): def pytest_cmdline_parse():
outcome = yield outcome = yield
config = outcome.get_result() config = outcome.get_result()

View File

@ -1,32 +1,32 @@
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
from pluggy import HookspecDecorator from pluggy import HookspecMarker
hookspec_opts = HookspecDecorator("pytest") hookspec = HookspecMarker("pytest")
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Initialization hooks called for every plugin # Initialization hooks called for every plugin
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@hookspec_opts(historic=True) @hookspec(historic=True)
def pytest_addhooks(pluginmanager): def pytest_addhooks(pluginmanager):
"""called at plugin registration time to allow adding new hooks via a call to """called at plugin registration time to allow adding new hooks via a call to
pluginmanager.addhooks(module_or_class, prefix).""" pluginmanager.add_hookspecs(module_or_class, prefix)."""
@hookspec_opts(historic=True) @hookspec(historic=True)
def pytest_namespace(): def pytest_namespace():
"""return dict of name->object to be made globally available in """return dict of name->object to be made globally available in
the pytest namespace. This hook is called at plugin registration the pytest namespace. This hook is called at plugin registration
time. time.
""" """
@hookspec_opts(historic=True) @hookspec(historic=True)
def pytest_plugin_registered(plugin, manager): def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """ """ a new pytest plugin got registered. """
@hookspec_opts(historic=True) @hookspec(historic=True)
def pytest_addoption(parser): def pytest_addoption(parser):
"""register argparse-style options and ini-style config values. """register argparse-style options and ini-style config values.
@ -52,7 +52,7 @@ def pytest_addoption(parser):
via (deprecated) ``pytest.config``. via (deprecated) ``pytest.config``.
""" """
@hookspec_opts(historic=True) @hookspec(historic=True)
def pytest_configure(config): def pytest_configure(config):
""" called after command line options have been parsed """ called after command line options have been parsed
and all plugins and initial conftest files been loaded. and all plugins and initial conftest files been loaded.
@ -65,14 +65,14 @@ def pytest_configure(config):
# discoverable conftest.py local plugins. # discoverable conftest.py local plugins.
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_cmdline_parse(pluginmanager, args): def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args. """ """return initialized config object, parsing the specified args. """
def pytest_cmdline_preparse(config, args): def pytest_cmdline_preparse(config, args):
"""(deprecated) modify command line arguments before option parsing. """ """(deprecated) modify command line arguments before option parsing. """
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_cmdline_main(config): def pytest_cmdline_main(config):
""" called for performing the main command line action. The default """ called for performing the main command line action. The default
implementation will invoke the configure hooks and runtest_mainloop. """ implementation will invoke the configure hooks and runtest_mainloop. """
@ -86,7 +86,7 @@ def pytest_load_initial_conftests(args, early_config, parser):
# collection hooks # collection hooks
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_collection(session): def pytest_collection(session):
""" perform the collection protocol for the given session. """ """ perform the collection protocol for the given session. """
@ -97,14 +97,14 @@ def pytest_collection_modifyitems(session, config, items):
def pytest_collection_finish(session): def pytest_collection_finish(session):
""" called after collection has been performed and modified. """ """ called after collection has been performed and modified. """
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_ignore_collect(path, config): def pytest_ignore_collect(path, config):
""" return True to prevent considering this path for collection. """ return True to prevent considering this path for collection.
This hook is consulted for all files and directories prior to calling This hook is consulted for all files and directories prior to calling
more specific hooks. more specific hooks.
""" """
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_collect_directory(path, parent): def pytest_collect_directory(path, parent):
""" called before traversing a directory for collection files. """ """ called before traversing a directory for collection files. """
@ -125,7 +125,7 @@ def pytest_collectreport(report):
def pytest_deselected(items): def pytest_deselected(items):
""" called for test items deselected by keyword. """ """ called for test items deselected by keyword. """
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_make_collect_report(collector): def pytest_make_collect_report(collector):
""" perform ``collector.collect()`` and return a CollectReport. """ """ perform ``collector.collect()`` and return a CollectReport. """
@ -133,7 +133,7 @@ def pytest_make_collect_report(collector):
# Python test function related hooks # Python test function related hooks
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_pycollect_makemodule(path, parent): def pytest_pycollect_makemodule(path, parent):
""" return a Module collector or None for the given path. """ return a Module collector or None for the given path.
This hook will be called for each matching test module path. This hook will be called for each matching test module path.
@ -141,11 +141,11 @@ def pytest_pycollect_makemodule(path, parent):
create test modules for files that do not match as a test module. create test modules for files that do not match as a test module.
""" """
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_pycollect_makeitem(collector, name, obj): def pytest_pycollect_makeitem(collector, name, obj):
""" return custom item/collector for a python object in a module, or None. """ """ return custom item/collector for a python object in a module, or None. """
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_pyfunc_call(pyfuncitem): def pytest_pyfunc_call(pyfuncitem):
""" call underlying test function. """ """ call underlying test function. """
@ -156,7 +156,7 @@ def pytest_generate_tests(metafunc):
# generic runtest related hooks # generic runtest related hooks
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_runtestloop(session): def pytest_runtestloop(session):
""" called for performing the main runtest loop """ called for performing the main runtest loop
(after collection finished). """ (after collection finished). """
@ -164,7 +164,7 @@ def pytest_runtestloop(session):
def pytest_itemstart(item, node): def pytest_itemstart(item, node):
""" (deprecated, use pytest_runtest_logstart). """ """ (deprecated, use pytest_runtest_logstart). """
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_runtest_protocol(item, nextitem): def pytest_runtest_protocol(item, nextitem):
""" implements the runtest_setup/call/teardown protocol for """ implements the runtest_setup/call/teardown protocol for
the given test item, including capturing exceptions and calling the given test item, including capturing exceptions and calling
@ -197,7 +197,7 @@ def pytest_runtest_teardown(item, nextitem):
so that nextitem only needs to call setup-functions. so that nextitem only needs to call setup-functions.
""" """
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
""" return a :py:class:`_pytest.runner.TestReport` object """ return a :py:class:`_pytest.runner.TestReport` object
for the given :py:class:`pytest.Item` and for the given :py:class:`pytest.Item` and
@ -242,7 +242,7 @@ def pytest_assertrepr_compare(config, op, left, right):
def pytest_report_header(config, startdir): def pytest_report_header(config, startdir):
""" return a string to be displayed as header info for terminal reporting.""" """ return a string to be displayed as header info for terminal reporting."""
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_report_teststatus(report): def pytest_report_teststatus(report):
""" return result-category, shortletter and verbose word for reporting.""" """ return result-category, shortletter and verbose word for reporting."""
@ -258,7 +258,7 @@ def pytest_logwarning(message, code, nodeid, fslocation):
# doctest hooks # doctest hooks
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@hookspec_opts(firstresult=True) @hookspec(firstresult=True)
def pytest_doctest_prepare_content(content): def pytest_doctest_prepare_content(content):
""" return processed content for a given doctest""" """ return processed content for a given doctest"""

View File

@ -512,12 +512,12 @@ class Session(FSCollector):
def _makeid(self): def _makeid(self):
return "" return ""
@pytest.hookimpl_opts(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_collectstart(self): def pytest_collectstart(self):
if self.shouldstop: if self.shouldstop:
raise self.Interrupted(self.shouldstop) raise self.Interrupted(self.shouldstop)
@pytest.hookimpl_opts(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_runtest_logreport(self, report): def pytest_runtest_logreport(self, report):
if report.failed and not hasattr(report, 'wasxfail'): if report.failed and not hasattr(report, 'wasxfail'):
self._testsfailed += 1 self._testsfailed += 1

View File

@ -24,7 +24,7 @@ def pytest_runtest_makereport(item, call):
call.excinfo = call2.excinfo call.excinfo = call2.excinfo
@pytest.hookimpl_opts(trylast=True) @pytest.hookimpl(trylast=True)
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
if is_potential_nosetest(item): if is_potential_nosetest(item):
if isinstance(item.parent, pytest.Generator): if isinstance(item.parent, pytest.Generator):

View File

@ -11,7 +11,7 @@ def pytest_addoption(parser):
choices=['failed', 'all'], choices=['failed', 'all'],
help="send failed|all info to bpaste.net pastebin service.") help="send failed|all info to bpaste.net pastebin service.")
@pytest.hookimpl_opts(trylast=True) @pytest.hookimpl(trylast=True)
def pytest_configure(config): def pytest_configure(config):
if config.option.pastebin == "all": if config.option.pastebin == "all":
tr = config.pluginmanager.getplugin('terminalreporter') tr = config.pluginmanager.getplugin('terminalreporter')

View File

@ -80,7 +80,7 @@ class LsofFdLeakChecker(object):
else: else:
return True return True
@pytest.hookimpl_opts(hookwrapper=True, tryfirst=True) @pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_item(self, item): def pytest_runtest_item(self, item):
lines1 = self.get_open_files() lines1 = self.get_open_files()
yield yield

View File

@ -181,7 +181,7 @@ def pytest_configure(config):
def pytest_sessionstart(session): def pytest_sessionstart(session):
session._fixturemanager = FixtureManager(session) session._fixturemanager = FixtureManager(session)
@pytest.hookimpl_opts(trylast=True) @pytest.hookimpl(trylast=True)
def pytest_namespace(): def pytest_namespace():
raises.Exception = pytest.fail.Exception raises.Exception = pytest.fail.Exception
return { return {
@ -200,7 +200,7 @@ def pytestconfig(request):
return request.config return request.config
@pytest.hookimpl_opts(trylast=True) @pytest.hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem): def pytest_pyfunc_call(pyfuncitem):
testfunction = pyfuncitem.obj testfunction = pyfuncitem.obj
if pyfuncitem._isyieldedfunction(): if pyfuncitem._isyieldedfunction():
@ -228,7 +228,7 @@ def pytest_collect_file(path, parent):
def pytest_pycollect_makemodule(path, parent): def pytest_pycollect_makemodule(path, parent):
return Module(path, parent) return Module(path, parent)
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(collector, name, obj): def pytest_pycollect_makeitem(collector, name, obj):
outcome = yield outcome = yield
res = outcome.get_result() res = outcome.get_result()

View File

@ -133,7 +133,7 @@ class MarkEvaluator:
return expl return expl
@pytest.hookimpl_opts(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
evalskip = MarkEvaluator(item, 'skipif') evalskip = MarkEvaluator(item, 'skipif')
if evalskip.istrue(): if evalskip.istrue():
@ -151,7 +151,7 @@ def check_xfail_no_run(item):
if not evalxfail.get('run', True): if not evalxfail.get('run', True):
pytest.xfail("[NOTRUN] " + evalxfail.getexplanation()) pytest.xfail("[NOTRUN] " + evalxfail.getexplanation())
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
outcome = yield outcome = yield
rep = outcome.get_result() rep = outcome.get_result()

View File

@ -268,7 +268,7 @@ class TerminalReporter:
def pytest_collection_modifyitems(self): def pytest_collection_modifyitems(self):
self.report_collect(True) self.report_collect(True)
@pytest.hookimpl_opts(trylast=True) @pytest.hookimpl(trylast=True)
def pytest_sessionstart(self, session): def pytest_sessionstart(self, session):
self._sessionstarttime = time.time() self._sessionstarttime = time.time()
if not self.showheader: if not self.showheader:
@ -355,7 +355,7 @@ class TerminalReporter:
indent = (len(stack) - 1) * " " indent = (len(stack) - 1) * " "
self._tw.line("%s%s" % (indent, col)) self._tw.line("%s%s" % (indent, col))
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_sessionfinish(self, exitstatus): def pytest_sessionfinish(self, exitstatus):
outcome = yield outcome = yield
outcome.get_result() outcome.get_result()

View File

@ -140,7 +140,7 @@ class TestCaseFunction(pytest.Function):
if traceback: if traceback:
excinfo.traceback = traceback excinfo.traceback = traceback
@pytest.hookimpl_opts(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
if isinstance(item, TestCaseFunction): if isinstance(item, TestCaseFunction):
if item._excinfo: if item._excinfo:
@ -152,7 +152,7 @@ def pytest_runtest_makereport(item, call):
# twisted trial support # twisted trial support
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item): def pytest_runtest_protocol(item):
if isinstance(item, TestCaseFunction) and \ if isinstance(item, TestCaseFunction) and \
'twisted.trial.unittest' in sys.modules: 'twisted.trial.unittest' in sys.modules:

View File

@ -201,9 +201,9 @@ You can ask which markers exist for your test suite - the list includes our just
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
@pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. @pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
@pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. @pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
For an example on how to add and work with markers from a plugin, see For an example on how to add and work with markers from a plugin, see
@ -375,9 +375,9 @@ The ``--markers`` option always gives you a list of available markers::
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
@pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. @pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
@pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. @pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
Reading markers which were set from multiple places Reading markers which were set from multiple places

View File

@ -534,7 +534,7 @@ case we just write some informations out to a ``failures`` file::
import pytest import pytest
import os.path import os.path
@pytest.hookimpl_opts(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call, __multicall__): def pytest_runtest_makereport(item, call, __multicall__):
# execute all other hooks to obtain the report object # execute all other hooks to obtain the report object
rep = __multicall__.execute() rep = __multicall__.execute()
@ -607,7 +607,7 @@ here is a little example implemented via a local plugin::
import pytest import pytest
@pytest.hookimpl_opts(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call, __multicall__): def pytest_runtest_makereport(item, call, __multicall__):
# execute all other hooks to obtain the report object # execute all other hooks to obtain the report object
rep = __multicall__.execute() rep = __multicall__.execute()

View File

@ -292,7 +292,7 @@ Here is an example definition of a hook wrapper::
import pytest import pytest
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem): def pytest_pyfunc_call(pyfuncitem):
# do whatever you want before the next hook executes # do whatever you want before the next hook executes
@ -305,8 +305,7 @@ Here is an example definition of a hook wrapper::
Note that hook wrappers don't return results themselves, they merely Note that hook wrappers don't return results themselves, they merely
perform tracing or other side effects around the actual hook implementations. perform tracing or other side effects around the actual hook implementations.
If the result of the underlying hook is a mutable object, they may modify If the result of the underlying hook is a mutable object, they may modify
that result, however. that result but it's probably better to avoid it.
Hook function ordering / call example Hook function ordering / call example
@ -338,16 +337,24 @@ after others, i.e. the position in the ``N``-sized list of functions::
Here is the order of execution: Here is the order of execution:
1. Plugin3's pytest_collection_modifyitems called until the yield point 1. Plugin3's pytest_collection_modifyitems called until the yield point
2. Plugin1's pytest_collection_modifyitems is called because it is a hook wrapper.
3. Plugin2's pytest_collection_modifyitems is called
4. Plugin3's pytest_collection_modifyitems called for executing after the yield 2. Plugin1's pytest_collection_modifyitems is called because it is marked
The yield receives a :py:class:`CallOutcome` instance which encapsulates with ``tryfirst=True``.
the result from calling the non-wrappers. Wrappers cannot modify the result.
3. Plugin2's pytest_collection_modifyitems is called because it is marked
with ``trylast=True`` (but even without this mark it would come after
Plugin1).
4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
point. The yield receives a :py:class:`CallOutcome` instance which encapsulates
the result from calling the non-wrappers. Wrappers shall not modify the result.
It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
``hookwrapper=True`` in which case it will influence the ordering of hookwrappers ``hookwrapper=True`` in which case it will influence the ordering of hookwrappers
among each other. among each other.
Declaring new hooks Declaring new hooks
------------------------ ------------------------
@ -368,11 +375,11 @@ For an example, see `newhooks.py`_ from :ref:`xdist`.
.. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default .. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default
Using hooks from 3rd party plugins Optionally using hooks from 3rd party plugins
------------------------------------- ---------------------------------------------
Using new hooks from plugins as explained above might be a little tricky Using new hooks from plugins as explained above might be a little tricky
because the standard :ref:`validation mechanism <validation>`: because of the standard :ref:`validation mechanism <validation>`:
if you depend on a plugin that is not installed, validation will fail and if you depend on a plugin that is not installed, validation will fail and
the error message will not make much sense to your users. the error message will not make much sense to your users.
@ -395,7 +402,6 @@ declaring the hook functions directly in your plugin module, for example::
This has the added benefit of allowing you to conditionally install hooks This has the added benefit of allowing you to conditionally install hooks
depending on which plugins are installed. depending on which plugins are installed.
.. _`well specified hooks`: .. _`well specified hooks`:
.. currentmodule:: _pytest.hookspec .. currentmodule:: _pytest.hookspec

View File

@ -13,7 +13,7 @@ if __name__ == '__main__': # if run as a script or by 'python -m pytest'
from _pytest.config import ( from _pytest.config import (
main, UsageError, _preloadplugins, cmdline, main, UsageError, _preloadplugins, cmdline,
hookspec_opts, hookimpl_opts hookspec, hookimpl
) )
from _pytest import __version__ from _pytest import __version__

View File

@ -559,7 +559,7 @@ class TestConftestCustomization:
b = testdir.mkdir("a").mkdir("b") b = testdir.mkdir("a").mkdir("b")
b.join("conftest.py").write(py.code.Source(""" b.join("conftest.py").write(py.code.Source("""
import pytest import pytest
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(): def pytest_pycollect_makeitem():
outcome = yield outcome = yield
if outcome.excinfo is None: if outcome.excinfo is None:

View File

@ -38,7 +38,7 @@ def test_hookvalidation_unknown(testdir):
def test_hookvalidation_optional(testdir): def test_hookvalidation_optional(testdir):
testdir.makeconftest(""" testdir.makeconftest("""
import pytest import pytest
@pytest.hookimpl_opts(optionalhook=True) @pytest.hookimpl(optionalhook=True)
def pytest_hello(xyz): def pytest_hello(xyz):
pass pass
""") """)

View File

@ -510,7 +510,7 @@ class TestKeywordSelection:
""") """)
testdir.makepyfile(conftest=""" testdir.makepyfile(conftest="""
import pytest import pytest
@pytest.hookimpl_opts(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(name): def pytest_pycollect_makeitem(name):
outcome = yield outcome = yield
if name == "TestClass": if name == "TestClass":