Skip to content

Commit 269551e

Browse files
Initial fix for issue 14112-pytest crashing with ptest-xdist
1 parent 6e43e68 commit 269551e

File tree

2 files changed

+73
-36
lines changed

2 files changed

+73
-36
lines changed

src/_pytest/assertion/rewrite.py

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -371,42 +371,46 @@ def _read_pyc(
371371
fp = open(pyc, "rb")
372372
except OSError:
373373
return None
374-
with fp:
375-
try:
376-
stat_result = os.stat(source)
377-
mtime = int(stat_result.st_mtime)
378-
size = stat_result.st_size
379-
data = fp.read(16)
380-
except OSError as e:
381-
trace(f"_read_pyc({source}): OSError {e}")
382-
return None
383-
# Check for invalid or out of date pyc file.
384-
if len(data) != (16):
385-
trace(f"_read_pyc({source}): invalid pyc (too short)")
386-
return None
387-
if data[:4] != importlib.util.MAGIC_NUMBER:
388-
trace(f"_read_pyc({source}): invalid pyc (bad magic number)")
389-
return None
390-
if data[4:8] != b"\x00\x00\x00\x00":
391-
trace(f"_read_pyc({source}): invalid pyc (unsupported flags)")
392-
return None
393-
mtime_data = data[8:12]
394-
if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF:
395-
trace(f"_read_pyc({source}): out of date")
396-
return None
397-
size_data = data[12:16]
398-
if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF:
399-
trace(f"_read_pyc({source}): invalid pyc (incorrect size)")
400-
return None
401-
try:
402-
co = marshal.load(fp)
403-
except Exception as e:
404-
trace(f"_read_pyc({source}): marshal.load error {e}")
405-
return None
406-
if not isinstance(co, types.CodeType):
407-
trace(f"_read_pyc({source}): not a code object")
408-
return None
409-
return co
374+
try:
375+
with fp:
376+
try:
377+
stat_result = os.stat(source)
378+
mtime = int(stat_result.st_mtime)
379+
size = stat_result.st_size
380+
data = fp.read(16)
381+
except OSError as e:
382+
trace(f"_read_pyc({source}): OSError {e}")
383+
return None
384+
# Check for invalid or out of date pyc file.
385+
if len(data) != (16):
386+
trace(f"_read_pyc({source}): invalid pyc (too short)")
387+
return None
388+
if data[:4] != importlib.util.MAGIC_NUMBER:
389+
trace(f"_read_pyc({source}): invalid pyc (bad magic number)")
390+
return None
391+
if data[4:8] != b"\x00\x00\x00\x00":
392+
trace(f"_read_pyc({source}): invalid pyc (unsupported flags)")
393+
return None
394+
mtime_data = data[8:12]
395+
if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF:
396+
trace(f"_read_pyc({source}): out of date")
397+
return None
398+
size_data = data[12:16]
399+
if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF:
400+
trace(f"_read_pyc({source}): invalid pyc (incorrect size)")
401+
return None
402+
try:
403+
co = marshal.load(fp)
404+
except Exception as e:
405+
trace(f"_read_pyc({source}): marshal.load error {e}")
406+
return None
407+
if not isinstance(co, types.CodeType):
408+
trace(f"_read_pyc({source}): not a code object")
409+
return None
410+
return co
411+
except OSError as e:
412+
trace(f"_read_pyc({source}): OSError {e}")
413+
return None
410414

411415

412416
def rewrite_asserts(

testing/test_assertrewrite.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,6 +1362,39 @@ def test_read_pyc(self, tmp_path: Path) -> None:
13621362

13631363
assert _read_pyc(source, pyc) is None # no error
13641364

1365+
def test_read_pyc_handles_context_manager_oserror(self, tmp_path: Path) -> None:
1366+
from _pytest.assertion.rewrite import _read_pyc
1367+
1368+
source = tmp_path / "source.py"
1369+
pyc = Path(str(source) + "c")
1370+
source.write_text("def test(): pass", encoding="utf-8")
1371+
py_compile.compile(str(source), str(pyc))
1372+
1373+
real_open = open
1374+
1375+
class FailingContextManager:
1376+
def __init__(self, fp) -> None:
1377+
self.fp = fp
1378+
1379+
def __enter__(self):
1380+
return self.fp
1381+
1382+
def __exit__(self, exc_type, exc, tb) -> None:
1383+
self.fp.close()
1384+
raise OSError(errno.EIO, "Input/output error")
1385+
1386+
def __getattr__(self, name):
1387+
return getattr(self.fp, name)
1388+
1389+
def mock_open(file, mode="r", *args, **kwargs):
1390+
fp = real_open(file, mode, *args, **kwargs)
1391+
if Path(file) == pyc and mode == "rb":
1392+
return FailingContextManager(fp)
1393+
return fp
1394+
1395+
with mock.patch("builtins.open", mock_open):
1396+
assert _read_pyc(source, pyc) is None
1397+
13651398
def test_read_pyc_success(self, tmp_path: Path, pytester: Pytester) -> None:
13661399
"""
13671400
Ensure that the _rewrite_test() -> _write_pyc() produces a pyc file

0 commit comments

Comments
 (0)