mtest: allow quickly interrupting the test run

The new behavior of interrupting the longest running test with Ctrl-C is useful
when tests hang, but not when the run is completely broken for some reason.
Psychology tells us that the user will compulsively spam Ctrl-C in this case,
so exit if three Ctrl-C's are detected within a second.
pull/7822/merge
Paolo Bonzini 4 years ago committed by Jussi Pakkanen
parent f13b2b4b1d
commit ea2f34e286
  1. 5
      docs/markdown/snippets/meson_test_interrupt.md
  2. 22
      mesonbuild/mtest.py

@ -0,0 +1,5 @@
## Ctrl-C behavior in `meson test`
Starting from this version, sending a `SIGINT` signal (or pressing `Ctrl-C`)
to `meson test` will interrupt the longest running test. Pressing `Ctrl-C`
three times within a second will exit `meson test`.

@ -55,6 +55,9 @@ GNU_SKIP_RETURNCODE = 77
# mean that the test failed even before testing what it is supposed to test. # mean that the test failed even before testing what it is supposed to test.
GNU_ERROR_RETURNCODE = 99 GNU_ERROR_RETURNCODE = 99
# Exit if 3 Ctrl-C's are received within one second
MAX_CTRLC = 3
def is_windows() -> bool: def is_windows() -> bool:
platname = platform.system().lower() platname = platform.system().lower()
return platname == 'windows' return platname == 'windows'
@ -1549,6 +1552,7 @@ class TestHarness:
futures = deque() # type: T.Deque[asyncio.Future] futures = deque() # type: T.Deque[asyncio.Future]
running_tests = dict() # type: T.Dict[asyncio.Future, str] running_tests = dict() # type: T.Dict[asyncio.Future, str]
interrupted = False interrupted = False
ctrlc_times = deque(maxlen=MAX_CTRLC) # type: T.Deque[float]
async def run_test(test: SingleTestRunner) -> None: async def run_test(test: SingleTestRunner) -> None:
async with semaphore: async with semaphore:
@ -1577,15 +1581,18 @@ class TestHarness:
del running_tests[future] del running_tests[future]
future.cancel() future.cancel()
def sigterm_handler() -> None: def cancel_all_tests() -> None:
nonlocal interrupted nonlocal interrupted
interrupted = True
while running_tests:
cancel_one_test(False)
def sigterm_handler() -> None:
if interrupted: if interrupted:
return return
interrupted = True
self.flush_logfiles() self.flush_logfiles()
mlog.warning('Received SIGTERM, exiting') mlog.warning('Received SIGTERM, exiting')
while running_tests: cancel_all_tests()
cancel_one_test(False)
def sigint_handler() -> None: def sigint_handler() -> None:
# We always pick the longest-running future that has not been cancelled # We always pick the longest-running future that has not been cancelled
@ -1593,7 +1600,12 @@ class TestHarness:
nonlocal interrupted nonlocal interrupted
if interrupted: if interrupted:
return return
if running_tests: ctrlc_times.append(asyncio.get_event_loop().time())
if len(ctrlc_times) == MAX_CTRLC and ctrlc_times[-1] - ctrlc_times[0] < 1:
self.flush_logfiles()
mlog.warning('CTRL-C detected, exiting')
cancel_all_tests()
elif running_tests:
cancel_one_test(True) cancel_one_test(True)
else: else:
self.flush_logfiles() self.flush_logfiles()

Loading…
Cancel
Save