mtest: add INTERRUPT to TestResult

Distinguish a failure due to user interrupt from a presumable ERROR
result due to the SIGTERM.  The test should fail after CTRL+C even if
the test traps SIGTERM and exits with a return code of 0.
pull/7836/head
Paolo Bonzini 4 years ago
parent bd526ec2dc
commit 98d3863fa4
  1. 45
      mesonbuild/mtest.py

@ -168,6 +168,7 @@ class TestResult(enum.Enum):
OK = 'OK' OK = 'OK'
TIMEOUT = 'TIMEOUT' TIMEOUT = 'TIMEOUT'
INTERRUPT = 'INTERRUPT'
SKIP = 'SKIP' SKIP = 'SKIP'
FAIL = 'FAIL' FAIL = 'FAIL'
EXPECTEDFAIL = 'EXPECTEDFAIL' EXPECTEDFAIL = 'EXPECTEDFAIL'
@ -376,7 +377,8 @@ class JunitBuilder:
'testsuite', 'testsuite',
name=suitename, name=suitename,
tests=str(len(test.results)), tests=str(len(test.results)),
errors=str(sum(1 for r in test.results if r is TestResult.ERROR)), errors=str(sum(1 for r in test.results if r in
{TestResult.INTERRUPT, TestResult.ERROR})),
failures=str(sum(1 for r in test.results if r in failures=str(sum(1 for r in test.results if r in
{TestResult.FAIL, TestResult.UNEXPECTEDPASS, TestResult.TIMEOUT})), {TestResult.FAIL, TestResult.UNEXPECTEDPASS, TestResult.TIMEOUT})),
skipped=str(sum(1 for r in test.results if r is TestResult.SKIP)), skipped=str(sum(1 for r in test.results if r is TestResult.SKIP)),
@ -395,6 +397,9 @@ class JunitBuilder:
elif result is TestResult.UNEXPECTEDPASS: elif result is TestResult.UNEXPECTEDPASS:
fail = et.SubElement(testcase, 'failure') fail = et.SubElement(testcase, 'failure')
fail.text = 'Test unexpected passed.' fail.text = 'Test unexpected passed.'
elif result is TestResult.INTERRUPT:
fail = et.SubElement(testcase, 'failure')
fail.text = 'Test was interrupted by user.'
elif result is TestResult.TIMEOUT: elif result is TestResult.TIMEOUT:
fail = et.SubElement(testcase, 'failure') fail = et.SubElement(testcase, 'failure')
fail.text = 'Test did not finish before configured timeout.' fail.text = 'Test did not finish before configured timeout.'
@ -667,7 +672,7 @@ class SingleTestRunner:
def _run_subprocess(self, args: T.List[str], *, timeout: T.Optional[int], def _run_subprocess(self, args: T.List[str], *, timeout: T.Optional[int],
stdout: T.IO, stderr: T.IO, stdout: T.IO, stderr: T.IO,
env: T.Dict[str, str], cwd: T.Optional[str]) -> T.Tuple[T.Optional[int], bool, T.Optional[str]]: env: T.Dict[str, str], cwd: T.Optional[str]) -> T.Tuple[T.Optional[int], TestResult, T.Optional[str]]:
def kill_process(p: subprocess.Popen) -> T.Optional[str]: def kill_process(p: subprocess.Popen) -> T.Optional[str]:
# Python does not provide multiplatform support for # Python does not provide multiplatform support for
# killing a process and all its children so we need # killing a process and all its children so we need
@ -727,27 +732,25 @@ class SingleTestRunner:
env=env, env=env,
cwd=cwd, cwd=cwd,
preexec_fn=preexec_fn if not is_windows() else None) preexec_fn=preexec_fn if not is_windows() else None)
timed_out = False result = None
kill_test = False additional_error = None
try: try:
p.communicate(timeout=timeout) p.communicate(timeout=timeout)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
if self.options.verbose: if self.options.verbose:
print('{} time out (After {} seconds)'.format(self.test.name, timeout)) print('{} time out (After {} seconds)'.format(self.test.name, timeout))
timed_out = True additional_error = kill_process(p)
result = TestResult.TIMEOUT
except KeyboardInterrupt: except KeyboardInterrupt:
mlog.warning('CTRL-C detected while running {}'.format(self.test.name)) mlog.warning('CTRL-C detected while running {}'.format(self.test.name))
kill_test = True additional_error = kill_process(p)
result = TestResult.INTERRUPT
finally: finally:
if self.options.gdb: if self.options.gdb:
# Let us accept ^C again # Let us accept ^C again
signal.signal(signal.SIGINT, previous_sigint_handler) signal.signal(signal.SIGINT, previous_sigint_handler)
if kill_test or timed_out: return p.returncode, result, additional_error
additional_error = kill_process(p)
return p.returncode, timed_out, additional_error
else:
return p.returncode, False, None
def _run_cmd(self, cmd: T.List[str]) -> TestRun: def _run_cmd(self, cmd: T.List[str]) -> TestRun:
starttime = time.time() starttime = time.time()
@ -794,12 +797,12 @@ class SingleTestRunner:
else: else:
timeout = self.test.timeout timeout = self.test.timeout
returncode, timed_out, additional_error = self._run_subprocess(cmd + extra_cmd, returncode, result, additional_error = self._run_subprocess(cmd + extra_cmd,
timeout=timeout, timeout=timeout,
stdout=stdout, stdout=stdout,
stderr=stderr, stderr=stderr,
env=self.env, env=self.env,
cwd=self.test.workdir) cwd=self.test.workdir)
endtime = time.time() endtime = time.time()
duration = endtime - starttime duration = endtime - starttime
if additional_error is None: if additional_error is None:
@ -816,8 +819,8 @@ class SingleTestRunner:
else: else:
stdo = "" stdo = ""
stde = additional_error stde = additional_error
if timed_out: if result:
return TestRun(self.test, self.test_env, TestResult.TIMEOUT, [], returncode, starttime, duration, stdo, stde, cmd) return TestRun(self.test, self.test_env, result, [], returncode, starttime, duration, stdo, stde, cmd)
else: else:
if self.test.protocol is TestProtocol.EXITCODE: if self.test.protocol is TestProtocol.EXITCODE:
return TestRun.make_exitcode(self.test, self.test_env, returncode, starttime, duration, stdo, stde, cmd) return TestRun.make_exitcode(self.test, self.test_env, returncode, starttime, duration, stdo, stde, cmd)
@ -919,7 +922,7 @@ class TestHarness:
self.skip_count += 1 self.skip_count += 1
elif result.res is TestResult.OK: elif result.res is TestResult.OK:
self.success_count += 1 self.success_count += 1
elif result.res is TestResult.FAIL or result.res is TestResult.ERROR: elif result.res in {TestResult.FAIL, TestResult.ERROR, TestResult.INTERRUPT}:
self.fail_count += 1 self.fail_count += 1
elif result.res is TestResult.EXPECTEDFAIL: elif result.res is TestResult.EXPECTEDFAIL:
self.expectedfail_count += 1 self.expectedfail_count += 1
@ -932,7 +935,7 @@ class TestHarness:
tests: T.List[TestSerialisation], tests: T.List[TestSerialisation],
name: str, result: TestRun, i: int) -> None: name: str, result: TestRun, i: int) -> None:
ok_statuses = (TestResult.OK, TestResult.EXPECTEDFAIL) ok_statuses = (TestResult.OK, TestResult.EXPECTEDFAIL)
bad_statuses = (TestResult.FAIL, TestResult.TIMEOUT, bad_statuses = (TestResult.FAIL, TestResult.TIMEOUT, TestResult.INTERRUPT,
TestResult.UNEXPECTEDPASS, TestResult.ERROR) TestResult.UNEXPECTEDPASS, TestResult.ERROR)
result_str = '{num:{numlen}}/{testcount} {name:{name_max_len}} {res:{reslen}} {dur:.2f}s'.format( result_str = '{num:{numlen}}/{testcount} {name:{name_max_len}} {res:{reslen}} {dur:.2f}s'.format(
numlen=len(str(test_count)), numlen=len(str(test_count)),

Loading…
Cancel
Save