Skip to content

Commit 8749901

Browse files
authored
incremental testsuite: don't write subsequent step files until they are needed (#11257)
This matters with namespace packages enabled - since an (otherwise) empty folder with some .2 files in it will be considered a namespace package. This is truer - by only writing the files at the point of the step. This helps unblock #9636
1 parent 656bc7e commit 8749901

File tree

4 files changed

+39
-28
lines changed

4 files changed

+39
-28
lines changed

mypy/test/data.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
# File modify/create operation: copy module contents from source_path.
2020
UpdateFile = NamedTuple('UpdateFile', [('module', str),
21-
('source_path', str),
21+
('content', str),
2222
('target_path', str)])
2323

2424
# File delete operation: delete module file.
@@ -270,11 +270,35 @@ def setup(self) -> None:
270270
self.tmpdir = tempfile.TemporaryDirectory(prefix='mypy-test-')
271271
os.chdir(self.tmpdir.name)
272272
os.mkdir(test_temp_dir)
273+
274+
# Precalculate steps for find_steps()
275+
steps: Dict[int, List[FileOperation]] = {}
276+
273277
for path, content in self.files:
274-
dir = os.path.dirname(path)
275-
os.makedirs(dir, exist_ok=True)
276-
with open(path, 'w', encoding='utf8') as f:
277-
f.write(content)
278+
m = re.match(r'.*\.([0-9]+)$', path)
279+
if m:
280+
# Skip writing subsequent incremental steps - rather
281+
# store them as operations.
282+
num = int(m.group(1))
283+
assert num >= 2
284+
target_path = re.sub(r'\.[0-9]+$', '', path)
285+
module = module_from_path(target_path)
286+
operation = UpdateFile(module, content, target_path)
287+
steps.setdefault(num, []).append(operation)
288+
else:
289+
# Write the first incremental steps
290+
dir = os.path.dirname(path)
291+
os.makedirs(dir, exist_ok=True)
292+
with open(path, 'w', encoding='utf8') as f:
293+
f.write(content)
294+
295+
for num, paths in self.deleted_paths.items():
296+
assert num >= 2
297+
for path in paths:
298+
module = module_from_path(path)
299+
steps.setdefault(num, []).append(DeleteFile(module, path))
300+
max_step = max(steps) if steps else 2
301+
self.steps = [steps.get(num, []) for num in range(2, max_step + 1)]
278302

279303
def teardown(self) -> None:
280304
assert self.old_cwd is not None and self.tmpdir is not None, \
@@ -312,23 +336,7 @@ def find_steps(self) -> List[List[FileOperation]]:
312336
313337
Defaults to having two steps if there aern't any operations.
314338
"""
315-
steps: Dict[int, List[FileOperation]] = {}
316-
for path, _ in self.files:
317-
m = re.match(r'.*\.([0-9]+)$', path)
318-
if m:
319-
num = int(m.group(1))
320-
assert num >= 2
321-
target_path = re.sub(r'\.[0-9]+$', '', path)
322-
module = module_from_path(target_path)
323-
operation = UpdateFile(module, path, target_path)
324-
steps.setdefault(num, []).append(operation)
325-
for num, paths in self.deleted_paths.items():
326-
assert num >= 2
327-
for path in paths:
328-
module = module_from_path(path)
329-
steps.setdefault(num, []).append(DeleteFile(module, path))
330-
max_step = max(steps) if steps else 2
331-
return [steps.get(num, []) for num in range(2, max_step + 1)]
339+
return self.steps
332340

333341

334342
def module_from_path(path: str) -> str:

mypy/test/helpers.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ def split_lines(*streams: bytes) -> List[str]:
406406
]
407407

408408

409-
def copy_and_fudge_mtime(source_path: str, target_path: str) -> None:
409+
def write_and_fudge_mtime(content: str, target_path: str) -> None:
410410
# In some systems, mtime has a resolution of 1 second which can
411411
# cause annoying-to-debug issues when a file has the same size
412412
# after a change. We manually set the mtime to circumvent this.
@@ -418,8 +418,10 @@ def copy_and_fudge_mtime(source_path: str, target_path: str) -> None:
418418
if os.path.isfile(target_path):
419419
new_time = os.stat(target_path).st_mtime + 1
420420

421-
# Use retries to work around potential flakiness on Windows (AppVeyor).
422-
retry_on_error(lambda: shutil.copy(source_path, target_path))
421+
dir = os.path.dirname(target_path)
422+
os.makedirs(dir, exist_ok=True)
423+
with open(target_path, "w", encoding="utf-8") as target:
424+
target.write(content)
423425

424426
if new_time:
425427
os.utime(target_path, times=(new_time, new_time))
@@ -430,7 +432,7 @@ def perform_file_operations(
430432
for op in operations:
431433
if isinstance(op, UpdateFile):
432434
# Modify/create file
433-
copy_and_fudge_mtime(op.source_path, op.target_path)
435+
write_and_fudge_mtime(op.content, op.target_path)
434436
else:
435437
# Delete file/directory
436438
if os.path.isdir(op.path):

test-data/unit/daemon.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ bar.py:3: (str)
274274
bar.py:4: (arg=str)
275275
$ dmypy suggest foo.foo
276276
(str) -> int
277-
$ {python} -c "import shutil; shutil.copy('foo.py.2', 'foo.py')"
277+
$ {python} -c "import shutil; shutil.copy('foo2.py', 'foo.py')"
278278
$ dmypy check foo.py bar.py
279279
bar.py:3: error: Incompatible types in assignment (expression has type "int", variable has type "str")
280280
== Return code: 1
@@ -284,7 +284,7 @@ def foo(arg):
284284
class Bar:
285285
def bar(self): pass
286286
var = 0
287-
[file foo.py.2]
287+
[file foo2.py]
288288
def foo(arg: str) -> int:
289289
return 12
290290
class Bar:

test-data/unit/fine-grained-follow-imports.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,7 @@ import p2.m2
638638
p1/m1.py:1: error: "int" not callable
639639
main.py:2: error: Cannot find implementation or library stub for module named "p2.m2"
640640
main.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
641+
main.py:2: error: Cannot find implementation or library stub for module named "p2"
641642
==
642643
p2/m2.py:1: error: "str" not callable
643644
p1/m1.py:1: error: "int" not callable

0 commit comments

Comments
 (0)