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

Loading…
Cancel
Save