Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new hook pytest_warning_recorded #7255

Merged
merged 7 commits into from May 31, 2020
1 change: 1 addition & 0 deletions doc/en/reference.rst
Expand Up @@ -711,6 +711,7 @@ Session related reporting hooks:
.. autofunction:: pytest_fixture_setup
.. autofunction:: pytest_fixture_post_finalizer
.. autofunction:: pytest_warning_captured
.. autofunction:: pytest_warning_recorded

Central hook for reporting about test execution:

Expand Down
33 changes: 28 additions & 5 deletions src/_pytest/hookspec.py
Expand Up @@ -622,8 +622,10 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):

@hookspec(historic=True)
def pytest_warning_captured(warning_message, when, item, location):
gnikonorov marked this conversation as resolved.
Show resolved Hide resolved
"""
Process a warning captured by the internal pytest warnings plugin.
"""(**Deprecated**) Process a warning captured by the internal pytest warnings plugin.

This hook is considered deprecated and will be removed in a future pytest version.
Use :func:`pytest_warning_recorded` instead.

:param warnings.WarningMessage warning_message:
The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
Expand All @@ -637,9 +639,6 @@ def pytest_warning_captured(warning_message, when, item, location):
* ``"runtest"``: during test execution.

:param pytest.Item|None item:
**DEPRECATED**: This parameter is incompatible with ``pytest-xdist``, and will always receive ``None``
in a future release.

The item being executed if ``when`` is ``"runtest"``, otherwise ``None``.

:param tuple location:
Expand All @@ -648,6 +647,30 @@ def pytest_warning_captured(warning_message, when, item, location):
"""


@hookspec(historic=True)
def pytest_warning_recorded(warning_message, when, nodeid, location):
gnikonorov marked this conversation as resolved.
Show resolved Hide resolved
"""
Process a warning captured by the internal pytest warnings plugin.

:param warnings.WarningMessage warning_message:
The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
the same attributes as the parameters of :py:func:`warnings.showwarning`.

:param str when:
Indicates when the warning was captured. Possible values:

* ``"config"``: during pytest configuration/initialization stage.
* ``"collect"``: during test collection.
* ``"runtest"``: during test execution.

:param str nodeid: full id of the item

:param tuple location:
Holds information about the execution context of the captured warning (filename, linenumber, function).
``function`` evaluates to <module> when the execution context is at the module level.
"""


# -------------------------------------------------------------------------
# doctest hooks
# -------------------------------------------------------------------------
Expand Down
7 changes: 4 additions & 3 deletions src/_pytest/terminal.py
Expand Up @@ -227,7 +227,7 @@ def pytest_report_teststatus(report: TestReport) -> Tuple[str, str, str]:
@attr.s
class WarningReport:
"""
Simple structure to hold warnings information captured by ``pytest_warning_captured``.
Simple structure to hold warnings information captured by ``pytest_warning_recorded``.

:ivar str message: user friendly message about the warning
:ivar str|None nodeid: node id that generated the warning (see ``get_location``).
Expand Down Expand Up @@ -412,13 +412,14 @@ def pytest_internalerror(self, excrepr):
return 1

def pytest_warning_captured(self, warning_message, item):
gnikonorov marked this conversation as resolved.
Show resolved Hide resolved
# from _pytest.nodes import get_fslocation_from_item
pass

def pytest_warning_recorded(self, warning_message, nodeid):
from _pytest.warnings import warning_record_to_str

fslocation = warning_message.filename, warning_message.lineno
message = warning_record_to_str(warning_message)

nodeid = item.nodeid if item is not None else ""
gnikonorov marked this conversation as resolved.
Show resolved Hide resolved
warning_report = WarningReport(
fslocation=fslocation, message=message, nodeid=nodeid
)
Expand Down
13 changes: 7 additions & 6 deletions src/_pytest/warnings.py
Expand Up @@ -81,7 +81,7 @@ def catch_warnings_for_item(config, ihook, when, item):

``item`` can be None if we are not in the context of an item execution.

Each warning captured triggers the ``pytest_warning_captured`` hook.
Each warning captured triggers the ``pytest_warning_recorded`` hook.
"""
cmdline_filters = config.getoption("pythonwarnings") or []
inifilters = config.getini("filterwarnings")
Expand All @@ -102,6 +102,7 @@ def catch_warnings_for_item(config, ihook, when, item):
for arg in cmdline_filters:
warnings.filterwarnings(*_parse_filter(arg, escape=True))

nodeid = "" if item is None else item.nodeid
gnikonorov marked this conversation as resolved.
Show resolved Hide resolved
if item is not None:
for mark in item.iter_markers(name="filterwarnings"):
for arg in mark.args:
Expand All @@ -110,8 +111,8 @@ def catch_warnings_for_item(config, ihook, when, item):
yield

for warning_message in log:
ihook.pytest_warning_captured.call_historic(
kwargs=dict(warning_message=warning_message, when=when, item=item)
ihook.pytest_warning_recorded.call_historic(
gnikonorov marked this conversation as resolved.
Show resolved Hide resolved
kwargs=dict(warning_message=warning_message, nodeid=nodeid, when=when)
gnikonorov marked this conversation as resolved.
Show resolved Hide resolved
)


Expand Down Expand Up @@ -166,7 +167,7 @@ def pytest_sessionfinish(session):
def _issue_warning_captured(warning, hook, stacklevel):
"""
This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage:
at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured
at this point the actual options might not have been set, so we manually trigger the pytest_warning_recorded
hook so we can display these warnings in the terminal. This is a hack until we can sort out #2891.

:param warning: the warning instance.
Expand All @@ -180,8 +181,8 @@ def _issue_warning_captured(warning, hook, stacklevel):
assert records is not None
frame = sys._getframe(stacklevel - 1)
location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
hook.pytest_warning_captured.call_historic(
hook.pytest_warning_recorded.call_historic(
gnikonorov marked this conversation as resolved.
Show resolved Hide resolved
kwargs=dict(
warning_message=records[0], when="config", item=None, location=location
warning_message=records[0], when="config", nodeid="", location=location
gnikonorov marked this conversation as resolved.
Show resolved Hide resolved
)
)
15 changes: 7 additions & 8 deletions testing/test_warnings.py
Expand Up @@ -268,21 +268,20 @@ def test_func(fix):
collected = []

class WarningCollector:
def pytest_warning_captured(self, warning_message, when, item):
imge_name = item.name if item is not None else ""
collected.append((str(warning_message.message), when, imge_name))
def pytest_warning_recorded(self, warning_message, when, nodeid):
collected.append((str(warning_message.message), when, nodeid))

result = testdir.runpytest(plugins=[WarningCollector()])
result.stdout.fnmatch_lines(["*1 passed*"])

expected = [
("config warning", "config", ""),
("collect warning", "collect", ""),
("setup warning", "runtest", "test_func"),
("call warning", "runtest", "test_func"),
("teardown warning", "runtest", "test_func"),
("setup warning", "runtest", "test_warning_captured_hook.py::test_func"),
("call warning", "runtest", "test_warning_captured_hook.py::test_func"),
("teardown warning", "runtest", "test_warning_captured_hook.py::test_func"),
]
assert collected == expected
assert collected == expected, str(collected)


@pytest.mark.filterwarnings("always")
Expand Down Expand Up @@ -649,7 +648,7 @@ class CapturedWarnings:
captured = []

@classmethod
def pytest_warning_captured(cls, warning_message, when, item, location):
def pytest_warning_recorded(cls, warning_message, when, nodeid, location):
cls.captured.append((warning_message, location))

testdir.plugins = [CapturedWarnings()]
Expand Down