From 755412b5261c448025d40214e3f2c0197e2e27bc Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 25 Nov 2020 14:47:44 +0100 Subject: mtest: read test stdout/stderr via asyncio pipes Instead of creating temporary files, get the StreamReaders from _run_subprocess's returned object. Through asyncio magic, their contents will be read as it becomes ready and then returned when the StreamReader.read future is awaited. Because of this change, the stdout and stderr can be easily preserved when TestSubprocess returns an additional_error. Signed-off-by: Paolo Bonzini --- mesonbuild/mtest.py | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index e2c2c56..e1782b3 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -32,7 +32,6 @@ import re import signal import subprocess import sys -import tempfile import textwrap import time import typing as T @@ -929,6 +928,14 @@ class TestSubprocess: self._process = p self.postwait_fn = postwait_fn # type: T.Callable[[], None] + @property + def stdout(self) -> T.Optional[asyncio.StreamReader]: + return self._process.stdout + + @property + def stderr(self) -> T.Optional[asyncio.StreamReader]: + return self._process.stderr + async def _kill(self) -> T.Optional[str]: # Python does not provide multiplatform support for # killing a process and all its children so we need @@ -1033,7 +1040,7 @@ class SingleTestRunner: return self.runobj async def _run_subprocess(self, args: T.List[str], *, - stdout: T.IO, stderr: T.IO, + stdout: int, stderr: int, env: T.Dict[str, str], cwd: T.Optional[str]) -> TestSubprocess: # Let gdb handle ^C instead of us if self.options.gdb: @@ -1088,11 +1095,12 @@ class SingleTestRunner: stdout = None stderr = None - if not self.options.verbose: - stdout = tempfile.TemporaryFile("wb+") - stderr = tempfile.TemporaryFile("wb+") if self.options.split else stdout - if self.test.protocol is TestProtocol.TAP and stderr is stdout: - stdout = tempfile.TemporaryFile("wb+") + if self.test.protocol is TestProtocol.TAP: + stdout = asyncio.subprocess.PIPE + stderr = None if self.options.verbose else asyncio.subprocess.PIPE + elif not self.options.verbose: + stdout = asyncio.subprocess.PIPE + stderr = asyncio.subprocess.PIPE if self.options.split else asyncio.subprocess.STDOUT extra_cmd = [] # type: T.List[str] if self.test.protocol is TestProtocol.GTEST: @@ -1114,24 +1122,24 @@ class SingleTestRunner: env=self.env, cwd=self.test.workdir) + stdo = stde = '' + stdo_task = stde_task = None + if stdout is not None: + stdo_task = p.stdout.read(-1) + if stderr is not None and stderr != asyncio.subprocess.STDOUT: + stde_task = p.stderr.read(-1) + returncode, result, additional_error = await p.wait(timeout) if result is TestResult.TIMEOUT and self.options.verbose: print('{} time out (After {} seconds)'.format(self.test.name, timeout)) - if additional_error is None: - if stdout is None: - stdo = '' - else: - stdout.seek(0) - stdo = decode(stdout.read()) - if stderr is None or stderr is stdout: - stde = '' - else: - stderr.seek(0) - stde = decode(stderr.read()) - else: - stdo = "" - stde = additional_error + if stdo_task is not None: + stdo = decode(await stdo_task) + if stde_task is not None: + stde = decode(await stde_task) + + if additional_error is not None: + stde += '\n' + additional_error # Print lines along the way if requested def lines() -> T.Iterator[str]: -- cgit v1.1