parent
7008385253
commit
b41acaea12
|
@ -0,0 +1,6 @@
|
||||||
|
``pluggy>=1.2.0`` is now required.
|
||||||
|
|
||||||
|
pytest now uses "new-style" hook wrappers internally, available since pluggy 1.2.0.
|
||||||
|
See `pluggy's 1.2.0 changelog <https://pluggy.readthedocs.io/en/latest/changelog.html#pluggy-1-2-0-2023-06-21>`_ and the :ref:`updated docs <hookwrapper>` for details.
|
||||||
|
|
||||||
|
Plugins which want to use new-style wrappers can do so if they require this version of pytest or later.
|
|
@ -808,11 +808,10 @@ case we just write some information out to a ``failures`` file:
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
# execute all other hooks to obtain the report object
|
# execute all other hooks to obtain the report object
|
||||||
outcome = yield
|
rep = yield
|
||||||
rep = outcome.get_result()
|
|
||||||
|
|
||||||
# we only look at actual failing test calls, not setup/teardown
|
# we only look at actual failing test calls, not setup/teardown
|
||||||
if rep.when == "call" and rep.failed:
|
if rep.when == "call" and rep.failed:
|
||||||
|
@ -826,6 +825,8 @@ case we just write some information out to a ``failures`` file:
|
||||||
|
|
||||||
f.write(rep.nodeid + extra + "\n")
|
f.write(rep.nodeid + extra + "\n")
|
||||||
|
|
||||||
|
return rep
|
||||||
|
|
||||||
|
|
||||||
if you then have failing tests:
|
if you then have failing tests:
|
||||||
|
|
||||||
|
@ -899,16 +900,17 @@ here is a little example implemented via a local plugin:
|
||||||
phase_report_key = StashKey[Dict[str, CollectReport]]()
|
phase_report_key = StashKey[Dict[str, CollectReport]]()
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
# execute all other hooks to obtain the report object
|
# execute all other hooks to obtain the report object
|
||||||
outcome = yield
|
rep = yield
|
||||||
rep = outcome.get_result()
|
|
||||||
|
|
||||||
# store test results for each phase of a call, which can
|
# store test results for each phase of a call, which can
|
||||||
# be "setup", "call", "teardown"
|
# be "setup", "call", "teardown"
|
||||||
item.stash.setdefault(phase_report_key, {})[rep.when] = rep
|
item.stash.setdefault(phase_report_key, {})[rep.when] = rep
|
||||||
|
|
||||||
|
return rep
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def something(request):
|
def something(request):
|
||||||
|
|
|
@ -56,7 +56,7 @@ The remaining hook functions will not be called in this case.
|
||||||
|
|
||||||
.. _`hookwrapper`:
|
.. _`hookwrapper`:
|
||||||
|
|
||||||
hookwrapper: executing around other hooks
|
hook wrappers: executing around other hooks
|
||||||
-------------------------------------------------
|
-------------------------------------------------
|
||||||
|
|
||||||
.. currentmodule:: _pytest.core
|
.. currentmodule:: _pytest.core
|
||||||
|
@ -69,10 +69,8 @@ which yields exactly once. When pytest invokes hooks it first executes
|
||||||
hook wrappers and passes the same arguments as to the regular hooks.
|
hook wrappers and passes the same arguments as to the regular hooks.
|
||||||
|
|
||||||
At the yield point of the hook wrapper pytest will execute the next hook
|
At the yield point of the hook wrapper pytest will execute the next hook
|
||||||
implementations and return their result to the yield point in the form of
|
implementations and return their result to the yield point, or will
|
||||||
a :py:class:`Result <pluggy._Result>` instance which encapsulates a result or
|
propagate an exception if they raised.
|
||||||
exception info. The yield point itself will thus typically not raise
|
|
||||||
exceptions (unless there are bugs).
|
|
||||||
|
|
||||||
Here is an example definition of a hook wrapper:
|
Here is an example definition of a hook wrapper:
|
||||||
|
|
||||||
|
@ -81,23 +79,32 @@ Here is an example definition of a hook wrapper:
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_pyfunc_call(pyfuncitem):
|
def pytest_pyfunc_call(pyfuncitem):
|
||||||
do_something_before_next_hook_executes()
|
do_something_before_next_hook_executes()
|
||||||
|
|
||||||
outcome = yield
|
# If the outcome is an exception, will raise the exception.
|
||||||
# outcome.excinfo may be None or a (cls, val, tb) tuple
|
res = yield
|
||||||
|
|
||||||
res = outcome.get_result() # will raise if outcome was exception
|
new_res = post_process_result(res)
|
||||||
|
|
||||||
post_process_result(res)
|
# Override the return value to the plugin system.
|
||||||
|
return new_res
|
||||||
|
|
||||||
outcome.force_result(new_res) # to override the return value to the plugin system
|
The hook wrapper needs to return a result for the hook, or raise an exception.
|
||||||
|
|
||||||
Note that hook wrappers don't return results themselves, they merely
|
In many cases, the wrapper only needs to perform tracing or other side effects
|
||||||
perform tracing or other side effects around the actual hook implementations.
|
around the actual hook implementations, in which case it can return the result
|
||||||
If the result of the underlying hook is a mutable object, they may modify
|
value of the ``yield``. The simplest (though useless) hook wrapper is
|
||||||
that result but it's probably better to avoid it.
|
``return (yield)``.
|
||||||
|
|
||||||
|
In other cases, the wrapper wants the adjust or adapt the result, in which case
|
||||||
|
it can return a new value. If the result of the underlying hook is a mutable
|
||||||
|
object, the wrapper may modify that result, but it's probably better to avoid it.
|
||||||
|
|
||||||
|
If the hook implementation failed with an exception, the wrapper can handle that
|
||||||
|
exception using a ``try-catch-finally`` around the ``yield``, by propagating it,
|
||||||
|
supressing it, or raising a different exception entirely.
|
||||||
|
|
||||||
For more information, consult the
|
For more information, consult the
|
||||||
:ref:`pluggy documentation about hook wrappers <pluggy:hookwrappers>`.
|
:ref:`pluggy documentation about hook wrappers <pluggy:hookwrappers>`.
|
||||||
|
@ -130,11 +137,14 @@ after others, i.e. the position in the ``N``-sized list of functions:
|
||||||
|
|
||||||
|
|
||||||
# Plugin 3
|
# Plugin 3
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_collection_modifyitems(items):
|
def pytest_collection_modifyitems(items):
|
||||||
# will execute even before the tryfirst one above!
|
# will execute even before the tryfirst one above!
|
||||||
outcome = yield
|
try:
|
||||||
# will execute after all non-hookwrappers executed
|
return (yield)
|
||||||
|
finally:
|
||||||
|
# will execute after all non-wrappers executed
|
||||||
|
...
|
||||||
|
|
||||||
Here is the order of execution:
|
Here is the order of execution:
|
||||||
|
|
||||||
|
@ -149,12 +159,11 @@ Here is the order of execution:
|
||||||
Plugin1).
|
Plugin1).
|
||||||
|
|
||||||
4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
|
4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
|
||||||
point. The yield receives a :py:class:`Result <pluggy._Result>` instance which encapsulates
|
point. The yield receives the result from calling the non-wrappers, or raises
|
||||||
the result from calling the non-wrappers. Wrappers shall not modify the result.
|
an exception if the non-wrappers raised.
|
||||||
|
|
||||||
It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
|
It's possible to use ``tryfirst`` and ``trylast`` also on hook wrappers
|
||||||
``hookwrapper=True`` in which case it will influence the ordering of hookwrappers
|
in which case it will influence the ordering of hook wrappers among each other.
|
||||||
among each other.
|
|
||||||
|
|
||||||
|
|
||||||
Declaring new hooks
|
Declaring new hooks
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
pallets-sphinx-themes
|
pallets-sphinx-themes
|
||||||
pluggy>=1.0
|
pluggy>=1.2.0
|
||||||
pygments-pytest>=2.3.0
|
pygments-pytest>=2.3.0
|
||||||
sphinx-removed-in>=0.2.0
|
sphinx-removed-in>=0.2.0
|
||||||
sphinx>=5,<6
|
sphinx>=5,<6
|
||||||
|
|
|
@ -46,7 +46,7 @@ py_modules = py
|
||||||
install_requires =
|
install_requires =
|
||||||
iniconfig
|
iniconfig
|
||||||
packaging
|
packaging
|
||||||
pluggy>=0.12,<2.0
|
pluggy>=1.2.0,<2.0
|
||||||
colorama;sys_platform=="win32"
|
colorama;sys_platform=="win32"
|
||||||
exceptiongroup>=1.0.0rc8;python_version<"3.11"
|
exceptiongroup>=1.0.0rc8;python_version<"3.11"
|
||||||
tomli>=1.0.0;python_version<"3.11"
|
tomli>=1.0.0;python_version<"3.11"
|
||||||
|
|
|
@ -112,8 +112,8 @@ def pytest_collection(session: "Session") -> None:
|
||||||
assertstate.hook.set_session(session)
|
assertstate.hook.set_session(session)
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(tryfirst=True, hookwrapper=True)
|
@hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
|
def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
||||||
"""Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks.
|
"""Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks.
|
||||||
|
|
||||||
The rewrite module will use util._reprcompare if it exists to use custom
|
The rewrite module will use util._reprcompare if it exists to use custom
|
||||||
|
@ -162,8 +162,9 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
|
||||||
|
|
||||||
util._assertion_pass = call_assertion_pass_hook
|
util._assertion_pass = call_assertion_pass_hook
|
||||||
|
|
||||||
yield
|
try:
|
||||||
|
return (yield)
|
||||||
|
finally:
|
||||||
util._reprcompare, util._assertion_pass = saved_assert_hooks
|
util._reprcompare, util._assertion_pass = saved_assert_hooks
|
||||||
util._config = None
|
util._config = None
|
||||||
|
|
||||||
|
|
|
@ -217,12 +217,12 @@ class LFPluginCollWrapper:
|
||||||
self.lfplugin = lfplugin
|
self.lfplugin = lfplugin
|
||||||
self._collected_at_least_one_failure = False
|
self._collected_at_least_one_failure = False
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_make_collect_report(self, collector: nodes.Collector):
|
def pytest_make_collect_report(
|
||||||
|
self, collector: nodes.Collector
|
||||||
|
) -> Generator[None, CollectReport, CollectReport]:
|
||||||
|
res = yield
|
||||||
if isinstance(collector, (Session, Package)):
|
if isinstance(collector, (Session, Package)):
|
||||||
out = yield
|
|
||||||
res: CollectReport = out.get_result()
|
|
||||||
|
|
||||||
# Sort any lf-paths to the beginning.
|
# Sort any lf-paths to the beginning.
|
||||||
lf_paths = self.lfplugin._last_failed_paths
|
lf_paths = self.lfplugin._last_failed_paths
|
||||||
|
|
||||||
|
@ -240,19 +240,16 @@ class LFPluginCollWrapper:
|
||||||
key=sort_key,
|
key=sort_key,
|
||||||
reverse=True,
|
reverse=True,
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
|
||||||
elif isinstance(collector, File):
|
elif isinstance(collector, File):
|
||||||
if collector.path in self.lfplugin._last_failed_paths:
|
if collector.path in self.lfplugin._last_failed_paths:
|
||||||
out = yield
|
|
||||||
res = out.get_result()
|
|
||||||
result = res.result
|
result = res.result
|
||||||
lastfailed = self.lfplugin.lastfailed
|
lastfailed = self.lfplugin.lastfailed
|
||||||
|
|
||||||
# Only filter with known failures.
|
# Only filter with known failures.
|
||||||
if not self._collected_at_least_one_failure:
|
if not self._collected_at_least_one_failure:
|
||||||
if not any(x.nodeid in lastfailed for x in result):
|
if not any(x.nodeid in lastfailed for x in result):
|
||||||
return
|
return res
|
||||||
self.lfplugin.config.pluginmanager.register(
|
self.lfplugin.config.pluginmanager.register(
|
||||||
LFPluginCollSkipfiles(self.lfplugin), "lfplugin-collskip"
|
LFPluginCollSkipfiles(self.lfplugin), "lfplugin-collskip"
|
||||||
)
|
)
|
||||||
|
@ -268,8 +265,8 @@ class LFPluginCollWrapper:
|
||||||
# Keep all sub-collectors.
|
# Keep all sub-collectors.
|
||||||
or isinstance(x, nodes.Collector)
|
or isinstance(x, nodes.Collector)
|
||||||
]
|
]
|
||||||
return
|
|
||||||
yield
|
return res
|
||||||
|
|
||||||
|
|
||||||
class LFPluginCollSkipfiles:
|
class LFPluginCollSkipfiles:
|
||||||
|
@ -342,14 +339,14 @@ class LFPlugin:
|
||||||
else:
|
else:
|
||||||
self.lastfailed[report.nodeid] = True
|
self.lastfailed[report.nodeid] = True
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True, tryfirst=True)
|
@hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_collection_modifyitems(
|
def pytest_collection_modifyitems(
|
||||||
self, config: Config, items: List[nodes.Item]
|
self, config: Config, items: List[nodes.Item]
|
||||||
) -> Generator[None, None, None]:
|
) -> Generator[None, None, None]:
|
||||||
yield
|
res = yield
|
||||||
|
|
||||||
if not self.active:
|
if not self.active:
|
||||||
return
|
return res
|
||||||
|
|
||||||
if self.lastfailed:
|
if self.lastfailed:
|
||||||
previously_failed = []
|
previously_failed = []
|
||||||
|
@ -394,6 +391,8 @@ class LFPlugin:
|
||||||
else:
|
else:
|
||||||
self._report_status += "not deselecting items."
|
self._report_status += "not deselecting items."
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
def pytest_sessionfinish(self, session: Session) -> None:
|
def pytest_sessionfinish(self, session: Session) -> None:
|
||||||
config = self.config
|
config = self.config
|
||||||
if config.getoption("cacheshow") or hasattr(config, "workerinput"):
|
if config.getoption("cacheshow") or hasattr(config, "workerinput"):
|
||||||
|
@ -414,11 +413,11 @@ class NFPlugin:
|
||||||
assert config.cache is not None
|
assert config.cache is not None
|
||||||
self.cached_nodeids = set(config.cache.get("cache/nodeids", []))
|
self.cached_nodeids = set(config.cache.get("cache/nodeids", []))
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True, tryfirst=True)
|
@hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_collection_modifyitems(
|
def pytest_collection_modifyitems(
|
||||||
self, items: List[nodes.Item]
|
self, items: List[nodes.Item]
|
||||||
) -> Generator[None, None, None]:
|
) -> Generator[None, None, None]:
|
||||||
yield
|
res = yield
|
||||||
|
|
||||||
if self.active:
|
if self.active:
|
||||||
new_items: Dict[str, nodes.Item] = {}
|
new_items: Dict[str, nodes.Item] = {}
|
||||||
|
@ -436,6 +435,8 @@ class NFPlugin:
|
||||||
else:
|
else:
|
||||||
self.cached_nodeids.update(item.nodeid for item in items)
|
self.cached_nodeids.update(item.nodeid for item in items)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]:
|
def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]:
|
||||||
return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) # type: ignore[no-any-return]
|
return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ from _pytest.fixtures import SubRequest
|
||||||
from _pytest.nodes import Collector
|
from _pytest.nodes import Collector
|
||||||
from _pytest.nodes import File
|
from _pytest.nodes import File
|
||||||
from _pytest.nodes import Item
|
from _pytest.nodes import Item
|
||||||
|
from _pytest.reports import CollectReport
|
||||||
|
|
||||||
_CaptureMethod = Literal["fd", "sys", "no", "tee-sys"]
|
_CaptureMethod = Literal["fd", "sys", "no", "tee-sys"]
|
||||||
|
|
||||||
|
@ -130,8 +131,8 @@ def _windowsconsoleio_workaround(stream: TextIO) -> None:
|
||||||
sys.stderr = _reopen_stdio(sys.stderr, "wb")
|
sys.stderr = _reopen_stdio(sys.stderr, "wb")
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_load_initial_conftests(early_config: Config):
|
def pytest_load_initial_conftests(early_config: Config) -> Generator[None, None, None]:
|
||||||
ns = early_config.known_args_namespace
|
ns = early_config.known_args_namespace
|
||||||
if ns.capture == "fd":
|
if ns.capture == "fd":
|
||||||
_windowsconsoleio_workaround(sys.stdout)
|
_windowsconsoleio_workaround(sys.stdout)
|
||||||
|
@ -145,12 +146,16 @@ def pytest_load_initial_conftests(early_config: Config):
|
||||||
|
|
||||||
# Finally trigger conftest loading but while capturing (issue #93).
|
# Finally trigger conftest loading but while capturing (issue #93).
|
||||||
capman.start_global_capturing()
|
capman.start_global_capturing()
|
||||||
outcome = yield
|
try:
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
capman.suspend_global_capture()
|
capman.suspend_global_capture()
|
||||||
if outcome.excinfo is not None:
|
except BaseException:
|
||||||
out, err = capman.read_global_capture()
|
out, err = capman.read_global_capture()
|
||||||
sys.stdout.write(out)
|
sys.stdout.write(out)
|
||||||
sys.stderr.write(err)
|
sys.stderr.write(err)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
# IO Helpers.
|
# IO Helpers.
|
||||||
|
@ -847,35 +852,39 @@ class CaptureManager:
|
||||||
|
|
||||||
# Hooks
|
# Hooks
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_make_collect_report(self, collector: Collector):
|
def pytest_make_collect_report(
|
||||||
|
self, collector: Collector
|
||||||
|
) -> Generator[None, CollectReport, CollectReport]:
|
||||||
if isinstance(collector, File):
|
if isinstance(collector, File):
|
||||||
self.resume_global_capture()
|
self.resume_global_capture()
|
||||||
outcome = yield
|
try:
|
||||||
|
rep = yield
|
||||||
|
finally:
|
||||||
self.suspend_global_capture()
|
self.suspend_global_capture()
|
||||||
out, err = self.read_global_capture()
|
out, err = self.read_global_capture()
|
||||||
rep = outcome.get_result()
|
|
||||||
if out:
|
if out:
|
||||||
rep.sections.append(("Captured stdout", out))
|
rep.sections.append(("Captured stdout", out))
|
||||||
if err:
|
if err:
|
||||||
rep.sections.append(("Captured stderr", err))
|
rep.sections.append(("Captured stderr", err))
|
||||||
else:
|
else:
|
||||||
yield
|
rep = yield
|
||||||
|
return rep
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_runtest_setup(self, item: Item) -> Generator[None, None, None]:
|
def pytest_runtest_setup(self, item: Item) -> Generator[None, None, None]:
|
||||||
with self.item_capture("setup", item):
|
with self.item_capture("setup", item):
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]:
|
def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]:
|
||||||
with self.item_capture("call", item):
|
with self.item_capture("call", item):
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_runtest_teardown(self, item: Item) -> Generator[None, None, None]:
|
def pytest_runtest_teardown(self, item: Item) -> Generator[None, None, None]:
|
||||||
with self.item_capture("teardown", item):
|
with self.item_capture("teardown", item):
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
@hookimpl(tryfirst=True)
|
@hookimpl(tryfirst=True)
|
||||||
def pytest_keyboard_interrupt(self) -> None:
|
def pytest_keyboard_interrupt(self) -> None:
|
||||||
|
|
|
@ -1341,11 +1341,13 @@ class Config:
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_collection(self) -> Generator[None, None, None]:
|
def pytest_collection(self) -> Generator[None, object, object]:
|
||||||
# Validate invalid ini keys after collection is done so we take in account
|
# Validate invalid ini keys after collection is done so we take in account
|
||||||
# options added by late-loading conftest files.
|
# options added by late-loading conftest files.
|
||||||
yield
|
try:
|
||||||
|
return (yield)
|
||||||
|
finally:
|
||||||
self._validate_config_options()
|
self._validate_config_options()
|
||||||
|
|
||||||
def _checkversion(self) -> None:
|
def _checkversion(self) -> None:
|
||||||
|
|
|
@ -304,10 +304,10 @@ class PdbInvoke:
|
||||||
|
|
||||||
|
|
||||||
class PdbTrace:
|
class PdbTrace:
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, None, None]:
|
def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, object, object]:
|
||||||
wrap_pytest_function_for_tracing(pyfuncitem)
|
wrap_pytest_function_for_tracing(pyfuncitem)
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
def wrap_pytest_function_for_tracing(pyfuncitem):
|
def wrap_pytest_function_for_tracing(pyfuncitem):
|
||||||
|
|
|
@ -62,8 +62,8 @@ def get_timeout_config_value(config: Config) -> float:
|
||||||
return float(config.getini("faulthandler_timeout") or 0.0)
|
return float(config.getini("faulthandler_timeout") or 0.0)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, trylast=True)
|
@pytest.hookimpl(wrapper=True, trylast=True)
|
||||||
def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
|
def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
||||||
timeout = get_timeout_config_value(item.config)
|
timeout = get_timeout_config_value(item.config)
|
||||||
if timeout > 0:
|
if timeout > 0:
|
||||||
import faulthandler
|
import faulthandler
|
||||||
|
@ -71,11 +71,11 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
|
||||||
stderr = item.config.stash[fault_handler_stderr_fd_key]
|
stderr = item.config.stash[fault_handler_stderr_fd_key]
|
||||||
faulthandler.dump_traceback_later(timeout, file=stderr)
|
faulthandler.dump_traceback_later(timeout, file=stderr)
|
||||||
try:
|
try:
|
||||||
yield
|
return (yield)
|
||||||
finally:
|
finally:
|
||||||
faulthandler.cancel_dump_traceback_later()
|
faulthandler.cancel_dump_traceback_later()
|
||||||
else:
|
else:
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(tryfirst=True)
|
@pytest.hookimpl(tryfirst=True)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from argparse import Action
|
from argparse import Action
|
||||||
|
from typing import Generator
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
@ -97,10 +98,9 @@ def pytest_addoption(parser: Parser) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_cmdline_parse():
|
def pytest_cmdline_parse() -> Generator[None, Config, Config]:
|
||||||
outcome = yield
|
config = yield
|
||||||
config: Config = outcome.get_result()
|
|
||||||
|
|
||||||
if config.option.debug:
|
if config.option.debug:
|
||||||
# --debug | --debug <file.log> was provided.
|
# --debug | --debug <file.log> was provided.
|
||||||
|
@ -128,6 +128,8 @@ def pytest_cmdline_parse():
|
||||||
|
|
||||||
config.add_cleanup(unset_tracing)
|
config.add_cleanup(unset_tracing)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
def showversion(config: Config) -> None:
|
def showversion(config: Config) -> None:
|
||||||
if config.option.version > 1:
|
if config.option.version > 1:
|
||||||
|
|
|
@ -60,7 +60,7 @@ def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None:
|
||||||
:param pytest.PytestPluginManager pluginmanager: The pytest plugin manager.
|
:param pytest.PytestPluginManager pluginmanager: The pytest plugin manager.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
This hook is incompatible with ``hookwrapper=True``.
|
This hook is incompatible with hook wrappers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ def pytest_plugin_registered(
|
||||||
:param pytest.PytestPluginManager manager: pytest plugin manager.
|
:param pytest.PytestPluginManager manager: pytest plugin manager.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
This hook is incompatible with ``hookwrapper=True``.
|
This hook is incompatible with hook wrappers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") ->
|
||||||
attribute or can be retrieved as the ``pytestconfig`` fixture.
|
attribute or can be retrieved as the ``pytestconfig`` fixture.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
This hook is incompatible with ``hookwrapper=True``.
|
This hook is incompatible with hook wrappers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ def pytest_configure(config: "Config") -> None:
|
||||||
imported.
|
imported.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
This hook is incompatible with ``hookwrapper=True``.
|
This hook is incompatible with hook wrappers.
|
||||||
|
|
||||||
:param pytest.Config config: The pytest config object.
|
:param pytest.Config config: The pytest config object.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -738,27 +738,26 @@ class LoggingPlugin:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True, tryfirst=True)
|
@hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_sessionstart(self) -> Generator[None, None, None]:
|
def pytest_sessionstart(self) -> Generator[None, None, None]:
|
||||||
self.log_cli_handler.set_when("sessionstart")
|
self.log_cli_handler.set_when("sessionstart")
|
||||||
|
|
||||||
with catching_logs(self.log_cli_handler, level=self.log_cli_level):
|
with catching_logs(self.log_cli_handler, level=self.log_cli_level):
|
||||||
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True, tryfirst=True)
|
@hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_collection(self) -> Generator[None, None, None]:
|
def pytest_collection(self) -> Generator[None, None, None]:
|
||||||
self.log_cli_handler.set_when("collection")
|
self.log_cli_handler.set_when("collection")
|
||||||
|
|
||||||
with catching_logs(self.log_cli_handler, level=self.log_cli_level):
|
with catching_logs(self.log_cli_handler, level=self.log_cli_level):
|
||||||
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_runtestloop(self, session: Session) -> Generator[None, None, None]:
|
def pytest_runtestloop(self, session: Session) -> Generator[None, object, object]:
|
||||||
if session.config.option.collectonly:
|
if session.config.option.collectonly:
|
||||||
yield
|
return (yield)
|
||||||
return
|
|
||||||
|
|
||||||
if self._log_cli_enabled() and self._config.getoption("verbose") < 1:
|
if self._log_cli_enabled() and self._config.getoption("verbose") < 1:
|
||||||
# The verbose flag is needed to avoid messy test progress output.
|
# The verbose flag is needed to avoid messy test progress output.
|
||||||
|
@ -766,7 +765,7 @@ class LoggingPlugin:
|
||||||
|
|
||||||
with catching_logs(self.log_cli_handler, level=self.log_cli_level):
|
with catching_logs(self.log_cli_handler, level=self.log_cli_level):
|
||||||
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||||
yield # Run all the tests.
|
return (yield) # Run all the tests.
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def pytest_runtest_logstart(self) -> None:
|
def pytest_runtest_logstart(self) -> None:
|
||||||
|
@ -791,12 +790,13 @@ class LoggingPlugin:
|
||||||
item.stash[caplog_records_key][when] = caplog_handler.records
|
item.stash[caplog_records_key][when] = caplog_handler.records
|
||||||
item.stash[caplog_handler_key] = caplog_handler
|
item.stash[caplog_handler_key] = caplog_handler
|
||||||
|
|
||||||
|
try:
|
||||||
yield
|
yield
|
||||||
|
finally:
|
||||||
log = report_handler.stream.getvalue().strip()
|
log = report_handler.stream.getvalue().strip()
|
||||||
item.add_report_section(when, "log", log)
|
item.add_report_section(when, "log", log)
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]:
|
def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]:
|
||||||
self.log_cli_handler.set_when("setup")
|
self.log_cli_handler.set_when("setup")
|
||||||
|
|
||||||
|
@ -804,17 +804,19 @@ class LoggingPlugin:
|
||||||
item.stash[caplog_records_key] = empty
|
item.stash[caplog_records_key] = empty
|
||||||
yield from self._runtest_for(item, "setup")
|
yield from self._runtest_for(item, "setup")
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_runtest_call(self, item: nodes.Item) -> Generator[None, None, None]:
|
def pytest_runtest_call(self, item: nodes.Item) -> Generator[None, None, None]:
|
||||||
self.log_cli_handler.set_when("call")
|
self.log_cli_handler.set_when("call")
|
||||||
|
|
||||||
yield from self._runtest_for(item, "call")
|
yield from self._runtest_for(item, "call")
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None, None, None]:
|
def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None, None, None]:
|
||||||
self.log_cli_handler.set_when("teardown")
|
self.log_cli_handler.set_when("teardown")
|
||||||
|
|
||||||
|
try:
|
||||||
yield from self._runtest_for(item, "teardown")
|
yield from self._runtest_for(item, "teardown")
|
||||||
|
finally:
|
||||||
del item.stash[caplog_records_key]
|
del item.stash[caplog_records_key]
|
||||||
del item.stash[caplog_handler_key]
|
del item.stash[caplog_handler_key]
|
||||||
|
|
||||||
|
@ -822,13 +824,13 @@ class LoggingPlugin:
|
||||||
def pytest_runtest_logfinish(self) -> None:
|
def pytest_runtest_logfinish(self) -> None:
|
||||||
self.log_cli_handler.set_when("finish")
|
self.log_cli_handler.set_when("finish")
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True, tryfirst=True)
|
@hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_sessionfinish(self) -> Generator[None, None, None]:
|
def pytest_sessionfinish(self) -> Generator[None, None, None]:
|
||||||
self.log_cli_handler.set_when("sessionfinish")
|
self.log_cli_handler.set_when("sessionfinish")
|
||||||
|
|
||||||
with catching_logs(self.log_cli_handler, level=self.log_cli_level):
|
with catching_logs(self.log_cli_handler, level=self.log_cli_level):
|
||||||
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def pytest_unconfigure(self) -> None:
|
def pytest_unconfigure(self) -> None:
|
||||||
|
|
|
@ -160,10 +160,12 @@ class LsofFdLeakChecker:
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True, tryfirst=True)
|
@hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]:
|
def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object]:
|
||||||
lines1 = self.get_open_files()
|
lines1 = self.get_open_files()
|
||||||
yield
|
try:
|
||||||
|
return (yield)
|
||||||
|
finally:
|
||||||
if hasattr(sys, "pypy_version_info"):
|
if hasattr(sys, "pypy_version_info"):
|
||||||
gc.collect()
|
gc.collect()
|
||||||
lines2 = self.get_open_files()
|
lines2 = self.get_open_files()
|
||||||
|
|
|
@ -249,6 +249,9 @@ class TestReport(BaseReport):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__test__ = False
|
__test__ = False
|
||||||
|
# Defined by skipping plugin.
|
||||||
|
# xfail reason if xfailed, otherwise not defined. Use hasattr to distinguish.
|
||||||
|
wasxfail: str
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -28,11 +28,13 @@ def pytest_addoption(parser: Parser) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_fixture_setup(
|
def pytest_fixture_setup(
|
||||||
fixturedef: FixtureDef[object], request: SubRequest
|
fixturedef: FixtureDef[object], request: SubRequest
|
||||||
) -> Generator[None, None, None]:
|
) -> Generator[None, object, object]:
|
||||||
yield
|
try:
|
||||||
|
return (yield)
|
||||||
|
finally:
|
||||||
if request.config.option.setupshow:
|
if request.config.option.setupshow:
|
||||||
if hasattr(request, "param"):
|
if hasattr(request, "param"):
|
||||||
# Save the fixture parameter so ._show_fixture_action() can
|
# Save the fixture parameter so ._show_fixture_action() can
|
||||||
|
|
|
@ -19,6 +19,7 @@ from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import skip
|
from _pytest.outcomes import skip
|
||||||
from _pytest.outcomes import xfail
|
from _pytest.outcomes import xfail
|
||||||
from _pytest.reports import BaseReport
|
from _pytest.reports import BaseReport
|
||||||
|
from _pytest.reports import TestReport
|
||||||
from _pytest.runner import CallInfo
|
from _pytest.runner import CallInfo
|
||||||
from _pytest.stash import StashKey
|
from _pytest.stash import StashKey
|
||||||
|
|
||||||
|
@ -243,7 +244,7 @@ def pytest_runtest_setup(item: Item) -> None:
|
||||||
xfail("[NOTRUN] " + xfailed.reason)
|
xfail("[NOTRUN] " + xfailed.reason)
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_runtest_call(item: Item) -> Generator[None, None, None]:
|
def pytest_runtest_call(item: Item) -> Generator[None, None, None]:
|
||||||
xfailed = item.stash.get(xfailed_key, None)
|
xfailed = item.stash.get(xfailed_key, None)
|
||||||
if xfailed is None:
|
if xfailed is None:
|
||||||
|
@ -252,18 +253,20 @@ def pytest_runtest_call(item: Item) -> Generator[None, None, None]:
|
||||||
if xfailed and not item.config.option.runxfail and not xfailed.run:
|
if xfailed and not item.config.option.runxfail and not xfailed.run:
|
||||||
xfail("[NOTRUN] " + xfailed.reason)
|
xfail("[NOTRUN] " + xfailed.reason)
|
||||||
|
|
||||||
yield
|
try:
|
||||||
|
return (yield)
|
||||||
|
finally:
|
||||||
# The test run may have added an xfail mark dynamically.
|
# The test run may have added an xfail mark dynamically.
|
||||||
xfailed = item.stash.get(xfailed_key, None)
|
xfailed = item.stash.get(xfailed_key, None)
|
||||||
if xfailed is None:
|
if xfailed is None:
|
||||||
item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item)
|
item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item)
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_runtest_makereport(item: Item, call: CallInfo[None]):
|
def pytest_runtest_makereport(
|
||||||
outcome = yield
|
item: Item, call: CallInfo[None]
|
||||||
rep = outcome.get_result()
|
) -> Generator[None, TestReport, TestReport]:
|
||||||
|
rep = yield
|
||||||
xfailed = item.stash.get(xfailed_key, None)
|
xfailed = item.stash.get(xfailed_key, None)
|
||||||
if item.config.option.runxfail:
|
if item.config.option.runxfail:
|
||||||
pass # don't interfere
|
pass # don't interfere
|
||||||
|
@ -286,6 +289,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]):
|
||||||
else:
|
else:
|
||||||
rep.outcome = "passed"
|
rep.outcome = "passed"
|
||||||
rep.wasxfail = xfailed.reason
|
rep.wasxfail = xfailed.reason
|
||||||
|
return rep
|
||||||
|
|
||||||
|
|
||||||
def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]:
|
def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]:
|
||||||
|
|
|
@ -15,7 +15,6 @@ from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import cast
|
|
||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import final
|
from typing import final
|
||||||
|
@ -849,12 +848,11 @@ class TerminalReporter:
|
||||||
for line in doc.splitlines():
|
for line in doc.splitlines():
|
||||||
self._tw.line("{}{}".format(indent + " ", line))
|
self._tw.line("{}{}".format(indent + " ", line))
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_sessionfinish(
|
def pytest_sessionfinish(
|
||||||
self, session: "Session", exitstatus: Union[int, ExitCode]
|
self, session: "Session", exitstatus: Union[int, ExitCode]
|
||||||
):
|
) -> Generator[None, None, None]:
|
||||||
outcome = yield
|
result = yield
|
||||||
outcome.get_result()
|
|
||||||
self._tw.line("")
|
self._tw.line("")
|
||||||
summary_exit_codes = (
|
summary_exit_codes = (
|
||||||
ExitCode.OK,
|
ExitCode.OK,
|
||||||
|
@ -875,14 +873,17 @@ class TerminalReporter:
|
||||||
elif session.shouldstop:
|
elif session.shouldstop:
|
||||||
self.write_sep("!", str(session.shouldstop), red=True)
|
self.write_sep("!", str(session.shouldstop), red=True)
|
||||||
self.summary_stats()
|
self.summary_stats()
|
||||||
|
return result
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_terminal_summary(self) -> Generator[None, None, None]:
|
def pytest_terminal_summary(self) -> Generator[None, None, None]:
|
||||||
self.summary_errors()
|
self.summary_errors()
|
||||||
self.summary_failures()
|
self.summary_failures()
|
||||||
self.summary_warnings()
|
self.summary_warnings()
|
||||||
self.summary_passes()
|
self.summary_passes()
|
||||||
yield
|
try:
|
||||||
|
return (yield)
|
||||||
|
finally:
|
||||||
self.short_test_summary()
|
self.short_test_summary()
|
||||||
# Display any extra warnings from teardown here (if any).
|
# Display any extra warnings from teardown here (if any).
|
||||||
self.summary_warnings()
|
self.summary_warnings()
|
||||||
|
@ -1466,7 +1467,7 @@ def _get_raw_skip_reason(report: TestReport) -> str:
|
||||||
The string is just the part given by the user.
|
The string is just the part given by the user.
|
||||||
"""
|
"""
|
||||||
if hasattr(report, "wasxfail"):
|
if hasattr(report, "wasxfail"):
|
||||||
reason = cast(str, report.wasxfail)
|
reason = report.wasxfail
|
||||||
if reason.startswith("reason: "):
|
if reason.startswith("reason: "):
|
||||||
reason = reason[len("reason: ") :]
|
reason = reason[len("reason: ") :]
|
||||||
return reason
|
return reason
|
||||||
|
|
|
@ -59,9 +59,13 @@ class catch_threading_exception:
|
||||||
|
|
||||||
def thread_exception_runtest_hook() -> Generator[None, None, None]:
|
def thread_exception_runtest_hook() -> Generator[None, None, None]:
|
||||||
with catch_threading_exception() as cm:
|
with catch_threading_exception() as cm:
|
||||||
|
try:
|
||||||
yield
|
yield
|
||||||
|
finally:
|
||||||
if cm.args:
|
if cm.args:
|
||||||
thread_name = "<unknown>" if cm.args.thread is None else cm.args.thread.name
|
thread_name = (
|
||||||
|
"<unknown>" if cm.args.thread is None else cm.args.thread.name
|
||||||
|
)
|
||||||
msg = f"Exception in thread {thread_name}\n\n"
|
msg = f"Exception in thread {thread_name}\n\n"
|
||||||
msg += "".join(
|
msg += "".join(
|
||||||
traceback.format_exception(
|
traceback.format_exception(
|
||||||
|
@ -73,16 +77,16 @@ def thread_exception_runtest_hook() -> Generator[None, None, None]:
|
||||||
warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg))
|
warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg))
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, trylast=True)
|
@pytest.hookimpl(wrapper=True, trylast=True)
|
||||||
def pytest_runtest_setup() -> Generator[None, None, None]:
|
def pytest_runtest_setup() -> Generator[None, None, None]:
|
||||||
yield from thread_exception_runtest_hook()
|
yield from thread_exception_runtest_hook()
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_call() -> Generator[None, None, None]:
|
def pytest_runtest_call() -> Generator[None, None, None]:
|
||||||
yield from thread_exception_runtest_hook()
|
yield from thread_exception_runtest_hook()
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_teardown() -> Generator[None, None, None]:
|
def pytest_runtest_teardown() -> Generator[None, None, None]:
|
||||||
yield from thread_exception_runtest_hook()
|
yield from thread_exception_runtest_hook()
|
||||||
|
|
|
@ -28,7 +28,7 @@ from _pytest.fixtures import fixture
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
from _pytest.monkeypatch import MonkeyPatch
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
from _pytest.nodes import Item
|
from _pytest.nodes import Item
|
||||||
from _pytest.reports import CollectReport
|
from _pytest.reports import TestReport
|
||||||
from _pytest.stash import StashKey
|
from _pytest.stash import StashKey
|
||||||
|
|
||||||
tmppath_result_key = StashKey[Dict[str, bool]]()
|
tmppath_result_key = StashKey[Dict[str, bool]]()
|
||||||
|
@ -309,10 +309,12 @@ def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]):
|
||||||
cleanup_dead_symlinks(basetemp)
|
cleanup_dead_symlinks(basetemp)
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(tryfirst=True, hookwrapper=True)
|
@hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_makereport(item: Item, call):
|
def pytest_runtest_makereport(
|
||||||
outcome = yield
|
item: Item, call
|
||||||
result: CollectReport = outcome.get_result()
|
) -> Generator[None, TestReport, TestReport]:
|
||||||
|
rep = yield
|
||||||
|
assert rep.when is not None
|
||||||
empty: Dict[str, bool] = {}
|
empty: Dict[str, bool] = {}
|
||||||
item.stash.setdefault(tmppath_result_key, empty)[result.when] = result.passed
|
item.stash.setdefault(tmppath_result_key, empty)[rep.when] = rep.passed
|
||||||
|
return rep
|
||||||
|
|
|
@ -376,8 +376,8 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None:
|
||||||
# Twisted trial support.
|
# Twisted trial support.
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(wrapper=True)
|
||||||
def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
|
def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
||||||
if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules:
|
if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules:
|
||||||
ut: Any = sys.modules["twisted.python.failure"]
|
ut: Any = sys.modules["twisted.python.failure"]
|
||||||
Failure__init__ = ut.Failure.__init__
|
Failure__init__ = ut.Failure.__init__
|
||||||
|
@ -400,10 +400,13 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
|
||||||
Failure__init__(self, exc_value, exc_type, exc_tb)
|
Failure__init__(self, exc_value, exc_type, exc_tb)
|
||||||
|
|
||||||
ut.Failure.__init__ = excstore
|
ut.Failure.__init__ = excstore
|
||||||
yield
|
try:
|
||||||
|
res = yield
|
||||||
|
finally:
|
||||||
ut.Failure.__init__ = Failure__init__
|
ut.Failure.__init__ = Failure__init__
|
||||||
else:
|
else:
|
||||||
yield
|
res = yield
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
def check_testcase_implements_trial_reporter(done: List[int] = []) -> None:
|
def check_testcase_implements_trial_reporter(done: List[int] = []) -> None:
|
||||||
|
|
|
@ -61,7 +61,9 @@ class catch_unraisable_exception:
|
||||||
|
|
||||||
def unraisable_exception_runtest_hook() -> Generator[None, None, None]:
|
def unraisable_exception_runtest_hook() -> Generator[None, None, None]:
|
||||||
with catch_unraisable_exception() as cm:
|
with catch_unraisable_exception() as cm:
|
||||||
|
try:
|
||||||
yield
|
yield
|
||||||
|
finally:
|
||||||
if cm.unraisable:
|
if cm.unraisable:
|
||||||
if cm.unraisable.err_msg is not None:
|
if cm.unraisable.err_msg is not None:
|
||||||
err_msg = cm.unraisable.err_msg
|
err_msg = cm.unraisable.err_msg
|
||||||
|
@ -78,16 +80,16 @@ def unraisable_exception_runtest_hook() -> Generator[None, None, None]:
|
||||||
warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
|
warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_setup() -> Generator[None, None, None]:
|
def pytest_runtest_setup() -> Generator[None, None, None]:
|
||||||
yield from unraisable_exception_runtest_hook()
|
yield from unraisable_exception_runtest_hook()
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_call() -> Generator[None, None, None]:
|
def pytest_runtest_call() -> Generator[None, None, None]:
|
||||||
yield from unraisable_exception_runtest_hook()
|
yield from unraisable_exception_runtest_hook()
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_teardown() -> Generator[None, None, None]:
|
def pytest_runtest_teardown() -> Generator[None, None, None]:
|
||||||
yield from unraisable_exception_runtest_hook()
|
yield from unraisable_exception_runtest_hook()
|
||||||
|
|
|
@ -60,8 +60,9 @@ def catch_warnings_for_item(
|
||||||
for arg in mark.args:
|
for arg in mark.args:
|
||||||
warnings.filterwarnings(*parse_warning_filter(arg, escape=False))
|
warnings.filterwarnings(*parse_warning_filter(arg, escape=False))
|
||||||
|
|
||||||
|
try:
|
||||||
yield
|
yield
|
||||||
|
finally:
|
||||||
for warning_message in log:
|
for warning_message in log:
|
||||||
ihook.pytest_warning_recorded.call_historic(
|
ihook.pytest_warning_recorded.call_historic(
|
||||||
kwargs=dict(
|
kwargs=dict(
|
||||||
|
@ -103,24 +104,24 @@ def warning_record_to_str(warning_message: warnings.WarningMessage) -> str:
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
|
def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
||||||
with catch_warnings_for_item(
|
with catch_warnings_for_item(
|
||||||
config=item.config, ihook=item.ihook, when="runtest", item=item
|
config=item.config, ihook=item.ihook, when="runtest", item=item
|
||||||
):
|
):
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_collection(session: Session) -> Generator[None, None, None]:
|
def pytest_collection(session: Session) -> Generator[None, object, object]:
|
||||||
config = session.config
|
config = session.config
|
||||||
with catch_warnings_for_item(
|
with catch_warnings_for_item(
|
||||||
config=config, ihook=config.hook, when="collect", item=None
|
config=config, ihook=config.hook, when="collect", item=None
|
||||||
):
|
):
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_terminal_summary(
|
def pytest_terminal_summary(
|
||||||
terminalreporter: TerminalReporter,
|
terminalreporter: TerminalReporter,
|
||||||
) -> Generator[None, None, None]:
|
) -> Generator[None, None, None]:
|
||||||
|
@ -128,23 +129,23 @@ def pytest_terminal_summary(
|
||||||
with catch_warnings_for_item(
|
with catch_warnings_for_item(
|
||||||
config=config, ihook=config.hook, when="config", item=None
|
config=config, ihook=config.hook, when="config", item=None
|
||||||
):
|
):
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_sessionfinish(session: Session) -> Generator[None, None, None]:
|
def pytest_sessionfinish(session: Session) -> Generator[None, None, None]:
|
||||||
config = session.config
|
config = session.config
|
||||||
with catch_warnings_for_item(
|
with catch_warnings_for_item(
|
||||||
config=config, ihook=config.hook, when="config", item=None
|
config=config, ihook=config.hook, when="config", item=None
|
||||||
):
|
):
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_load_initial_conftests(
|
def pytest_load_initial_conftests(
|
||||||
early_config: "Config",
|
early_config: "Config",
|
||||||
) -> Generator[None, None, None]:
|
) -> Generator[None, None, None]:
|
||||||
with catch_warnings_for_item(
|
with catch_warnings_for_item(
|
||||||
config=early_config, ihook=early_config.hook, when="config", item=None
|
config=early_config, ihook=early_config.hook, when="config", item=None
|
||||||
):
|
):
|
||||||
yield
|
return (yield)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Generator
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -21,8 +22,8 @@ if sys.gettrace():
|
||||||
sys.settrace(orig_trace)
|
sys.settrace(orig_trace)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_collection_modifyitems(items):
|
def pytest_collection_modifyitems(items) -> Generator[None, None, None]:
|
||||||
"""Prefer faster tests.
|
"""Prefer faster tests.
|
||||||
|
|
||||||
Use a hook wrapper to do this in the beginning, so e.g. --ff still works
|
Use a hook wrapper to do this in the beginning, so e.g. --ff still works
|
||||||
|
@ -62,7 +63,7 @@ def pytest_collection_modifyitems(items):
|
||||||
|
|
||||||
items[:] = fast_items + neutral_items + slow_items + slowest_items
|
items[:] = fast_items + neutral_items + slow_items + slowest_items
|
||||||
|
|
||||||
yield
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
|
@ -1040,13 +1040,13 @@ def test_log_set_path(pytester: Pytester) -> None:
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_runtest_setup(item):
|
def pytest_runtest_setup(item):
|
||||||
config = item.config
|
config = item.config
|
||||||
logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
|
logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
|
||||||
report_file = os.path.join({}, item._request.node.name)
|
report_file = os.path.join({}, item._request.node.name)
|
||||||
logging_plugin.set_log_path(report_file)
|
logging_plugin.set_log_path(report_file)
|
||||||
yield
|
return (yield)
|
||||||
""".format(
|
""".format(
|
||||||
repr(report_dir_base)
|
repr(report_dir_base)
|
||||||
)
|
)
|
||||||
|
|
|
@ -827,11 +827,11 @@ class TestConftestCustomization:
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""\
|
"""\
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_pycollect_makemodule():
|
def pytest_pycollect_makemodule():
|
||||||
outcome = yield
|
mod = yield
|
||||||
mod = outcome.get_result()
|
|
||||||
mod.obj.hello = "world"
|
mod.obj.hello = "world"
|
||||||
|
return mod
|
||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
|
@ -855,14 +855,13 @@ class TestConftestCustomization:
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""\
|
"""\
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_pycollect_makeitem():
|
def pytest_pycollect_makeitem():
|
||||||
outcome = yield
|
result = yield
|
||||||
if outcome.excinfo is None:
|
|
||||||
result = outcome.get_result()
|
|
||||||
if result:
|
if result:
|
||||||
for func in result:
|
for func in result:
|
||||||
func._some123 = "world"
|
func._some123 = "world"
|
||||||
|
return result
|
||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
|
|
|
@ -334,12 +334,11 @@ class TestPrunetraceback:
|
||||||
pytester.makeconftest(
|
pytester.makeconftest(
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_make_collect_report():
|
def pytest_make_collect_report():
|
||||||
outcome = yield
|
rep = yield
|
||||||
rep = outcome.get_result()
|
|
||||||
rep.headerlines += ["header1"]
|
rep.headerlines += ["header1"]
|
||||||
outcome.force_result(rep)
|
return rep
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = pytester.runpytest(p)
|
result = pytester.runpytest(p)
|
||||||
|
|
|
@ -1317,7 +1317,7 @@ def test_load_initial_conftest_last_ordering(_config_for_test):
|
||||||
hookimpls = [
|
hookimpls = [
|
||||||
(
|
(
|
||||||
hookimpl.function.__module__,
|
hookimpl.function.__module__,
|
||||||
"wrapper" if hookimpl.hookwrapper else "nonwrapper",
|
"wrapper" if (hookimpl.wrapper or hookimpl.hookwrapper) else "nonwrapper",
|
||||||
)
|
)
|
||||||
for hookimpl in hc.get_hookimpls()
|
for hookimpl in hc.get_hookimpls()
|
||||||
]
|
]
|
||||||
|
|
|
@ -806,12 +806,12 @@ class TestKeywordSelection:
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
conftest="""
|
conftest="""
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_pycollect_makeitem(name):
|
def pytest_pycollect_makeitem(name):
|
||||||
outcome = yield
|
item = yield
|
||||||
if name == "TestClass":
|
if name == "TestClass":
|
||||||
item = outcome.get_result()
|
|
||||||
item.extra_keyword_matches.add("xxx")
|
item.extra_keyword_matches.add("xxx")
|
||||||
|
return item
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
reprec = pytester.inline_run(p.parent, "-s", "-k", keyword)
|
reprec = pytester.inline_run(p.parent, "-s", "-k", keyword)
|
||||||
|
|
|
@ -94,11 +94,13 @@ def test_clean_up(pytester: Pytester) -> None:
|
||||||
after: Optional[List[str]] = None
|
after: Optional[List[str]] = None
|
||||||
|
|
||||||
class Plugin:
|
class Plugin:
|
||||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_unconfigure(self) -> Generator[None, None, None]:
|
def pytest_unconfigure(self) -> Generator[None, None, None]:
|
||||||
nonlocal before, after
|
nonlocal before, after
|
||||||
before = sys.path.copy()
|
before = sys.path.copy()
|
||||||
yield
|
try:
|
||||||
|
return (yield)
|
||||||
|
finally:
|
||||||
after = sys.path.copy()
|
after = sys.path.copy()
|
||||||
|
|
||||||
result = pytester.runpytest_inprocess(plugins=[Plugin()])
|
result = pytester.runpytest_inprocess(plugins=[Plugin()])
|
||||||
|
|
|
@ -542,10 +542,10 @@ def test_runtest_in_module_ordering(pytester: Pytester) -> None:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mylist(self, request):
|
def mylist(self, request):
|
||||||
return request.function.mylist
|
return request.function.mylist
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_runtest_call(self, item):
|
def pytest_runtest_call(self, item):
|
||||||
try:
|
try:
|
||||||
(yield).get_result()
|
yield
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
def test_hello1(self, mylist):
|
def test_hello1(self, mylist):
|
||||||
|
@ -826,12 +826,12 @@ def test_unicode_in_longrepr(pytester: Pytester) -> None:
|
||||||
pytester.makeconftest(
|
pytester.makeconftest(
|
||||||
"""\
|
"""\
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_runtest_makereport():
|
def pytest_runtest_makereport():
|
||||||
outcome = yield
|
rep = yield
|
||||||
rep = outcome.get_result()
|
|
||||||
if rep.when == "call":
|
if rep.when == "call":
|
||||||
rep.longrepr = 'ä'
|
rep.longrepr = 'ä'
|
||||||
|
return rep
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
|
|
|
@ -725,12 +725,12 @@ class TestTerminalFunctional:
|
||||||
)
|
)
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
def test_deselected_with_hookwrapper(self, pytester: Pytester) -> None:
|
def test_deselected_with_hook_wrapper(self, pytester: Pytester) -> None:
|
||||||
pytester.makeconftest(
|
pytester.makeconftest(
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_collection_modifyitems(config, items):
|
def pytest_collection_modifyitems(config, items):
|
||||||
yield
|
yield
|
||||||
deselected = items.pop()
|
deselected = items.pop()
|
||||||
|
|
|
@ -952,7 +952,7 @@ def test_issue333_result_clearing(pytester: Pytester) -> None:
|
||||||
pytester.makeconftest(
|
pytester.makeconftest(
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_runtest_call(item):
|
def pytest_runtest_call(item):
|
||||||
yield
|
yield
|
||||||
assert 0
|
assert 0
|
||||||
|
|
Loading…
Reference in New Issue