Skip to content

Commit 2cd69e4

Browse files
committed
Better testing support
1 parent 8e901f9 commit 2cd69e4

File tree

7 files changed

+94
-59
lines changed

7 files changed

+94
-59
lines changed

mypy/build.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ def wait_for_worker(status_file: str, timeout: float = 5.0) -> tuple[int, str]:
234234
sys.exit(2)
235235

236236

237-
def start_worker(options_data: str, idx: int) -> subprocess.Popen[bytes]:
237+
def start_worker(options_data: str, idx: int, env: Mapping[str, str]) -> subprocess.Popen[bytes]:
238238
status_file = f".mypy_worker.{idx}.json"
239239
if os.path.isfile(status_file):
240240
os.unlink(status_file)
@@ -245,7 +245,7 @@ def start_worker(options_data: str, idx: int) -> subprocess.Popen[bytes]:
245245
f"--status-file={status_file}",
246246
f'--options-data="{options_data}"',
247247
]
248-
return subprocess.Popen(command)
248+
return subprocess.Popen(command, env=env)
249249

250250

251251
def get_worker(idx: int, proc: subprocess.Popen[bytes]) -> WorkerClient:
@@ -268,6 +268,7 @@ def build(
268268
stdout: TextIO | None = None,
269269
stderr: TextIO | None = None,
270270
extra_plugins: Sequence[Plugin] | None = None,
271+
worker_env: Mapping[str, str] | None = None,
271272
) -> BuildResult:
272273
"""Analyze a program.
273274
@@ -311,15 +312,10 @@ def default_flush_errors(
311312
workers = []
312313
procs = []
313314
if options.num_workers > 0:
314-
if options.use_builtins_fixtures:
315-
os.environ["MYPY_TEST_PREFIX"] = os.path.dirname(os.path.dirname(__file__))
316-
if alt_lib_path:
317-
os.environ["MYPY_ALT_LIB_PATH"] = alt_lib_path
318-
319315
pickled_options = pickle.dumps(options.snapshot())
320316
options_data = base64.b64encode(pickled_options).decode()
321317
for i in range(options.num_workers):
322-
procs.append(start_worker(options_data, i))
318+
procs.append(start_worker(options_data, i, worker_env or os.environ))
323319
for i, proc in enumerate(procs):
324320
workers.append(get_worker(i, proc))
325321

@@ -350,6 +346,18 @@ def default_flush_errors(
350346
flush_errors(None, e.messages, serious)
351347
e.messages = messages
352348
raise
349+
finally:
350+
for worker in workers:
351+
try:
352+
send(worker.conn, {"final": True})
353+
except OSError:
354+
pass
355+
for worker in workers:
356+
worker.conn.close()
357+
worker.proc.wait()
358+
status_file = f".mypy_worker.{worker.idx}.json"
359+
if os.path.isfile(status_file):
360+
os.unlink(status_file)
353361

354362

355363
def build_inner(
@@ -426,19 +434,6 @@ def build_inner(
426434
dump_line_checking_stats(options.line_checking_stats, graph)
427435
return BuildResult(manager, graph)
428436
finally:
429-
430-
for worker in workers:
431-
try:
432-
send(worker.conn, {"final": True})
433-
except OSError:
434-
pass
435-
for worker in workers:
436-
worker.conn.close()
437-
worker.proc.wait()
438-
status_file = f".mypy_worker.{worker.idx}.json"
439-
if os.path.isfile(status_file):
440-
os.unlink(status_file)
441-
442437
t0 = time.time()
443438
manager.metastore.commit()
444439
manager.add_stats(cache_commit_time=time.time() - t0)

mypy/build_worker/worker.py

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sys
1111
import time
1212

13+
from mypy import util
1314
from mypy.build import (
1415
SCC,
1516
BuildManager,
@@ -68,47 +69,27 @@ def main(argv: list[str]) -> None:
6869
finally:
6970
server.cleanup()
7071

72+
if options.fast_exit:
73+
util.hard_exit(0)
74+
7175

7276
def serve(server: IPCServer, options: Options, errors: Errors, fscache: FileSystemCache) -> None:
7377
data = receive(server)
7478
sources = [BuildSource(*st) for st in data["sources"]]
79+
manager = setup_worker_manager(sources, options, errors, fscache)
80+
if manager is None:
81+
return
7582

76-
data_dir = os.path.dirname(os.path.dirname(__file__))
77-
alt_lib_path = os.environ.get("MYPY_ALT_LIB_PATH")
78-
search_paths = compute_search_paths(sources, options, data_dir, alt_lib_path)
79-
80-
source_set = BuildSourceSet(sources)
81-
plugin, snapshot = load_plugins(options, errors, sys.stdout, [])
82-
83-
def flush_errors(filename: str | None, new_messages: list[str], is_serious: bool) -> None:
84-
pass
85-
86-
manager = BuildManager(
87-
data_dir,
88-
search_paths,
89-
ignore_prefix=os.getcwd(),
90-
source_set=source_set,
91-
reports=None,
92-
options=options,
93-
version_id=__version__,
94-
plugin=plugin,
95-
plugins_snapshot=snapshot,
96-
errors=errors,
97-
error_formatter=None,
98-
flush_errors=flush_errors,
99-
fscache=fscache,
100-
stdout=sys.stdout,
101-
stderr=sys.stderr,
102-
)
103-
104-
gc.disable()
83+
if platform.python_implementation() == "CPython":
84+
gc.disable()
10585
try:
10686
graph = load_graph(sources, manager)
10787
except CompileError:
10888
return
109-
gc.freeze()
110-
gc.unfreeze()
111-
gc.enable()
89+
if platform.python_implementation() == "CPython":
90+
gc.freeze()
91+
gc.unfreeze()
92+
gc.enable()
11293

11394
for id in graph:
11495
manager.import_map[id] = set(graph[id].dependencies + graph[id].suppressed)
@@ -144,5 +125,40 @@ def flush_errors(filename: str | None, new_messages: list[str], is_serious: bool
144125
manager.add_stats(total_process_stale_time=time.time() - t0, stale_sccs_processed=1)
145126

146127

128+
def setup_worker_manager(
129+
sources: list[BuildSource], options: Options, errors: Errors, fscache: FileSystemCache
130+
) -> BuildManager | None:
131+
data_dir = os.path.dirname(os.path.dirname(__file__))
132+
alt_lib_path = os.environ.get("MYPY_ALT_LIB_PATH")
133+
search_paths = compute_search_paths(sources, options, data_dir, alt_lib_path)
134+
135+
source_set = BuildSourceSet(sources)
136+
try:
137+
plugin, snapshot = load_plugins(options, errors, sys.stdout, [])
138+
except CompileError:
139+
return None
140+
141+
def flush_errors(filename: str | None, new_messages: list[str], is_serious: bool) -> None:
142+
pass
143+
144+
return BuildManager(
145+
data_dir,
146+
search_paths,
147+
ignore_prefix=os.getcwd(),
148+
source_set=source_set,
149+
reports=None,
150+
options=options,
151+
version_id=__version__,
152+
plugin=plugin,
153+
plugins_snapshot=snapshot,
154+
errors=errors,
155+
error_formatter=None,
156+
flush_errors=flush_errors,
157+
fscache=fscache,
158+
stdout=sys.stdout,
159+
stderr=sys.stderr,
160+
)
161+
162+
147163
def console_entry() -> None:
148164
main(sys.argv[1:])

mypy/main.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ def main(
167167
# Exit without freeing objects -- it's faster.
168168
#
169169
# NOTE: We don't flush all open files on exit (or run other destructors)!
170-
# TODO: use this for workers and join?
171170
util.hard_exit(code)
172171
elif code:
173172
sys.exit(code)

mypy/test/helpers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,7 @@ def find_test_files(pattern: str, exclude: list[str] | None = None) -> list[str]
482482
for path in (pathlib.Path(test_data_prefix).rglob(pattern))
483483
if path.name not in (exclude or [])
484484
]
485+
486+
487+
def remove_typevar_ids(a: list[str]) -> list[str]:
488+
return [re.sub(r"`-?\d+", "", line) for line in a]

mypy/test/testcheck.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
normalize_error_messages,
2323
parse_options,
2424
perform_file_operations,
25+
remove_typevar_ids,
2526
)
2627
from mypy.test.update_data import update_testcase_output
2728

@@ -133,6 +134,11 @@ def run_case_once(
133134
options.use_builtins_fixtures = True
134135
options.show_traceback = True
135136

137+
if options.num_workers:
138+
options.fixed_format_cache = True
139+
if testcase.output_files:
140+
raise pytest.skip("Reports are not supported in parallel mode yet")
141+
136142
# Enable some options automatically based on test file name.
137143
if "columns" in testcase.file:
138144
options.show_column_numbers = True
@@ -160,12 +166,22 @@ def run_case_once(
160166
)
161167

162168
plugin_dir = os.path.join(test_data_prefix, "plugins")
163-
sys.path.insert(0, plugin_dir)
164169

170+
worker_env = None
171+
if options.num_workers > 0:
172+
worker_env = os.environ.copy()
173+
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
174+
worker_env["PYTHONPATH"] = os.pathsep.join([root_dir, plugin_dir])
175+
worker_env["MYPY_TEST_PREFIX"] = root_dir
176+
worker_env["MYPY_ALT_LIB_PATH"] = test_temp_dir
177+
178+
sys.path.insert(0, plugin_dir)
165179
res = None
166180
blocker = False
167181
try:
168-
res = build.build(sources=sources, options=options, alt_lib_path=test_temp_dir)
182+
res = build.build(
183+
sources=sources, options=options, alt_lib_path=test_temp_dir, worker_env=worker_env
184+
)
169185
a = res.errors
170186
except CompileError as e:
171187
a = e.messages
@@ -196,6 +212,9 @@ def run_case_once(
196212
if output != a and testcase.config.getoption("--update-data", False):
197213
update_testcase_output(testcase, a, incremental_step=incremental_step)
198214

215+
if options.num_workers > 0:
216+
a = remove_typevar_ids(a)
217+
output = remove_typevar_ids(output)
199218
assert_string_arrays_equal(output, a, msg.format(testcase.file, testcase.line))
200219

201220
if res:
@@ -211,7 +230,8 @@ def run_case_once(
211230
for module, target in res.manager.processed_targets
212231
if module in testcase.test_modules
213232
]
214-
if expected is not None:
233+
# TODO: check targets in parallel mode (e.g. per SCC).
234+
if options.num_workers == 0 and expected is not None:
215235
assert_target_equivalence(name, expected, actual)
216236
if incremental_step > 1:
217237
suffix = "" if incremental_step == 2 else str(incremental_step - 1)

test-data/unit/check-ctypes.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,11 @@ reveal_type(intarr4(*int_values)) # N: Revealed type is "_ctypes.Array[ctypes.c
166166
reveal_type(intarr4(*c_int_values)) # N: Revealed type is "_ctypes.Array[ctypes.c_int]"
167167
reveal_type(intarr6(1, ctypes.c_int(2), *int_values)) # N: Revealed type is "_ctypes.Array[ctypes.c_int]"
168168
reveal_type(intarr6(1, ctypes.c_int(2), *c_int_values)) # N: Revealed type is "_ctypes.Array[ctypes.c_int]"
169-
[typing fixtures/typing-medium.pyi]
170169

171170
float_values = [1.0, 2.0, 3.0, 4.0]
172-
intarr4(*float_values) # E: Array constructor argument 1 of type "List[float]" is not convertible to the array element type "Iterable[c_int]"
171+
intarr4(*float_values) # E: Array constructor argument 1 of type "list[float]" is not convertible to the array element type "Iterable[c_int]"
173172
[builtins fixtures/floatdict.pyi]
173+
[typing fixtures/typing-medium.pyi]
174174

175175
[case testCtypesArrayConstructorKwargs]
176176
import ctypes

test-data/unit/lib-stub/typing_extensions.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import collections
12
import typing
23
from typing import Any, Callable, Mapping, Iterable, Iterator, NoReturn as NoReturn, Dict, Tuple, Type, Union
34
from typing import TYPE_CHECKING as TYPE_CHECKING

0 commit comments

Comments
 (0)