mtest: store all test results directly to TestRun

Store return code, test result and additional error directly to the
relevant TestRun instance. This reduces the number of individual
arguments to other relevant functions that need to be passed around and
thus simplifies the code. The test output (and error) were earlier
similarly moved to be stored directly to the TestRun instance for the
same reason.
pull/9478/head
Hemmo Nieminen 3 years ago committed by Eli Schwartz
parent 702f3fe15f
commit 4304df30c2
  1. 117
      mesonbuild/mtest.py

@ -880,11 +880,12 @@ class TestRun:
self.name = name self.name = name
self.timeout = timeout self.timeout = timeout
self.results = list() # type: T.List[TAPParser.Test] self.results = list() # type: T.List[TAPParser.Test]
self.returncode = 0 self.returncode = None # type: T.Optional[int]
self.starttime = None # type: T.Optional[float] self.starttime = None # type: T.Optional[float]
self.duration = None # type: T.Optional[float] self.duration = None # type: T.Optional[float]
self.stdo = '' self.stdo = ''
self.stde = '' self.stde = ''
self.additional_error = ''
self.cmd = None # type: T.Optional[T.List[str]] self.cmd = None # type: T.Optional[T.List[str]]
self.env = test_env # type: T.Dict[str, str] self.env = test_env # type: T.Dict[str, str]
self.should_fail = test.should_fail self.should_fail = test.should_fail
@ -930,17 +931,16 @@ class TestRun:
return self.get_exit_status() return self.get_exit_status()
return self.get_results() return self.get_results()
def _complete(self, returncode: int, res: TestResult) -> None: def _complete(self) -> None:
assert isinstance(res, TestResult) if self.res == TestResult.RUNNING:
if self.should_fail and res in (TestResult.OK, TestResult.FAIL): self.res = TestResult.OK
res = TestResult.UNEXPECTEDPASS if res.is_ok() else TestResult.EXPECTEDFAIL assert isinstance(self.res, TestResult)
if self.should_fail and self.res in (TestResult.OK, TestResult.FAIL):
self.res = TestResult.UNEXPECTEDPASS if self.res is TestResult.OK else TestResult.EXPECTEDFAIL
if self.stdo and not self.stdo.endswith('\n'): if self.stdo and not self.stdo.endswith('\n'):
self.stdo += '\n' self.stdo += '\n'
if self.stde and not self.stde.endswith('\n'): if self.stde and not self.stde.endswith('\n'):
self.stde += '\n' self.stde += '\n'
self.res = res
self.returncode = returncode
self.duration = time.time() - self.starttime self.duration = time.time() - self.starttime
@property @property
@ -953,14 +953,16 @@ class TestRun:
def complete_skip(self) -> None: def complete_skip(self) -> None:
self.starttime = time.time() self.starttime = time.time()
self._complete(GNU_SKIP_RETURNCODE, TestResult.SKIP) self.returncode = GNU_SKIP_RETURNCODE
self.res = TestResult.SKIP
self._complete()
def complete(self, returncode: int, res: TestResult) -> None: def complete(self) -> None:
self._complete(returncode, res) self._complete()
def get_log(self, colorize: bool = False, stderr_only: bool = False) -> str: def get_log(self, colorize: bool = False, stderr_only: bool = False) -> str:
stdo = '' if stderr_only else self.stdo stdo = '' if stderr_only else self.stdo
if self.stde: if self.stde or self.additional_error:
res = '' res = ''
if stdo: if stdo:
res += mlog.cyan('stdout:').get_text(colorize) + '\n' res += mlog.cyan('stdout:').get_text(colorize) + '\n'
@ -968,7 +970,7 @@ class TestRun:
if res[-1:] != '\n': if res[-1:] != '\n':
res += '\n' res += '\n'
res += mlog.cyan('stderr:').get_text(colorize) + '\n' res += mlog.cyan('stderr:').get_text(colorize) + '\n'
res += self.stde res += join_lines(self.stde, self.additional_error)
else: else:
res = stdo res = stdo
if res and res[-1:] != '\n': if res and res[-1:] != '\n':
@ -979,30 +981,29 @@ class TestRun:
def needs_parsing(self) -> bool: def needs_parsing(self) -> bool:
return False return False
async def parse(self, harness: 'TestHarness', lines: T.AsyncIterator[str]) -> T.Tuple[TestResult, str]: async def parse(self, harness: 'TestHarness', lines: T.AsyncIterator[str]) -> None:
async for l in lines: async for l in lines:
pass pass
return TestResult.OK, ''
class TestRunExitCode(TestRun): class TestRunExitCode(TestRun):
def complete(self, returncode: int, res: TestResult) -> None: def complete(self) -> None:
if res: if self.res != TestResult.RUNNING:
pass pass
elif returncode == GNU_SKIP_RETURNCODE: elif self.returncode == GNU_SKIP_RETURNCODE:
res = TestResult.SKIP self.res = TestResult.SKIP
elif returncode == GNU_ERROR_RETURNCODE: elif self.returncode == GNU_ERROR_RETURNCODE:
res = TestResult.ERROR self.res = TestResult.ERROR
else: else:
res = TestResult.FAIL if bool(returncode) else TestResult.OK self.res = TestResult.FAIL if bool(self.returncode) else TestResult.OK
super().complete(returncode, res) super().complete()
TestRun.PROTOCOL_TO_CLASS[TestProtocol.EXITCODE] = TestRunExitCode TestRun.PROTOCOL_TO_CLASS[TestProtocol.EXITCODE] = TestRunExitCode
class TestRunGTest(TestRunExitCode): class TestRunGTest(TestRunExitCode):
def complete(self, returncode: int, res: TestResult) -> None: def complete(self) -> None:
filename = f'{self.test.name}.xml' filename = f'{self.test.name}.xml'
if self.test.workdir: if self.test.workdir:
filename = os.path.join(self.test.workdir, filename) filename = os.path.join(self.test.workdir, filename)
@ -1015,7 +1016,7 @@ class TestRunGTest(TestRunExitCode):
# will handle the failure, don't generate a stacktrace. # will handle the failure, don't generate a stacktrace.
pass pass
super().complete(returncode, res) super().complete()
TestRun.PROTOCOL_TO_CLASS[TestProtocol.GTEST] = TestRunGTest TestRun.PROTOCOL_TO_CLASS[TestProtocol.GTEST] = TestRunGTest
@ -1025,17 +1026,15 @@ class TestRunTAP(TestRun):
def needs_parsing(self) -> bool: def needs_parsing(self) -> bool:
return True return True
def complete(self, returncode: int, res: TestResult) -> None: def complete(self) -> None:
if returncode != 0 and not res.was_killed(): if self.returncode != 0 and not self.res.was_killed():
res = TestResult.ERROR self.res = TestResult.ERROR
self.stde = self.stde or '' self.stde = self.stde or ''
self.stde += f'\n(test program exited with status code {returncode})' self.stde += f'\n(test program exited with status code {self.returncode})'
super().complete()
super().complete(returncode, res)
async def parse(self, harness: 'TestHarness', lines: T.AsyncIterator[str]) -> T.Tuple[TestResult, str]: async def parse(self, harness: 'TestHarness', lines: T.AsyncIterator[str]) -> None:
res = TestResult.OK res = None
error = ''
async for i in TAPParser().parse_async(lines): async for i in TAPParser().parse_async(lines):
if isinstance(i, TAPParser.Bailout): if isinstance(i, TAPParser.Bailout):
@ -1047,13 +1046,15 @@ class TestRunTAP(TestRun):
res = TestResult.FAIL res = TestResult.FAIL
harness.log_subtest(self, i.name or f'subtest {i.number}', i.result) harness.log_subtest(self, i.name or f'subtest {i.number}', i.result)
elif isinstance(i, TAPParser.Error): elif isinstance(i, TAPParser.Error):
error = '\nTAP parsing error: ' + i.message self.additional_error += 'TAP parsing error: ' + i.message
res = TestResult.ERROR res = TestResult.ERROR
if all(t.result is TestResult.SKIP for t in self.results): if all(t.result is TestResult.SKIP for t in self.results):
# This includes the case where self.results is empty # This includes the case where self.results is empty
res = TestResult.SKIP res = TestResult.SKIP
return res, error
if res and self.res == TestResult.RUNNING:
self.res = res
TestRun.PROTOCOL_TO_CLASS[TestProtocol.TAP] = TestRunTAP TestRun.PROTOCOL_TO_CLASS[TestProtocol.TAP] = TestRunTAP
@ -1063,7 +1064,7 @@ class TestRunRust(TestRun):
def needs_parsing(self) -> bool: def needs_parsing(self) -> bool:
return True return True
async def parse(self, harness: 'TestHarness', lines: T.AsyncIterator[str]) -> T.Tuple[TestResult, str]: async def parse(self, harness: 'TestHarness', lines: T.AsyncIterator[str]) -> None:
def parse_res(n: int, name: str, result: str) -> TAPParser.Test: def parse_res(n: int, name: str, result: str) -> TAPParser.Test:
if result == 'ok': if result == 'ok':
return TAPParser.Test(n, name, TestResult.OK, None) return TAPParser.Test(n, name, TestResult.OK, None)
@ -1084,14 +1085,18 @@ class TestRunRust(TestRun):
harness.log_subtest(self, name, t.result) harness.log_subtest(self, name, t.result)
n += 1 n += 1
res = None
if all(t.result is TestResult.SKIP for t in self.results): if all(t.result is TestResult.SKIP for t in self.results):
# This includes the case where self.results is empty # This includes the case where self.results is empty
return TestResult.SKIP, '' res = TestResult.SKIP
elif any(t.result is TestResult.ERROR for t in self.results): elif any(t.result is TestResult.ERROR for t in self.results):
return TestResult.ERROR, '' res = TestResult.ERROR
elif any(t.result is TestResult.FAIL for t in self.results): elif any(t.result is TestResult.FAIL for t in self.results):
return TestResult.FAIL, '' res = TestResult.FAIL
return TestResult.OK, ''
if res and self.res == TestResult.RUNNING:
self.res = res
TestRun.PROTOCOL_TO_CLASS[TestProtocol.RUST] = TestRunRust TestRun.PROTOCOL_TO_CLASS[TestProtocol.RUST] = TestRunRust
@ -1299,26 +1304,24 @@ class TestSubprocess:
if self.stde_task: if self.stde_task:
self.stde_task.cancel() self.stde_task.cancel()
async def wait(self, timeout: T.Optional[int]) -> T.Tuple[int, TestResult, T.Optional[str]]: async def wait(self, test: 'TestRun') -> None:
p = self._process p = self._process
result = None
additional_error = None
self.all_futures.append(asyncio.ensure_future(p.wait())) self.all_futures.append(asyncio.ensure_future(p.wait()))
try: try:
await complete_all(self.all_futures, timeout=timeout) await complete_all(self.all_futures, timeout=test.timeout)
except asyncio.TimeoutError: except asyncio.TimeoutError:
additional_error = await self._kill() test.additional_error += await self._kill() or ''
result = TestResult.TIMEOUT test.res = TestResult.TIMEOUT
except asyncio.CancelledError: except asyncio.CancelledError:
# The main loop must have seen Ctrl-C. # The main loop must have seen Ctrl-C.
additional_error = await self._kill() test.additional_error += await self._kill() or ''
result = TestResult.INTERRUPT test.res = TestResult.INTERRUPT
finally: finally:
if self.postwait_fn: if self.postwait_fn:
self.postwait_fn() self.postwait_fn()
return p.returncode or 0, result, additional_error test.returncode = p.returncode or 0
class SingleTestRunner: class SingleTestRunner:
@ -1478,7 +1481,6 @@ class SingleTestRunner:
env=self.runobj.env, env=self.runobj.env,
cwd=self.test.workdir) cwd=self.test.workdir)
parse_task = None
if self.runobj.needs_parsing: if self.runobj.needs_parsing:
parse_coro = self.runobj.parse(harness, p.stdout_lines()) parse_coro = self.runobj.parse(harness, p.stdout_lines())
parse_task = asyncio.ensure_future(parse_coro) parse_task = asyncio.ensure_future(parse_coro)
@ -1487,21 +1489,16 @@ class SingleTestRunner:
stdo_task, stde_task = p.communicate(self.runobj, self.console_mode) stdo_task, stde_task = p.communicate(self.runobj, self.console_mode)
parse_task = None parse_task = None
returncode, result, additional_error = await p.wait(self.runobj.timeout) await p.wait(self.runobj)
if parse_task is not None:
res, error = await parse_task
if error:
additional_error = join_lines(additional_error, error)
result = result or res
if parse_task:
await parse_task
if stdo_task: if stdo_task:
await stdo_task await stdo_task
if stde_task: if stde_task:
await stde_task await stde_task
self.runobj.stde = join_lines(self.runobj.stde, additional_error) self.runobj.complete()
self.runobj.complete(returncode, result)
class TestHarness: class TestHarness:

Loading…
Cancel
Save