diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 6bc7657c5..13d2484bb 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -711,6 +711,7 @@ Session related reporting hooks: .. autofunction:: pytest_fixture_setup .. autofunction:: pytest_fixture_post_finalizer .. autofunction:: pytest_warning_captured +.. autofunction:: pytest_warning_record Central hook for reporting about test execution: diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index b4fab332d..c983f87f9 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -622,6 +622,33 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config): @hookspec(historic=True) def pytest_warning_captured(warning_message, when, item, location): + """(**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_record` instead. + + :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 pytest.Item|None item: + The item being executed if ``when`` is ``"runtest"``, otherwise ``None``. + + :param tuple location: + Holds information about the execution context of the captured warning (filename, linenumber, function). + ``function`` evaluates to when the execution context is at the module level. + """ + + +@hookspec(historic=True) +def pytest_warning_record(warning_message, when, nodeid, location): """ Process a warning captured by the internal pytest warnings plugin. @@ -636,11 +663,7 @@ def pytest_warning_captured(warning_message, when, item, location): * ``"collect"``: during test collection. * ``"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 str nodeid: full id of the item :param tuple location: Holds information about the execution context of the captured warning (filename, linenumber, function). diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 8ecb5a16b..2524fb21f 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -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_record``. :ivar str message: user friendly message about the warning :ivar str|None nodeid: node id that generated the warning (see ``get_location``). @@ -412,13 +412,14 @@ class TerminalReporter: return 1 def pytest_warning_captured(self, warning_message, item): - # from _pytest.nodes import get_fslocation_from_item + pass + + def pytest_warning_record(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 "" warning_report = WarningReport( fslocation=fslocation, message=message, nodeid=nodeid ) diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 527bb03b0..16d8de8f8 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -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_record`` hook. """ cmdline_filters = config.getoption("pythonwarnings") or [] inifilters = config.getini("filterwarnings") @@ -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 if item is not None: for mark in item.iter_markers(name="filterwarnings"): for arg in mark.args: @@ -110,8 +111,9 @@ 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) + # raise ValueError(ihook.pytest_warning_record) + ihook.pytest_warning_record.call_historic( + kwargs=dict(warning_message=warning_message, nodeid=nodeid, when=when) ) @@ -166,8 +168,9 @@ 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 - hook so we can display these warnings in the terminal. This is a hack until we can sort out #2891. + at this point the actual options might not have been set, so we manually trigger the pytest_warning_record hooks + so we can display these warnings in the terminal. + This is a hack until we can sort out #2891. :param warning: the warning instance. :param hook: the hook caller @@ -180,8 +183,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_record.call_historic( kwargs=dict( - warning_message=records[0], when="config", item=None, location=location + warning_message=records[0], when="config", nodeid="", location=location ) ) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 51d1286b4..ec75e6571 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -268,9 +268,8 @@ def test_warning_captured_hook(testdir): 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_record(self, warning_message, when, nodeid): + collected.append((str(warning_message.message), when, nodeid)) result = testdir.runpytest(plugins=[WarningCollector()]) result.stdout.fnmatch_lines(["*1 passed*"]) @@ -278,11 +277,11 @@ def test_warning_captured_hook(testdir): 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") @@ -649,7 +648,7 @@ class TestStackLevel: captured = [] @classmethod - def pytest_warning_captured(cls, warning_message, when, item, location): + def pytest_warning_record(cls, warning_message, when, nodeid, location): cls.captured.append((warning_message, location)) testdir.plugins = [CapturedWarnings()]