Skip to content

incremental testsuite: don't write subsequent step files until they are needed #11257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 30 additions & 22 deletions mypy/test/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

# File modify/create operation: copy module contents from source_path.
UpdateFile = NamedTuple('UpdateFile', [('module', str),
('source_path', str),
('content', str),
('target_path', str)])

# File delete operation: delete module file.
Expand Down Expand Up @@ -270,11 +270,35 @@ def setup(self) -> None:
self.tmpdir = tempfile.TemporaryDirectory(prefix='mypy-test-')
os.chdir(self.tmpdir.name)
os.mkdir(test_temp_dir)

# Precalculate steps for find_steps()
steps: Dict[int, List[FileOperation]] = {}

for path, content in self.files:
dir = os.path.dirname(path)
os.makedirs(dir, exist_ok=True)
with open(path, 'w', encoding='utf8') as f:
f.write(content)
m = re.match(r'.*\.([0-9]+)$', path)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this stuff so I could combine the re.match logic.
I wonder if we can totally eliminate find_steps and just inject the steps as an arg to suite.run_test()

if m:
# Skip writing subsequent incremental steps - rather
# store them as operations.
num = int(m.group(1))
assert num >= 2
target_path = re.sub(r'\.[0-9]+$', '', path)
module = module_from_path(target_path)
operation = UpdateFile(module, content, target_path)
steps.setdefault(num, []).append(operation)
else:
# Write the first incremental steps
dir = os.path.dirname(path)
os.makedirs(dir, exist_ok=True)
with open(path, 'w', encoding='utf8') as f:
f.write(content)

for num, paths in self.deleted_paths.items():
assert num >= 2
for path in paths:
module = module_from_path(path)
steps.setdefault(num, []).append(DeleteFile(module, path))
max_step = max(steps) if steps else 2
self.steps = [steps.get(num, []) for num in range(2, max_step + 1)]

def teardown(self) -> None:
assert self.old_cwd is not None and self.tmpdir is not None, \
Expand Down Expand Up @@ -312,23 +336,7 @@ def find_steps(self) -> List[List[FileOperation]]:

Defaults to having two steps if there aern't any operations.
"""
steps: Dict[int, List[FileOperation]] = {}
for path, _ in self.files:
m = re.match(r'.*\.([0-9]+)$', path)
if m:
num = int(m.group(1))
assert num >= 2
target_path = re.sub(r'\.[0-9]+$', '', path)
module = module_from_path(target_path)
operation = UpdateFile(module, path, target_path)
steps.setdefault(num, []).append(operation)
for num, paths in self.deleted_paths.items():
assert num >= 2
for path in paths:
module = module_from_path(path)
steps.setdefault(num, []).append(DeleteFile(module, path))
max_step = max(steps) if steps else 2
return [steps.get(num, []) for num in range(2, max_step + 1)]
return self.steps


def module_from_path(path: str) -> str:
Expand Down
10 changes: 6 additions & 4 deletions mypy/test/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ def split_lines(*streams: bytes) -> List[str]:
]


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

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

if new_time:
os.utime(target_path, times=(new_time, new_time))
Expand All @@ -430,7 +432,7 @@ def perform_file_operations(
for op in operations:
if isinstance(op, UpdateFile):
# Modify/create file
copy_and_fudge_mtime(op.source_path, op.target_path)
write_and_fudge_mtime(op.content, op.target_path)
else:
# Delete file/directory
if os.path.isdir(op.path):
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/daemon.test
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ bar.py:3: (str)
bar.py:4: (arg=str)
$ dmypy suggest foo.foo
(str) -> int
$ {python} -c "import shutil; shutil.copy('foo.py.2', 'foo.py')"
$ {python} -c "import shutil; shutil.copy('foo2.py', 'foo.py')"
$ dmypy check foo.py bar.py
bar.py:3: error: Incompatible types in assignment (expression has type "int", variable has type "str")
== Return code: 1
Expand All @@ -284,7 +284,7 @@ def foo(arg):
class Bar:
def bar(self): pass
var = 0
[file foo.py.2]
[file foo2.py]
def foo(arg: str) -> int:
return 12
class Bar:
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fine-grained-follow-imports.test
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ import p2.m2
p1/m1.py:1: error: "int" not callable
main.py:2: error: Cannot find implementation or library stub for module named "p2.m2"
main.py:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
main.py:2: error: Cannot find implementation or library stub for module named "p2"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now happens because the flags have --namespace-packages for this test! Before, p2 was being found as a namespace package because it contained p2/m2.py.2. This sort of error is happening all over the place in #9636

==
p2/m2.py:1: error: "str" not callable
p1/m1.py:1: error: "int" not callable
Expand Down