recwarn: pass originating module name when re-emitting unmatched warnings (fixes #11933)#14585
Open
mokashang wants to merge 2 commits into
Open
recwarn: pass originating module name when re-emitting unmatched warnings (fixes #11933)#14585mokashang wants to merge 2 commits into
mokashang wants to merge 2 commits into
Conversation
…ings
The re-emit loop in WarningsChecker.__exit__ passed
module=w.__module__ to warnings.warn_explicit, but w is a
warnings.WarningMessage instance, so w.__module__ is always
the string "warnings" (the module that WarningMessage is defined
in). As a result, a user-installed filter such as
warnings.filterwarnings("ignore", module=r"my_module") never matches
the re-emitted warning, even though it matches the original.
Recover the originating module's importable name by looking it up in
sys.modules by filename. Fall back to None when no module
matches, which lets warn_explicit derive the name from the filename
itself (matching its own default behavior).
Add a regression test using a packaged module so the importable name
(regr_pkg.inner) differs from the filename-derived default. The
test's module-anchored regex matches only the correct importable name
and fails for both the original buggy value ("warnings") and the
filename-derived fallback, ensuring the test pins the fix down rather
than the looser invariant.
Fixes pytest-dev#11933
The regression test for pytest-dev#11933 writes a small helper module via Path.write_text without specifying an encoding. pytest's own CI matrix runs with PYTHONWARNDEFAULTENCODING=1 (set in tox.ini for all environments except pylib), and the project's filterwarnings config treats every warning as an error. The unencoded write_text triggers EncodingWarning, failing the test on every Python 3.10+ job that respects that env var (all ubuntu-py311+, macos, windows). Matches the convention already used elsewhere in testing/ (see test_assertion.py, test_assertrewrite.py, etc.).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
pytest.warnsre-emits unmatched warnings on__exit__viawarnings.warn_explicit(..., module=w.__module__, ...). Butwis awarnings.WarningMessageinstance, sow.__module__evaluates to"warnings"(the module that theWarningMessageclass is defined in) forevery re-emitted warning, regardless of where the warning actually came from.
The visible consequence: a user-installed filter such as
matches the original warning fine, but silently fails to match its re-emit.
Code that should be quiet around
pytest.warns(...)blocks gets noisy(or, with
"error"filters, raises unexpectedly).This is #11933, reported by @Zac-HD with a precise diagnosis:
module=w.__module__should be the importable module name of the call site, not the home module of
WarningMessage.Approach
WarningMessagedoes not preserve themoduleargument originally passed towarn_explicit, so we can't read it offw. The next best thing is to look upsys.modulesby__file__to find the module whose source file matchesw.filename, and fall back toNonewhen nothing matches.warn_explicit'sdocumented behavior with
module=Noneis to derive a name fromfilenameitself, which matches what plain
warnings.warn(...)would have produced ifcalled from outside any
pytest.warnscontext — so the fallback is a cleandegenerate, not a regression.
The fix is contained to two spots in
src/_pytest/recwarn.py: a helper_module_name_for_filename, and replacing themodule=w.__module__argumentin the re-emit call.
Why this PR re-opens ground covered by #12898
The earlier attempt (#12898, by @reaganjlee, closed for inactivity) landed
on essentially the same shape of fix but was closed because the regression test
@nicoddemus asked for was inadequate — specifically, the test passed even when
module=was set toNoneinstead of the recovered name, so it pinned thewrong invariant.
This PR addresses that directly. The new test
test_re_emit_preserves_module_nameuses a packaged module(
regr_pkg/inner.py) so the importable name (regr_pkg.inner) differs fromwarn_explicit's filename-derived default (<...>/regr_pkg/inner). Thefilter regex
^regr_pkg\.inner$matches only the importable form andnothing else. Concretely, the test:
"regr_pkg.inner"),"warnings"doesn't match),module=None(filename-derived"<...>/regr_pkg/inner"doesn't match).So the test pins down "recover the correct module name", not the looser
"don't pass
\"warnings\"".Verification
testing/test_recwarn.py— 67 passed (66 existing + 1 new)testing/test_recwarn.py testing/test_warnings.py— 119 passed, 4 skippedtesting/suite (excludingtest_recwarn.py, kept separate above) —3974 passed, 124 skipped, 12 xfailed, 1 xpassed; no failures introduced.
ruff check,ruff format --check,mypyon the touched files — all clean.I also confirmed end-to-end by reverting the fix locally and re-running the
new test: it fails as designed. With the
module=None"looser" fix it alsofails, which is what nicoddemus wanted from a regression test.
Fixes #11933.