diff --git a/changelog/11933.bugfix.rst b/changelog/11933.bugfix.rst new file mode 100644 index 0000000000..e9cbcce6a1 --- /dev/null +++ b/changelog/11933.bugfix.rst @@ -0,0 +1 @@ +Fix regression with :func:`pytest.warns` using wrong module when re-emitting unmatched warnings. diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 0dc002edd9..54db462e92 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -5,6 +5,7 @@ from pprint import pformat import re +import sys from types import TracebackType from typing import Any from typing import Callable @@ -327,12 +328,20 @@ def found_str() -> str: # Whether or not any warnings matched, we want to re-emit all unmatched warnings. for w in self: if not self.matches(w): + module = next( + ( + k + for k, v in sys.modules.items() + if getattr(v, "__file__", None) == w.filename + ), + None, + ) warnings.warn_explicit( message=w.message, category=w.category, filename=w.filename, lineno=w.lineno, - module=w.__module__, + module=module, source=w.source, ) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 384f2b66a1..7f0fed32e8 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -545,6 +545,38 @@ def test_it(): result.assert_outcomes() +def test_re_emit_uses_correct_module(pytester: Pytester) -> None: + warning_module_code = """ +import warnings + +def trigger_warning(msg): + warnings.warn(msg, UserWarning) +""" + pytester.makepyfile(module_a=warning_module_code) + pytester.makepyfile(module_b=warning_module_code) + + test_code = """ + import pytest + import warnings + from module_a import trigger_warning as trigger_warning_a + from module_b import trigger_warning as trigger_warning_b + + def test_ignore_warning_from_module_a(): + with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"): + with pytest.warns(UserWarning, match="module A.") as outer: + warnings.filterwarnings("ignore", category=UserWarning, module="module_a") + with pytest.warns(UserWarning, match="module B.") as inner: # re-emit the module A warning + trigger_warning_a("module A.") + trigger_warning_b("module B.") + """ + # Write the test to a new file called 'test_re_emit.py' + pytester.makepyfile(test_re_emit=test_code) + + # Run the test and assert that it passed + result = pytester.runpytest() + result.assert_outcomes(passed=1) + + def test_raise_type_error_on_invalid_warning() -> None: """Check pytest.warns validates warning messages are strings (#10865) or Warning instances (#11959)."""