Skip to content

Commit 89f2a9d

Browse files
committed
Avoid possible infinite recursion when writing pyc files in assert rewrite
What happens is that atomic_write on Python 2.7 on Windows will try to convert the paths to unicode, but this triggers the import of the encoding module for the file system codec, which in turn triggers the rewrite, which in turn again tries to import the module, and so on. This short-circuits the cases where we try to import another file when writing a pyc file; I don't expect this to affect anything because the only modules that could be affected are those imported by atomic_writes. Fix pytest-dev#3506
1 parent 9620b16 commit 89f2a9d

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(): pass")
1137+
testdir.makepyfile(test_bar="def test(): 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)