diff options
author | Hemmo Nieminen <hemmo.nieminen@iki.fi> | 2022-02-01 00:00:00 +0200 |
---|---|---|
committer | Hemmo Nieminen <hemmo.nieminen@iki.fi> | 2022-03-23 00:00:00 +0200 |
commit | 8a5ba78702d855e47eb22ba6e6355616e3866a75 (patch) | |
tree | 756c4e5d955d0b3f9868dea6dbc35041464775ab | |
parent | c7bbe47694a1ca2f34508bc4626eccaeded6830c (diff) | |
download | meson-8a5ba78702d855e47eb22ba6e6355616e3866a75.zip meson-8a5ba78702d855e47eb22ba6e6355616e3866a75.tar.gz meson-8a5ba78702d855e47eb22ba6e6355616e3866a75.tar.bz2 |
mtest: store test output directly to a TestRun object
By storing test output directly to the TestRun instance we avoid the
need to pass the outputs around in individual function arguments thus
simplifying the code.
The amount of individual arguments will be further reduced in a
future commit.
-rw-r--r-- | mesonbuild/mtest.py | 81 |
1 files changed, 48 insertions, 33 deletions
diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index c2235d3..bce7cc9 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -888,8 +888,8 @@ class TestRun: self.returncode = 0 self.starttime = None # type: T.Optional[float] self.duration = None # type: T.Optional[float] - self.stdo = None # type: T.Optional[str] - self.stde = None # type: T.Optional[str] + self.stdo = '' + self.stde = '' self.cmd = None # type: T.Optional[T.List[str]] self.env = test_env # type: T.Dict[str, str] self.should_fail = test.should_fail @@ -935,17 +935,18 @@ class TestRun: return self.get_exit_status() return self.get_results() - def _complete(self, returncode: int, res: TestResult, - stdo: T.Optional[str], stde: T.Optional[str]) -> None: + 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 + 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 - self.stdo = stdo - self.stde = stde @property def cmdline(self) -> T.Optional[str]: @@ -955,13 +956,12 @@ class TestRun: return env_tuple_to_str(test_only_env) + \ ' '.join(sh_quote(x) for x in self.cmd) - def complete_skip(self, message: str) -> None: + def complete_skip(self) -> None: self.starttime = time.time() - self._complete(GNU_SKIP_RETURNCODE, TestResult.SKIP, message, None) + self._complete(GNU_SKIP_RETURNCODE, TestResult.SKIP) - def complete(self, returncode: int, res: TestResult, - stdo: T.Optional[str], stde: T.Optional[str]) -> None: - self._complete(returncode, res, stdo, stde) + def complete(self, returncode: int, res: TestResult) -> None: + self._complete(returncode, res) def get_log(self, colorize: bool = False, stderr_only: bool = False) -> str: stdo = '' if stderr_only else self.stdo @@ -992,8 +992,7 @@ class TestRun: class TestRunExitCode(TestRun): - def complete(self, returncode: int, res: TestResult, - stdo: T.Optional[str], stde: T.Optional[str]) -> None: + def complete(self, returncode: int, res: TestResult) -> None: if res: pass elif returncode == GNU_SKIP_RETURNCODE: @@ -1002,14 +1001,13 @@ class TestRunExitCode(TestRun): res = TestResult.ERROR else: res = TestResult.FAIL if bool(returncode) else TestResult.OK - super().complete(returncode, res, stdo, stde) + super().complete(returncode, res) TestRun.PROTOCOL_TO_CLASS[TestProtocol.EXITCODE] = TestRunExitCode class TestRunGTest(TestRunExitCode): - def complete(self, returncode: int, res: TestResult, - stdo: T.Optional[str], stde: T.Optional[str]) -> None: + def complete(self, returncode: int, res: TestResult) -> None: filename = f'{self.test.name}.xml' if self.test.workdir: filename = os.path.join(self.test.workdir, filename) @@ -1022,7 +1020,7 @@ class TestRunGTest(TestRunExitCode): # will handle the failure, don't generate a stacktrace. pass - super().complete(returncode, res, stdo, stde) + super().complete(returncode, res) TestRun.PROTOCOL_TO_CLASS[TestProtocol.GTEST] = TestRunGTest @@ -1032,14 +1030,13 @@ class TestRunTAP(TestRun): def needs_parsing(self) -> bool: return True - def complete(self, returncode: int, res: TestResult, - stdo: str, stde: str) -> None: + def complete(self, returncode: int, res: TestResult) -> None: if returncode != 0 and not res.was_killed(): res = TestResult.ERROR - stde = stde or '' - stde += f'\n(test program exited with status code {returncode})' + self.stde = self.stde or '' + self.stde += f'\n(test program exited with status code {returncode})' - super().complete(returncode, res, stdo, stde) + super().complete(returncode, res) async def parse(self, harness: 'TestHarness', lines: T.AsyncIterator[str]) -> T.Tuple[TestResult, str]: res = TestResult.OK @@ -1235,16 +1232,28 @@ class TestSubprocess: self.stdo_task = asyncio.ensure_future(decode_coro) return queue_iter(q) - def communicate(self, console_mode: ConsoleUser) -> T.Tuple[T.Optional[T.Awaitable[str]], - T.Optional[T.Awaitable[str]]]: + def communicate(self, + test: 'TestRun', + console_mode: ConsoleUser) -> T.Tuple[T.Optional[T.Awaitable[str]], + T.Optional[T.Awaitable[str]]]: + async def collect_stdo(test: 'TestRun', + reader: asyncio.StreamReader, + console_mode: ConsoleUser) -> None: + test.stdo = await read_decode(reader, console_mode) + + async def collect_stde(test: 'TestRun', + reader: asyncio.StreamReader, + console_mode: ConsoleUser) -> None: + test.stde = await read_decode(reader, console_mode) + # asyncio.ensure_future ensures that printing can # run in the background, even before it is awaited if self.stdo_task is None and self.stdout is not None: - decode_coro = read_decode(self._process.stdout, console_mode) + decode_coro = collect_stdo(test, self._process.stdout, console_mode) self.stdo_task = asyncio.ensure_future(decode_coro) self.all_futures.append(self.stdo_task) if self.stderr is not None and self.stderr != asyncio.subprocess.STDOUT: - decode_coro = read_decode(self._process.stderr, console_mode) + decode_coro = collect_stde(test, self._process.stderr, console_mode) self.stde_task = asyncio.ensure_future(decode_coro) self.all_futures.append(self.stde_task) @@ -1407,9 +1416,9 @@ class SingleTestRunner: async def run(self, harness: 'TestHarness') -> TestRun: if self.cmd is None: - skip_stdout = 'Not run because can not execute cross compiled binaries.' + self.stdo = 'Not run because can not execute cross compiled binaries.' harness.log_start_test(self.runobj) - self.runobj.complete_skip(skip_stdout) + self.runobj.complete_skip() else: cmd = self.cmd + self.test.cmd_args + self.options.test_args self.runobj.start(cmd) @@ -1478,8 +1487,11 @@ class SingleTestRunner: if self.runobj.needs_parsing: parse_coro = self.runobj.parse(harness, p.stdout_lines()) parse_task = asyncio.ensure_future(parse_coro) + stdo_task = stde_task = None + else: + stdo_task, stde_task = p.communicate(self.runobj, self.console_mode) + parse_task = None - stdo_task, stde_task = p.communicate(self.console_mode) returncode, result, additional_error = await p.wait(self.runobj.timeout) if parse_task is not None: @@ -1488,10 +1500,13 @@ class SingleTestRunner: additional_error = join_lines(additional_error, error) result = result or res - stdo = await stdo_task if stdo_task else '' - stde = await stde_task if stde_task else '' - stde = join_lines(stde, additional_error) - self.runobj.complete(returncode, result, stdo, stde) + 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) class TestHarness: |