Skip to content

Commit 9270819

Browse files
authored
testcheck: sort inline assertions (#15290)
The order in which inline error assertions are collected into the `TestCase.output` (the "expected") can differ from the actual errors' order. Specifically, it's the order of the files themselves (as they're traversed during build), rather than individual lines. To allow inline assertions across multiple modules, we can sort the collected errors to match the actual errors (only when it concerns module order; within the modules the order remains stable).
1 parent 5d6b0b6 commit 9270819

File tree

3 files changed

+34
-9
lines changed

3 files changed

+34
-9
lines changed

mypy/test/data.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ def parse_test_case(case: DataDrivenTestCase) -> None:
208208
).format(passnum, case.file, first_item.line)
209209
)
210210

211+
output_inline_start = len(output)
211212
input = first_item.data
212213
expand_errors(input, output, "main")
213214
for file_path, contents in files:
@@ -225,6 +226,7 @@ def parse_test_case(case: DataDrivenTestCase) -> None:
225226

226227
case.input = input
227228
case.output = output
229+
case.output_inline_start = output_inline_start
228230
case.output2 = output2
229231
case.last_line = case.line + item.line + len(item.data) - 2
230232
case.files = files
@@ -246,6 +248,7 @@ class DataDrivenTestCase(pytest.Item):
246248

247249
input: list[str]
248250
output: list[str] # Output for the first pass
251+
output_inline_start: int
249252
output2: dict[int, list[str]] # Output for runs 2+, indexed by run number
250253

251254
# full path of test suite

mypy/test/testcheck.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,19 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
8484
else:
8585
self.run_case_once(testcase)
8686

87+
def _sort_output_if_needed(self, testcase: DataDrivenTestCase, a: list[str]) -> None:
88+
idx = testcase.output_inline_start
89+
if not testcase.files or idx == len(testcase.output):
90+
return
91+
92+
def _filename(_msg: str) -> str:
93+
return _msg.partition(":")[0]
94+
95+
file_weights = {file: idx for idx, file in enumerate(_filename(msg) for msg in a)}
96+
testcase.output[idx:] = sorted(
97+
testcase.output[idx:], key=lambda msg: file_weights.get(_filename(msg), -1)
98+
)
99+
87100
def run_case_once(
88101
self,
89102
testcase: DataDrivenTestCase,
@@ -163,21 +176,20 @@ def run_case_once(
163176
a = normalize_error_messages(a)
164177

165178
# Make sure error messages match
166-
if incremental_step == 0:
167-
# Not incremental
168-
msg = "Unexpected type checker output ({}, line {})"
179+
if incremental_step < 2:
180+
if incremental_step == 1:
181+
msg = "Unexpected type checker output in incremental, run 1 ({}, line {})"
182+
else:
183+
assert incremental_step == 0
184+
msg = "Unexpected type checker output ({}, line {})"
185+
self._sort_output_if_needed(testcase, a)
169186
output = testcase.output
170-
elif incremental_step == 1:
171-
msg = "Unexpected type checker output in incremental, run 1 ({}, line {})"
172-
output = testcase.output
173-
elif incremental_step > 1:
187+
else:
174188
msg = (
175189
f"Unexpected type checker output in incremental, run {incremental_step}"
176190
+ " ({}, line {})"
177191
)
178192
output = testcase.output2.get(incremental_step, [])
179-
else:
180-
raise AssertionError()
181193

182194
if output != a and testcase.config.getoption("--update-data", False):
183195
update_testcase_output(testcase, a)

test-data/unit/check-basic.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,13 @@ class A:
493493

494494
[file test.py]
495495
def foo(s: str) -> None: ...
496+
497+
[case testInlineAssertions]
498+
import a, b
499+
s1: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str")
500+
[file a.py]
501+
s2: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str")
502+
[file b.py]
503+
s3: str = 42 # E: Incompatible types in assignment (expression has type "int", variable has type "str")
504+
[file c.py]
505+
s3: str = 'foo'

0 commit comments

Comments
 (0)