Skip to content

Commit 43eb352

Browse files
authored
Fix the stubgen test flake. (#4811)
Fix the stubgen test flake. The core problem is that `importlib.invalidate_caches()` is full of lies and does not actually invalidate all of the relevant caches. Python maintains sys.path_importer_cache, which maps from directory names on the sys.path to "importer" objects for that path entry. `importlib.invalidate_caches()` invalidates the caches inside each of the per-directory importer objects, but there is an additional negative cache that is *not* cleared: sys.path entries whose directory doesn't exist have their importer set to None, which prevents that path entry from ever being searched unless it is manually cleared. This failure comes up rarely, because it only occurs if the following events occur in sequence: 1. 'stubgen-test-path' is added to sys.path, but no new import is done while there is a 'stubgen-test-path' subdirectory in the working directory. This is done by all the stubgen tests that don't end with `_import`. 2. Some non-stubgen test does an import of a previously unimported module. This will cause sys.path_importer_cache['stubgen-test-path'] to be set to None when the directory doesn't exist during the module search. This can happen the *first* time that a `_python2` test is run since `parse.parse()` dynamically imports `mypy.fastparse2` when asked to parse python 2. 3. A stubgen test tries to use importlib to import a module in 'stubgen-test-path'. All of the `_import` tests do this. We fix this by clearing out the relevant entry from `sys.path_importer_cache` ourselves. Fixes #4635
1 parent 751b4ef commit 43eb352

File tree

2 files changed

+11
-39
lines changed

2 files changed

+11
-39
lines changed

mypy/stubgen.py

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,6 @@ def find_module_path_and_all(module: str, pyversion: Tuple[int, int],
153153
try:
154154
mod = importlib.import_module(module)
155155
except Exception:
156-
# Print some debugging output that might help diagnose problems.
157-
print('=== debug dump follows ===')
158-
traceback.print_exc()
159-
dump_sys_path()
160-
print('PYTHONPATH: %s' % os.getenv("PYTHONPATH"))
161-
dump_dir(os.getcwd())
162-
print('=== end of debug dump ===')
163156
raise CantImport(module)
164157
if is_c_module(mod):
165158
return None
@@ -175,31 +168,6 @@ def find_module_path_and_all(module: str, pyversion: Tuple[int, int],
175168
return module_path, module_all
176169

177170

178-
def dump_sys_path() -> None:
179-
print('sys.path:')
180-
for entry in sys.path:
181-
print(' %r' % entry)
182-
if entry in sys.path_importer_cache:
183-
x = sys.path_importer_cache[entry]
184-
print(' path_importer: %r / %r' % (x, getattr(x, '__dict__', None)))
185-
try:
186-
print(' stat: %s ' % str(os.stat(entry)))
187-
except Exception as e:
188-
pass
189-
190-
191-
def dump_dir(path: str) -> None:
192-
for root, dirs, files in os.walk(os.getcwd()):
193-
print('%s:' % root)
194-
for d in dirs:
195-
print(' %s/' % d)
196-
for f in files:
197-
path = os.path.join(root, f)
198-
print(' %s (%d bytes)' % (f, os.path.getsize(path)))
199-
if not dirs and not files:
200-
print(' (empty)')
201-
202-
203171
def load_python_module_info(module: str, interpreter: str) -> Tuple[str, Optional[List[str]]]:
204172
"""Return tuple (module path, module __all__) for a Python 2 module.
205173

mypy/test/teststubgen.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import glob
2-
import importlib
32
import os.path
43
import shutil
54
import sys
@@ -125,7 +124,7 @@ def test_stubgen(testcase: DataDrivenTestCase) -> None:
125124
handle.close()
126125
# Without this we may sometimes be unable to import the module below, as importlib
127126
# caches os.listdir() results in Python 3.3+ (Guido explained this to me).
128-
reset_importlib_caches()
127+
reset_importlib_cache('stubgen-test-path')
129128
try:
130129
if testcase.name.endswith('_import'):
131130
generate_stub_for_module(name, out_dir, quiet=True,
@@ -145,11 +144,16 @@ def test_stubgen(testcase: DataDrivenTestCase) -> None:
145144
shutil.rmtree(out_dir)
146145

147146

148-
def reset_importlib_caches() -> None:
149-
try:
150-
importlib.invalidate_caches()
151-
except (ImportError, AttributeError):
152-
pass
147+
def reset_importlib_cache(entry: str) -> None:
148+
# importlib.invalidate_caches() is insufficient, since it doesn't
149+
# clear cache entries that indicate that a directory on the path
150+
# does not exist, which can cause failures. Just directly clear
151+
# the sys.path_importer_cache entry ourselves. Other possible
152+
# workarounds include always using different paths in the sys.path
153+
# (perhaps by using the full path name) or removing the entry from
154+
# sys.path after each run.
155+
if entry in sys.path_importer_cache:
156+
del sys.path_importer_cache[entry]
153157

154158

155159
def load_output(dirname: str) -> List[str]:

0 commit comments

Comments
 (0)