From ffd47ceefcda22ef178e14c2f90698417a75fe33 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 25 Aug 2018 20:41:16 -0300 Subject: [PATCH] Implement new pytest_warning_captured hook --- src/_pytest/hookspec.py | 19 ++++++++++++ src/_pytest/warnings.py | 62 ++++++++++++++++++++-------------------- testing/test_warnings.py | 19 ++++++++++++ 3 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index e2969110a..246a59d59 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -535,6 +535,25 @@ def pytest_logwarning(message, code, nodeid, fslocation): """ +@hookspec(historic=True) +def pytest_warning_captured(warning, when, item): + """ + Process a warning captured by the internal pytest plugin. + + :param warnings.WarningMessage warning: + The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains + the same attributes as :py:func:`warnings.showwarning`. + + :param str when: + Indicates when the warning was captured. Possible values: + * ``"collect"``: during test collection. + * ``"runtest"``: during test execution. + + :param pytest.Item|None item: + The item being executed if ``when == "runtest"``, else ``None``. + """ + + # ------------------------------------------------------------------------- # doctest hooks # ------------------------------------------------------------------------- diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 3a93f92f3..7600840ea 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -58,7 +58,7 @@ def pytest_configure(config): @contextmanager -def catch_warnings_for_item(item): +def deprecated_catch_warnings_for_item(item): """ catches the warnings generated during setup/call/teardown execution of the given item and after it is done posts them as warnings to this @@ -80,40 +80,40 @@ def catch_warnings_for_item(item): yield for warning in log: - warn_msg = warning.message - unicode_warning = False - - if compat._PY2 and any( - isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args - ): - new_args = [] - for m in warn_msg.args: - new_args.append( - compat.ascii_escaped(m) - if isinstance(m, compat.UNICODE_TYPES) - else m - ) - unicode_warning = list(warn_msg.args) != new_args - warn_msg.args = new_args - - msg = warnings.formatwarning( - warn_msg, - warning.category, - warning.filename, - warning.lineno, - warning.line, + item.ihook.pytest_warning_captured.call_historic( + kwargs=dict(warning=warning, when="runtest", item=item) ) - item.warn("unused", msg) + deprecated_emit_warning(item, warning) - if unicode_warning: - warnings.warn( - "Warning is using unicode non convertible to ascii, " - "converting to a safe representation:\n %s" % msg, - UnicodeWarning, - ) + +def deprecated_emit_warning(item, warning): + """ + Emits the deprecated ``pytest_logwarning`` for the given warning and item. + """ + warn_msg = warning.message + unicode_warning = False + if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args): + new_args = [] + for m in warn_msg.args: + new_args.append( + compat.ascii_escaped(m) if isinstance(m, compat.UNICODE_TYPES) else m + ) + unicode_warning = list(warn_msg.args) != new_args + warn_msg.args = new_args + + msg = warnings.formatwarning( + warn_msg, warning.category, warning.filename, warning.lineno, warning.line + ) + item.warn("unused", msg) + if unicode_warning: + warnings.warn( + "Warning is using unicode non convertible to ascii, " + "converting to a safe representation:\n %s" % msg, + UnicodeWarning, + ) @pytest.hookimpl(hookwrapper=True) def pytest_runtest_protocol(item): - with catch_warnings_for_item(item): + with deprecated_catch_warnings_for_item(item): yield diff --git a/testing/test_warnings.py b/testing/test_warnings.py index a26fb4597..d0e5bbe4b 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -302,3 +302,22 @@ def test_filterwarnings_mark_registration(testdir): ) result = testdir.runpytest("--strict") assert result.ret == 0 + + +@pytest.mark.filterwarnings("always") +def test_warning_captured_hook(testdir, pyfile_with_warnings): + + collected = [] + + class WarningCollector: + def pytest_warning_captured(self, warning, when, item): + collected.append((warning.category, when, item.name)) + + result = testdir.runpytest(plugins=[WarningCollector()]) + result.stdout.fnmatch_lines(["*1 passed*"]) + + expected = [ + (UserWarning, "runtest", "test_func"), + (RuntimeWarning, "runtest", "test_func"), + ] + assert collected == expected