Skip to content

Commit 5940a4a

Browse files
committed
fix: replace co_filename with the correct filename when the file is moved
1 parent 4e80019 commit 5940a4a

3 files changed

Lines changed: 46 additions & 0 deletions

File tree

changelog/14552.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed assertion-rewrite cache behavior for moved test files: when a rewritten ``.pyc`` was reused after renaming or moving a test module/directory, nested code objects could keep a stale ``co_filename`` from the old path. This caused ``inspect.currentframe().f_code.co_filename`` (and related traceback/reporting paths) to point to the previous location instead of the current file path.
2+

src/_pytest/assertion/rewrite.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,28 @@ def _read_pyc(
397397
if not isinstance(co, types.CodeType):
398398
trace(f"_read_pyc({source}): not a code object")
399399
return None
400+
# A cached pyc can be moved together with the source file (for example
401+
# by renaming a package or test directory). In that case, the marshaled
402+
# code object's ``co_filename`` still points to the old source path,
403+
# which leaks stale filenames in tracebacks and ``inspect``.
404+
source_str = str(source)
405+
if co.co_filename != source_str:
406+
co = _replace_code_filenames(co, source_str)
400407
return co
401408

402409

410+
def _replace_code_filenames(co: types.CodeType, filename: str) -> types.CodeType:
411+
return co.replace(
412+
co_filename=filename,
413+
co_consts=tuple(
414+
_replace_code_filenames(const, filename)
415+
if isinstance(const, types.CodeType)
416+
else const
417+
for const in co.co_consts
418+
),
419+
)
420+
421+
403422
def rewrite_asserts(
404423
mod: ast.Module,
405424
source: bytes,

testing/test_assertrewrite.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,31 @@ def test_foo():
11381138
glob.glob("__pycache__/*.pyc")
11391139
)
11401140

1141+
def test_moved_test_file_updates_code_filename(
1142+
self, pytester: Pytester, monkeypatch: pytest.MonkeyPatch
1143+
) -> None:
1144+
"""Moving a test module must keep ``co_filename`` synchronized with ``__file__``."""
1145+
monkeypatch.delenv("PYTHONPYCACHEPREFIX", raising=False)
1146+
1147+
pytester.makepyfile(
1148+
**{
1149+
"test1/test_a.py": """
1150+
from inspect import currentframe
1151+
1152+
def test_a():
1153+
assert currentframe().f_code.co_filename == __file__
1154+
"""
1155+
}
1156+
)
1157+
1158+
first = pytester.runpytest_subprocess("-s", "test1/test_a.py")
1159+
first.assert_outcomes(passed=1)
1160+
1161+
pytester.path.joinpath("test1").rename(pytester.path.joinpath("test2"))
1162+
1163+
second = pytester.runpytest_subprocess("-s", "test2/test_a.py")
1164+
second.assert_outcomes(passed=1)
1165+
11411166
@pytest.mark.skipif('"__pypy__" in sys.modules')
11421167
def test_pyc_vs_pyo(
11431168
self,

0 commit comments

Comments
 (0)