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 <pbonzini@redhat.com>
pull/8029/head
Paolo Bonzini 4 years ago
parent 0ccc70ae1b
commit 755412b526
  1. 50
      mesonbuild/mtest.py

@ -32,7 +32,6 @@ import re
import signal import signal
import subprocess import subprocess
import sys import sys
import tempfile
import textwrap import textwrap
import time import time
import typing as T import typing as T
@ -929,6 +928,14 @@ class TestSubprocess:
self._process = p self._process = p
self.postwait_fn = postwait_fn # type: T.Callable[[], None] 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]: async def _kill(self) -> T.Optional[str]:
# Python does not provide multiplatform support for # Python does not provide multiplatform support for
# killing a process and all its children so we need # killing a process and all its children so we need
@ -1033,7 +1040,7 @@ class SingleTestRunner:
return self.runobj return self.runobj
async def _run_subprocess(self, args: T.List[str], *, 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: env: T.Dict[str, str], cwd: T.Optional[str]) -> TestSubprocess:
# Let gdb handle ^C instead of us # Let gdb handle ^C instead of us
if self.options.gdb: if self.options.gdb:
@ -1088,11 +1095,12 @@ class SingleTestRunner:
stdout = None stdout = None
stderr = None stderr = None
if not self.options.verbose: if self.test.protocol is TestProtocol.TAP:
stdout = tempfile.TemporaryFile("wb+") stdout = asyncio.subprocess.PIPE
stderr = tempfile.TemporaryFile("wb+") if self.options.split else stdout stderr = None if self.options.verbose else asyncio.subprocess.PIPE
if self.test.protocol is TestProtocol.TAP and stderr is stdout: elif not self.options.verbose:
stdout = tempfile.TemporaryFile("wb+") stdout = asyncio.subprocess.PIPE
stderr = asyncio.subprocess.PIPE if self.options.split else asyncio.subprocess.STDOUT
extra_cmd = [] # type: T.List[str] extra_cmd = [] # type: T.List[str]
if self.test.protocol is TestProtocol.GTEST: if self.test.protocol is TestProtocol.GTEST:
@ -1114,24 +1122,24 @@ class SingleTestRunner:
env=self.env, env=self.env,
cwd=self.test.workdir) 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) returncode, result, additional_error = await p.wait(timeout)
if result is TestResult.TIMEOUT and self.options.verbose: if result is TestResult.TIMEOUT and self.options.verbose:
print('{} time out (After {} seconds)'.format(self.test.name, timeout)) print('{} time out (After {} seconds)'.format(self.test.name, timeout))
if additional_error is None: if stdo_task is not None:
if stdout is None: stdo = decode(await stdo_task)
stdo = '' if stde_task is not None:
else: stde = decode(await stde_task)
stdout.seek(0)
stdo = decode(stdout.read()) if additional_error is not None:
if stderr is None or stderr is stdout: stde += '\n' + additional_error
stde = ''
else:
stderr.seek(0)
stde = decode(stderr.read())
else:
stdo = ""
stde = additional_error
# Print lines along the way if requested # Print lines along the way if requested
def lines() -> T.Iterator[str]: def lines() -> T.Iterator[str]:

Loading…
Cancel
Save