add documented hookimpl_opts and hookspec_opts decorators

so that one doesn't have to use pytest.mark or function-attribute setting anymore

--HG--
branch : more_plugin
This commit is contained in:
holger krekel 2015-04-25 11:29:11 +02:00
parent bbbb6dc2e3
commit d2a5c7f99b
21 changed files with 150 additions and 47 deletions

View File

@ -26,6 +26,13 @@
change but it might still break 3rd party plugins which relied on change but it might still break 3rd party plugins which relied on
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
``pytest.hookspec_opts`` decorators for setting impl/spec
specific parameters. This substitutes the previous
now deprecated use of ``pytest.mark`` which is meant to
contain markers for test functions only.
2.7.1.dev (compared to 2.7.0) 2.7.1.dev (compared to 2.7.0)
----------------------------- -----------------------------

View File

@ -29,7 +29,7 @@ def pytest_addoption(parser):
help="shortcut for --capture=no.") help="shortcut for --capture=no.")
@pytest.mark.hookwrapper @pytest.hookimpl_opts(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.mark.hookwrapper @pytest.hookimpl_opts(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.mark.hookwrapper @pytest.hookimpl_opts(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.mark.hookwrapper @pytest.hookimpl_opts(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.mark.hookwrapper @pytest.hookimpl_opts(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.mark.tryfirst @pytest.hookimpl_opts(tryfirst=True)
def pytest_keyboard_interrupt(self, excinfo): def pytest_keyboard_interrupt(self, excinfo):
self.reset_capturings() self.reset_capturings()
@pytest.mark.tryfirst @pytest.hookimpl_opts(tryfirst=True)
def pytest_internalerror(self, excinfo): def pytest_internalerror(self, excinfo):
self.reset_capturings() self.reset_capturings()

View File

@ -7,6 +7,52 @@ import py
py3 = sys.version_info > (3,0) py3 = sys.version_info > (3,0)
def hookspec_opts(firstresult=False):
""" returns a decorator which will define a function as a hook specfication.
If firstresult is True the 1:N hook call (N being the number of registered
hook implementation functions) will stop at I<=N when the I'th function
returns a non-None result.
"""
def setattr_hookspec_opts(func):
if firstresult:
func.firstresult = firstresult
return func
return setattr_hookspec_opts
def hookimpl_opts(hookwrapper=False, optionalhook=False,
tryfirst=False, trylast=False):
""" Return a decorator which marks a function as a hook implementation.
If optionalhook is True a missing matching hook specification will not result
in an error (by default it is an error if no matching spec is found).
If tryfirst is True this hook implementation will run as early as possible
in the chain of N hook implementations for a specfication.
If trylast is True this hook implementation will run as late as possible
in the chain of N hook implementations.
If hookwrapper is True the hook implementations needs to execute exactly
one "yield". The code before the yield is run early before any non-hookwrapper
function is run. The code after the yield is run after all non-hookwrapper
function have run. The yield receives an ``CallOutcome`` object representing
the exception or result outcome of the inner calls (including other hookwrapper
calls).
"""
def setattr_hookimpl_opts(func):
if hookwrapper:
func.hookwrapper = True
if optionalhook:
func.optionalhook = True
if tryfirst:
func.tryfirst = True
if trylast:
func.trylast = True
return func
return setattr_hookimpl_opts
class TagTracer: class TagTracer:
def __init__(self): def __init__(self):
self._tag2proc = {} self._tag2proc = {}

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.mark.hookwrapper @pytest.hookimpl_opts(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,5 +1,7 @@
""" 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 _pytest.core import hookspec_opts
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Initialization # Initialization
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@ -15,9 +17,9 @@ def pytest_namespace():
are parsed. are parsed.
""" """
@hookspec_opts(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. """
pytest_cmdline_parse.firstresult = True
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. """
@ -47,10 +49,10 @@ def pytest_addoption(parser):
via (deprecated) ``pytest.config``. via (deprecated) ``pytest.config``.
""" """
@hookspec_opts(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. """
pytest_cmdline_main.firstresult = True
def pytest_load_initial_conftests(args, early_config, parser): def pytest_load_initial_conftests(args, early_config, parser):
""" implements the loading of initial conftest files ahead """ implements the loading of initial conftest files ahead
@ -64,18 +66,18 @@ def pytest_configure(config):
def pytest_unconfigure(config): def pytest_unconfigure(config):
""" called before test process is exited. """ """ called before test process is exited. """
@hookspec_opts(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). """
pytest_runtestloop.firstresult = True
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# collection hooks # collection hooks
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@hookspec_opts(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. """
pytest_collection.firstresult = True
def pytest_collection_modifyitems(session, config, items): def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order """ called after collection has been performed, may filter or re-order
@ -84,16 +86,16 @@ 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)
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.
""" """
pytest_ignore_collect.firstresult = True
@hookspec_opts(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. """
pytest_collect_directory.firstresult = True
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
""" return collection Node or None for the given path. Any new node """ return collection Node or None for the given path. Any new node
@ -112,29 +114,29 @@ 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)
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. """
pytest_make_collect_report.firstresult = True
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Python test function related hooks # Python test function related hooks
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@hookspec_opts(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.
The pytest_collect_file hook needs to be used if you want to The pytest_collect_file hook needs to be used if you want to
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.
""" """
pytest_pycollect_makemodule.firstresult = True
@hookspec_opts(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. """
pytest_pycollect_makeitem.firstresult = True
@hookspec_opts(firstresult=True)
def pytest_pyfunc_call(pyfuncitem): def pytest_pyfunc_call(pyfuncitem):
""" call underlying test function. """ """ call underlying test function. """
pytest_pyfunc_call.firstresult = True
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
""" generate (multiple) parametrized calls to a test function.""" """ generate (multiple) parametrized calls to a test function."""
@ -145,6 +147,7 @@ def pytest_generate_tests(metafunc):
def pytest_itemstart(item, node): def pytest_itemstart(item, node):
""" (deprecated, use pytest_runtest_logstart). """ """ (deprecated, use pytest_runtest_logstart). """
@hookspec_opts(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
@ -158,7 +161,6 @@ def pytest_runtest_protocol(item, nextitem):
:return boolean: True if no further hook implementations should be invoked. :return boolean: True if no further hook implementations should be invoked.
""" """
pytest_runtest_protocol.firstresult = True
def pytest_runtest_logstart(nodeid, location): def pytest_runtest_logstart(nodeid, location):
""" signal the start of running a single test item. """ """ signal the start of running a single test item. """
@ -178,12 +180,12 @@ 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)
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
:py:class:`_pytest.runner.CallInfo`. :py:class:`_pytest.runner.CallInfo`.
""" """
pytest_runtest_makereport.firstresult = True
def pytest_runtest_logreport(report): def pytest_runtest_logreport(report):
""" process a test setup/call/teardown report relating to """ process a test setup/call/teardown report relating to
@ -220,9 +222,9 @@ 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)
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."""
pytest_report_teststatus.firstresult = True
def pytest_terminal_summary(terminalreporter): def pytest_terminal_summary(terminalreporter):
""" add additional section in terminal summary reporting. """ """ add additional section in terminal summary reporting. """
@ -236,9 +238,9 @@ def pytest_logwarning(message, code, nodeid, fslocation):
# doctest hooks # doctest hooks
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@hookspec_opts(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"""
pytest_doctest_prepare_content.firstresult = True
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# error handling and internal debugging hooks # error handling and internal debugging hooks

View File

@ -519,12 +519,12 @@ class Session(FSCollector):
def _makeid(self): def _makeid(self):
return "" return ""
@pytest.mark.tryfirst @pytest.hookimpl_opts(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.mark.tryfirst @pytest.hookimpl_opts(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.mark.trylast @pytest.hookimpl_opts(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.mark.trylast @pytest.hookimpl_opts(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

@ -172,7 +172,7 @@ def pytest_configure(config):
def pytest_sessionstart(session): def pytest_sessionstart(session):
session._fixturemanager = FixtureManager(session) session._fixturemanager = FixtureManager(session)
@pytest.mark.trylast @pytest.hookimpl_opts(trylast=True)
def pytest_namespace(): def pytest_namespace():
raises.Exception = pytest.fail.Exception raises.Exception = pytest.fail.Exception
return { return {
@ -191,7 +191,7 @@ def pytestconfig(request):
return request.config return request.config
@pytest.mark.trylast @pytest.hookimpl_opts(trylast=True)
def pytest_pyfunc_call(pyfuncitem): def pytest_pyfunc_call(pyfuncitem):
testfunction = pyfuncitem.obj testfunction = pyfuncitem.obj
if pyfuncitem._isyieldedfunction(): if pyfuncitem._isyieldedfunction():
@ -219,7 +219,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.mark.hookwrapper @pytest.hookimpl_opts(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()
@ -1667,7 +1667,7 @@ class FixtureManager:
self.parsefactories(plugin, nodeid) self.parsefactories(plugin, nodeid)
self._seenplugins.add(plugin) self._seenplugins.add(plugin)
@pytest.mark.tryfirst @pytest.hookimpl_opts(tryfirst=True)
def pytest_configure(self, config): def pytest_configure(self, config):
plugins = config.pluginmanager.getplugins() plugins = config.pluginmanager.getplugins()
for plugin in plugins: for plugin in plugins:

View File

@ -133,7 +133,7 @@ class MarkEvaluator:
return expl return expl
@pytest.mark.tryfirst @pytest.hookimpl_opts(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.mark.hookwrapper @pytest.hookimpl_opts(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

@ -265,7 +265,7 @@ class TerminalReporter:
def pytest_collection_modifyitems(self): def pytest_collection_modifyitems(self):
self.report_collect(True) self.report_collect(True)
@pytest.mark.trylast @pytest.hookimpl_opts(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:
@ -350,7 +350,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.mark.hookwrapper @pytest.hookimpl_opts(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.mark.tryfirst @pytest.hookimpl_opts(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.mark.hookwrapper @pytest.hookimpl_opts(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.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. @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.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late 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.
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.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. @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.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late 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.
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.mark.tryfirst @pytest.hookimpl_opts(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.mark.tryfirst @pytest.hookimpl_opts(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

@ -458,7 +458,7 @@ Here is an example definition of a hook wrapper::
import pytest import pytest
@pytest.mark.hookwrapper @pytest.hookimpl_opts(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
outcome = yield outcome = yield

View File

@ -12,6 +12,7 @@ if __name__ == '__main__': # if run as a script or by 'python -m pytest'
# else we are imported # else we are imported
from _pytest.config import main, UsageError, _preloadplugins, cmdline from _pytest.config import main, UsageError, _preloadplugins, cmdline
from _pytest.core import hookspec_opts, hookimpl_opts
from _pytest import __version__ from _pytest import __version__
_preloadplugins() # to populate pytest.* namespace so help(pytest) works _preloadplugins() # to populate pytest.* namespace so help(pytest) works

View File

@ -66,7 +66,7 @@ def check_open_files(config):
error.append(error[0]) error.append(error[0])
raise AssertionError("\n".join(error)) raise AssertionError("\n".join(error))
@pytest.mark.trylast @pytest.hookimpl_opts(trylast=True)
def pytest_runtest_teardown(item, __multicall__): def pytest_runtest_teardown(item, __multicall__):
item.config._basedir.chdir() item.config._basedir.chdir()
if hasattr(item.config, '_openfiles'): if hasattr(item.config, '_openfiles'):

View File

@ -563,7 +563,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.mark.hookwrapper @pytest.hookimpl_opts(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

@ -192,6 +192,53 @@ class TestAddMethodOrdering:
assert hc.nonwrappers == [he_method1_middle] assert hc.nonwrappers == [he_method1_middle]
assert hc.wrappers == [he_method1, he_method3] assert hc.wrappers == [he_method1, he_method3]
def test_hookspec_opts(self, pm):
class HookSpec:
@hookspec_opts()
def he_myhook1(self, arg1):
pass
@hookspec_opts(firstresult=True)
def he_myhook2(self, arg1):
pass
@hookspec_opts(firstresult=False)
def he_myhook3(self, arg1):
pass
pm.addhooks(HookSpec)
assert not pm.hook.he_myhook1.firstresult
assert pm.hook.he_myhook2.firstresult
assert not pm.hook.he_myhook3.firstresult
def test_hookimpl_opts(self):
for name in ["hookwrapper", "optionalhook", "tryfirst", "trylast"]:
for val in [True, False]:
@hookimpl_opts(**{name: val})
def he_myhook1(self, arg1):
pass
if val:
assert getattr(he_myhook1, name)
else:
assert not hasattr(he_myhook1, name)
def test_decorator_functional(self, pm):
class HookSpec:
@hookspec_opts(firstresult=True)
def he_myhook(self, arg1):
""" add to arg1 """
pm.addhooks(HookSpec)
class Plugin:
@hookimpl_opts()
def he_myhook(self, arg1):
return arg1 + 1
pm.register(Plugin())
results = pm.hook.he_myhook(arg1=17)
assert results == 18
class TestPytestPluginInteractions: class TestPytestPluginInteractions:

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.mark.optionalhook @pytest.hookimpl_opts(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.mark.hookwrapper @pytest.hookimpl_opts(hookwrapper=True)
def pytest_pycollect_makeitem(name): def pytest_pycollect_makeitem(name):
outcome = yield outcome = yield
if name == "TestClass": if name == "TestClass":