Skip to content

Commit 10c1c7c

Browse files
authored
Merge pull request #3895 from nicoddemus/issue-3506
Avoid possible infinite recursion when writing pyc files in assert rewrite
2 parents 16f452e + 82a7ca9 commit 10c1c7c

File tree

3 files changed

+40
-1
lines changed

3 files changed

+40
-1
lines changed

changelog/3506.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix possible infinite recursion when writing ``.pyc`` files.

src/_pytest/assertion/rewrite.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,16 @@ def __init__(self, config):
6464
self._rewritten_names = set()
6565
self._register_with_pkg_resources()
6666
self._must_rewrite = set()
67+
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
68+
# which might result in infinite recursion (#3506)
69+
self._writing_pyc = False
6770

6871
def set_session(self, session):
6972
self.session = session
7073

7174
def find_module(self, name, path=None):
75+
if self._writing_pyc:
76+
return None
7277
state = self.config._assertstate
7378
state.trace("find_module called for: %s" % name)
7479
names = name.rsplit(".", 1)
@@ -151,7 +156,11 @@ def find_module(self, name, path=None):
151156
# Probably a SyntaxError in the test.
152157
return None
153158
if write:
154-
_write_pyc(state, co, source_stat, pyc)
159+
self._writing_pyc = True
160+
try:
161+
_write_pyc(state, co, source_stat, pyc)
162+
finally:
163+
self._writing_pyc = False
155164
else:
156165
state.trace("found cached rewritten pyc for %r" % (fn,))
157166
self.modules[name] = co, pyc

testing/test_assertrewrite.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,3 +1124,32 @@ def test_simple_failure():
11241124

11251125
result = testdir.runpytest()
11261126
result.stdout.fnmatch_lines("*E*assert (1 + 1) == 3")
1127+
1128+
1129+
def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch):
1130+
"""Fix infinite recursion when writing pyc files: if an import happens to be triggered when writing the pyc
1131+
file, this would cause another call to the hook, which would trigger another pyc writing, which could
1132+
trigger another import, and so on. (#3506)"""
1133+
from _pytest.assertion import rewrite
1134+
1135+
testdir.syspathinsert()
1136+
testdir.makepyfile(test_foo="def test_foo(): pass")
1137+
testdir.makepyfile(test_bar="def test_bar(): pass")
1138+
1139+
original_write_pyc = rewrite._write_pyc
1140+
1141+
write_pyc_called = []
1142+
1143+
def spy_write_pyc(*args, **kwargs):
1144+
# make a note that we have called _write_pyc
1145+
write_pyc_called.append(True)
1146+
# try to import a module at this point: we should not try to rewrite this module
1147+
assert hook.find_module("test_bar") is None
1148+
return original_write_pyc(*args, **kwargs)
1149+
1150+
monkeypatch.setattr(rewrite, "_write_pyc", spy_write_pyc)
1151+
monkeypatch.setattr(sys, "dont_write_bytecode", False)
1152+
1153+
hook = AssertionRewritingHook(pytestconfig)
1154+
assert hook.find_module("test_foo") is not None
1155+
assert len(write_pyc_called) == 1

0 commit comments

Comments
 (0)