diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 99815b70cf1..169ab6d7028 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -195,6 +195,12 @@ def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool: tries to filter what we're sure won't be rewritten before getting to it. """ + # stdlib modules are never rewritten; bail out early to avoid calling + # fnmatch_ex, which can trigger lazy import resolution and cause + # recursion with PYTHON_LAZY_IMPORTS=all (#14632). + if name.partition(".")[0] in sys.stdlib_module_names: + return True + if self.session is not None and not self._session_paths_checked: self._session_paths_checked = True for initial_path in self.session._initialpaths: diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index e11863547ba..023ef4322cb 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -2424,3 +2424,35 @@ def test(): ) reprec = pytester.inline_run("-p", "no:terminalreporter") reprec.assertoutcome(passed=1) + + +def test_rewrite_hook_stdlib_modules_skipped(pytestconfig: pytest.Config) -> None: + """AssertionRewritingHook.find_spec returns None for stdlib modules early + (via sys.stdlib_module_names in _early_rewrite_bailout) to prevent + recursion with PYTHON_LAZY_IMPORTS=all (#14632).""" + hook = AssertionRewritingHook(pytestconfig) + # stdlib modules are always skipped; this also breaks the recursion that + # PYTHON_LAZY_IMPORTS=all would cause when fnmatch resolves lazily inside + # _early_rewrite_bailout. + assert hook.find_spec("fnmatch") is None + assert hook.find_spec("os") is None + assert hook.find_spec("re") is None + + +@pytest.mark.skipif( + sys.version_info < (3, 15), + reason="PYTHON_LAZY_IMPORTS requires Python 3.15+", +) +def test_lazy_imports_all_does_not_crash_pytest( + pytester: Pytester, monkeypatch: pytest.MonkeyPatch +) -> None: + """pytest does not crash with PYTHON_LAZY_IMPORTS=all (#14632).""" + pytester.makepyfile( + """ + def test_foo(): + assert 1 == 1 + """ + ) + monkeypatch.setenv("PYTHON_LAZY_IMPORTS", "all") + result = pytester.runpytest_subprocess() + assert result.ret == 0