Warn when a collected test module was imported too early, and fix three cases where it is - #15022
Draft
RonnyPfannschmidt wants to merge 5 commits into
Draft
RonnyPfannschmidt wants to merge 5 commits into
RonnyPfannschmidt wants to merge 5 commits into
Conversation
RonnyPfannschmidt
marked this pull request as draft
September 14, 2026 09:33
_early_rewrite_bailout seeds _basenames_to_check_rewrite from the initial paths using the file stem, so `pytest pkg/__init__.py` contributed `__init__`. The module is imported under the name `pkg`, whose last part never matched, so the hook bailed out before _should_rewrite could recognise the file as an initial path and rewrite it. Seed the containing directory name as well when the initial path is an __init__.py. Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
…tlib _import_module_using_spec asks sys.meta_path for the module using the module file's parent directory as the search path. For a package that is the package directory itself, so PathFinder looked for `sub` inside `sub/` and found nothing; the loop fell through to spec_from_file_location, which picks SourceFileLoader and bypasses the assertion-rewrite hook. Search the directory containing the package instead when the module path is an __init__.py. Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
search_pypath passes the argument straight to importlib.util.find_spec. For `--pyargs t.py` importlib imports everything up to the last dot as a package, so `t` -- the test module itself -- lands in sys.modules before collection starts. The lookup then fails and pytest falls back to treating the argument as a path, but the module is now imported, and an imported module can no longer be assertion-rewritten. A name ending in `.py` is a filename, not a module name, so skip the sys.path search for it entirely. Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
…arly Rewriting only happens on import, so anything that imports a test module before collection reaches it -- typically a top-level `import` in a conftest -- silently costs that module its assertion introspection. The existing "Module already imported so cannot be rewritten" warning only covers modules passed to register_assert_rewrite, not this case. After importing a test module, check whether it came from the rewrite hook's loader. If it did not, but the hook would have rewritten it, warn with PytestAssertRewriteWarning and name both remedies. Re-asking _should_rewrite is what keeps this quiet: without it the check fires for every package __init__.py and every --doctest-modules target, neither of which is meant to be rewritten. Closes pytest-dev#1930 Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
The "Assertion introspection details" section said only that pytest rewrites the modules it discovers, without saying that an import which beats collection to the module takes that away. The conftest example further up does exactly the top-level import that bit the reporter of issue pytest-dev#1929, so note why it is safe there and what makes it unsafe elsewhere. Co-Authored-By: Claude Opus 5 (1M context) via Claude Code <noreply@anthropic.com>
RonnyPfannschmidt
force-pushed
the
fix-1930-early-import-rewrite
branch
from
September 15, 2026 11:34
532e7b8 to
bb6fdf4
Compare
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.
Closes #1930.
What the agent found
#1930 asked for a warning when assertion rewriting silently doesn't happen. Chasing
it turned up that the warning, once written, fires in three places on
mainwhererewriting is genuinely missing today — so the fixes come first and the warning last.
The original #1929 repro still reproduces, in the shape the gist actually had it:
It works if the module is named
test_*.py(_should_rewritematchespython_filesregardless of who imports first), which is why #1930 looked fixed in 2018.
The commits
pytest pkg/__init__.pyis never rewritten._early_rewrite_bailoutseeds_basenames_to_check_rewritefrom each initial path's file stem, so an__init__.pycontributes__init__while the module is imported aspkg. Thebailout fires and
_should_rewrite— which would have said yes viaisinitpath—is never reached.
--import-mode=importlibnever rewrites a package's__init__.py._import_module_using_specaskssys.meta_pathusingmodule_path.parent; forpkg/sub/__init__.pythat ispkg/sub, soPathFinderhunts forsubinsidesub/and finds nothing. The loop falls through tospec_from_file_location,which picks
SourceFileLoaderand bypasses the hook. Same tree underprependgives
assert 1 == 2; underimportlib, bareAssertionError. Distinct from--import-mode=importlib breaks assertion rewriting #12044.
--pyargs t.pyimports the test module while resolving it.search_pypathhands the argument to
importlib.util.find_spec, which imports everything up tothe last dot as a package — for
t.pythat importst. The lookup then fails andpytest falls back to the path, but the module is in
sys.modulesand can nolonger be rewritten.
The warning warn when tests raise plain assertion errors without annotation #1930 asked for. After importing a test module, check whether it
came from the rewrite hook's loader; if not, but
_should_rewritesays it shouldhave, emit
PytestAssertRewriteWarningnaming both remedies.Re-asking
_should_rewriteis what makes this usable. Without that gate the checkfired ~150 times across pytest's own suite — every package
__init__.py, every--doctest-modulestarget — for 87 failures. With it: 8 warnings, 5 failures,and all five were the three bugs above. With those fixed the suite is clean and
the warning is silent.
Docs.
assert.rstsaid pytest rewrites the modules it discovers, withoutsaying that an earlier import takes that away. The
pytest_assertrepr_compareexample there does the same top-level conftest import that bit the no output from pytest_assertrepr_compare in conftest #1929 reporter.
Notes for review
1930.*; the three bugfixes have no issuenumbers of their own. Happy to open separate issues and rename if you'd rather.
warn_if_not_rewrittenreaches forhook._should_rewrite. It lives in_pytest/assertion/__init__.py, next to the hook, so it stays inside the package —but say the word if you'd rather that became a public method on the hook.
--pyargsfix keys off a.pysuffix. That matches the existing"looks like package module, but actually filename" comment and the documented
contract (
--pyargstakes module names), at the cost of no longer resolving ahypothetical real module named
t.py.🤖 Generated with Claude Code