diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 3214d8275..32c741733 100644 --- a/mesonbuild/mtest.py +++ b/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: