Skip to content

Commit cd525d2

Browse files
authored
[mypyc] Don't swallow KeyboardInterrupt in test_run (python#7396)
Currently we use distutil's `run_setup` function to wrap the invocation of `setup.py` in test_run, which speeds things up a little bit. Unfortunately `run_setup` swallows KeyboardInterrupt (and all other failures), which prevents ctrl-c from terminating tests (the tests will fail instead with -n0; with parallel testing, pytest will die but it will take several seconds, I think while it waits for tests to finish.) Make a clone of run_setup that doesn't have these problems.
1 parent 5cd1dab commit cd525d2

File tree

1 file changed

+43
-8
lines changed

1 file changed

+43
-8
lines changed

mypyc/test/test_run.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import contextlib
88
import shutil
99
import sys
10-
from typing import Iterator, Optional
10+
from typing import Any, Iterator, Optional, List, cast
1111

1212
from mypy import build
1313
from mypy.test.data import DataDrivenTestCase
@@ -24,8 +24,6 @@
2424
show_c
2525
)
2626

27-
from distutils.core import run_setup
28-
2927
files = [
3028
'run-functions.test',
3129
'run.test',
@@ -46,6 +44,43 @@
4644
"""
4745

4846

47+
def run_setup(script_name: str, script_args: List[str]) -> bool:
48+
"""Run a setup script in a somewhat controlled environment.
49+
50+
This is adapted from code in distutils and our goal here is that is
51+
faster to not need to spin up a python interpreter to run it.
52+
53+
We had to fork it because the real run_setup swallows errors
54+
and KeyboardInterrupt with no way to recover them (!).
55+
The real version has some extra features that we removed since
56+
we weren't using them.
57+
58+
Returns whether the setup succeeded.
59+
"""
60+
save_argv = sys.argv.copy()
61+
g = {'__file__': script_name}
62+
try:
63+
try:
64+
sys.argv[0] = script_name
65+
sys.argv[1:] = script_args
66+
with open(script_name, 'rb') as f:
67+
exec(f.read(), g)
68+
finally:
69+
sys.argv = save_argv
70+
except SystemExit as e:
71+
# typeshed reports code as being an int but that is wrong
72+
code = cast(Any, e).code
73+
# distutils converts KeyboardInterrupt into a SystemExit with
74+
# "interrupted" as the argument. Convert it back so that
75+
# pytest will exit instead of just failing the test.
76+
if code == "interrupted":
77+
raise KeyboardInterrupt
78+
79+
return code == 0 or code is None
80+
81+
return True
82+
83+
4984
@contextlib.contextmanager
5085
def chdir_manager(target: str) -> Iterator[None]:
5186
dir = os.getcwd()
@@ -150,15 +185,15 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
150185
with open(setup_file, 'w') as f:
151186
f.write(setup_format.format(module_paths))
152187

153-
run_setup(setup_file, ['build_ext', '--inplace'])
154-
# Oh argh run_setup doesn't propagate failure. For now we'll just assert
155-
# that the file is there.
156-
suffix = 'pyd' if sys.platform == 'win32' else 'so'
157-
if not glob.glob('native.*.{}'.format(suffix)):
188+
if not run_setup(setup_file, ['build_ext', '--inplace']):
158189
if testcase.config.getoption('--mypyc-showc'):
159190
show_c(cfiles)
160191
assert False, "Compilation failed"
161192

193+
# Assert that an output file got created
194+
suffix = 'pyd' if sys.platform == 'win32' else 'so'
195+
assert glob.glob('native.*.{}'.format(suffix))
196+
162197
for p in to_delete:
163198
os.remove(p)
164199

0 commit comments

Comments
 (0)