Merge pull request #8225 from bonzini/mtest-asyncio-cleanups
mtest: cleanups and bugfixes
This commit is contained in:
commit
4d5f6876f9
|
@ -147,6 +147,13 @@ def print_safe(s: str) -> None:
|
|||
s = s.encode('ascii', errors='backslashreplace').decode('ascii')
|
||||
print(s)
|
||||
|
||||
def join_lines(a: str, b: str) -> str:
|
||||
if not a:
|
||||
return b
|
||||
if not b:
|
||||
return a
|
||||
return a + '\n' + b
|
||||
|
||||
def returncode_to_status(retcode: int) -> str:
|
||||
# Note: We can't use `os.WIFSIGNALED(result.returncode)` and the related
|
||||
# functions here because the status returned by subprocess is munged. It
|
||||
|
@ -180,6 +187,19 @@ class TestException(MesonException):
|
|||
pass
|
||||
|
||||
|
||||
@enum.unique
|
||||
class ConsoleUser(enum.Enum):
|
||||
|
||||
# the logger can use the console
|
||||
LOGGER = 0
|
||||
|
||||
# the console is used by gdb
|
||||
GDB = 1
|
||||
|
||||
# the console is used to write stdout/stderr
|
||||
STDOUT = 2
|
||||
|
||||
|
||||
@enum.unique
|
||||
class TestResult(enum.Enum):
|
||||
|
||||
|
@ -522,9 +542,7 @@ class ConsoleLogger(TestLogger):
|
|||
|
||||
self.test_count = harness.test_count
|
||||
|
||||
# In verbose mode, the progress report gets in the way of the tests'
|
||||
# stdout and stderr.
|
||||
if self.is_tty() and not harness.options.verbose:
|
||||
if self.is_tty() and not harness.need_console:
|
||||
# Account for "[aa-bb/cc] OO " in the progress report
|
||||
self.max_left_width = 3 * len(str(self.test_count)) + 8
|
||||
self.progress_task = asyncio.ensure_future(report_progress())
|
||||
|
@ -731,6 +749,10 @@ class JunitBuilder(TestLogger):
|
|||
|
||||
class TestRun:
|
||||
TEST_NUM = 0
|
||||
PROTOCOL_TO_CLASS: T.Dict[TestProtocol, T.Type['TestRun']] = {}
|
||||
|
||||
def __new__(cls, test: TestSerialisation, *args: T.Any, **kwargs: T.Any) -> T.Any:
|
||||
return super().__new__(TestRun.PROTOCOL_TO_CLASS[test.protocol])
|
||||
|
||||
def __init__(self, test: TestSerialisation, test_env: T.Dict[str, str],
|
||||
name: str, timeout: T.Optional[int]):
|
||||
|
@ -746,92 +768,15 @@ class TestRun:
|
|||
self.stdo = None # type: T.Optional[str]
|
||||
self.stde = None # type: T.Optional[str]
|
||||
self.cmd = None # type: T.Optional[T.List[str]]
|
||||
self.env = dict() # type: T.Dict[str, str]
|
||||
self.env = test_env # type: T.Dict[str, str]
|
||||
self.should_fail = test.should_fail
|
||||
self.project = test.project_name
|
||||
self.junit = None # type: T.Optional[et.ElementTree]
|
||||
|
||||
def start(self) -> None:
|
||||
def start(self, cmd: T.List[str]) -> None:
|
||||
self.res = TestResult.RUNNING
|
||||
self.starttime = time.time()
|
||||
|
||||
def complete_gtest(self, returncode: int,
|
||||
stdo: T.Optional[str], stde: T.Optional[str],
|
||||
cmd: T.List[str]) -> None:
|
||||
filename = '{}.xml'.format(self.test.name)
|
||||
if self.test.workdir:
|
||||
filename = os.path.join(self.test.workdir, filename)
|
||||
tree = et.parse(filename)
|
||||
|
||||
self.complete_exitcode(returncode, stdo, stde, cmd, junit=tree)
|
||||
|
||||
def complete_exitcode(self, returncode: int,
|
||||
stdo: T.Optional[str], stde: T.Optional[str],
|
||||
cmd: T.List[str],
|
||||
**kwargs: T.Any) -> None:
|
||||
if returncode == GNU_SKIP_RETURNCODE:
|
||||
res = TestResult.SKIP
|
||||
elif returncode == GNU_ERROR_RETURNCODE:
|
||||
res = TestResult.ERROR
|
||||
else:
|
||||
res = TestResult.FAIL if bool(returncode) else TestResult.OK
|
||||
self.complete(returncode, res, stdo, stde, cmd, **kwargs)
|
||||
|
||||
async def parse_tap(self, lines: T.AsyncIterator[str]) -> T.Tuple[TestResult, str]:
|
||||
res = TestResult.OK
|
||||
error = ''
|
||||
|
||||
async for i in TAPParser().parse_async(lines):
|
||||
if isinstance(i, TAPParser.Bailout):
|
||||
res = TestResult.ERROR
|
||||
elif isinstance(i, TAPParser.Test):
|
||||
self.results.append(i)
|
||||
if i.result.is_bad():
|
||||
res = TestResult.FAIL
|
||||
elif isinstance(i, TAPParser.Error):
|
||||
error = '\nTAP 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
|
||||
|
||||
def complete_tap(self, returncode: int, res: TestResult,
|
||||
stdo: str, stde: str, cmd: T.List[str]) -> None:
|
||||
if returncode != 0 and not res.was_killed():
|
||||
res = TestResult.ERROR
|
||||
stde += '\n(test program exited with status code {})'.format(returncode,)
|
||||
|
||||
self.complete(returncode, res, stdo, stde, cmd)
|
||||
|
||||
async def parse_rust(self, lines: T.AsyncIterator[str]) -> T.Tuple[TestResult, str]:
|
||||
def parse_res(n: int, name: str, result: str) -> TAPParser.Test:
|
||||
if result == 'ok':
|
||||
return TAPParser.Test(n, name, TestResult.OK, None)
|
||||
elif result == 'ignored':
|
||||
return TAPParser.Test(n, name, TestResult.SKIP, None)
|
||||
elif result == 'FAILED':
|
||||
return TAPParser.Test(n, name, TestResult.FAIL, None)
|
||||
return TAPParser.Test(n, name, TestResult.ERROR,
|
||||
'Unsupported output from rust test: {}'.format(result))
|
||||
|
||||
n = 1
|
||||
async for line in lines:
|
||||
if line.startswith('test ') and not line.startswith('test result'):
|
||||
_, name, _, result = line.rstrip().split(' ')
|
||||
name = name.replace('::', '.')
|
||||
self.results.append(parse_res(n, name, result))
|
||||
n += 1
|
||||
|
||||
if all(t.result is TestResult.SKIP for t in self.results):
|
||||
# This includes the case where self.results is empty
|
||||
return TestResult.SKIP, ''
|
||||
elif any(t.result is TestResult.ERROR for t in self.results):
|
||||
return TestResult.ERROR, ''
|
||||
elif any(t.result is TestResult.FAIL for t in self.results):
|
||||
return TestResult.FAIL, ''
|
||||
return TestResult.OK, ''
|
||||
self.cmd = cmd
|
||||
|
||||
@property
|
||||
def num(self) -> int:
|
||||
|
@ -856,9 +801,8 @@ class TestRun:
|
|||
return '{}/{} subtests passed'.format(passed, ran)
|
||||
return ''
|
||||
|
||||
def complete(self, returncode: int, res: TestResult,
|
||||
stdo: T.Optional[str], stde: T.Optional[str],
|
||||
cmd: T.List[str], *, junit: T.Optional[et.ElementTree] = None) -> None:
|
||||
def _complete(self, returncode: int, res: TestResult,
|
||||
stdo: T.Optional[str], stde: T.Optional[str]) -> 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
|
||||
|
@ -868,8 +812,14 @@ class TestRun:
|
|||
self.duration = time.time() - self.starttime
|
||||
self.stdo = stdo
|
||||
self.stde = stde
|
||||
self.cmd = cmd
|
||||
self.junit = junit
|
||||
|
||||
def complete_skip(self, message: str) -> None:
|
||||
self.starttime = time.time()
|
||||
self._complete(GNU_SKIP_RETURNCODE, TestResult.SKIP, message, None)
|
||||
|
||||
def complete(self, returncode: int, res: TestResult,
|
||||
stdo: T.Optional[str], stde: T.Optional[str]) -> None:
|
||||
self._complete(returncode, res, stdo, stde)
|
||||
|
||||
def get_log(self) -> str:
|
||||
res = '--- command ---\n'
|
||||
|
@ -906,6 +856,118 @@ class TestRun:
|
|||
log += '\n'.join(lines[-100:])
|
||||
return log
|
||||
|
||||
@property
|
||||
def needs_parsing(self) -> bool:
|
||||
return False
|
||||
|
||||
async def parse(self, lines: T.AsyncIterator[str]) -> T.Tuple[TestResult, str]:
|
||||
async for l in lines:
|
||||
pass
|
||||
return TestResult.OK, ''
|
||||
|
||||
|
||||
class TestRunExitCode(TestRun):
|
||||
|
||||
def complete(self, returncode: int, res: TestResult,
|
||||
stdo: T.Optional[str], stde: T.Optional[str]) -> None:
|
||||
if res:
|
||||
pass
|
||||
elif returncode == GNU_SKIP_RETURNCODE:
|
||||
res = TestResult.SKIP
|
||||
elif returncode == GNU_ERROR_RETURNCODE:
|
||||
res = TestResult.ERROR
|
||||
else:
|
||||
res = TestResult.FAIL if bool(returncode) else TestResult.OK
|
||||
super().complete(returncode, res, stdo, stde)
|
||||
|
||||
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:
|
||||
filename = '{}.xml'.format(self.test.name)
|
||||
if self.test.workdir:
|
||||
filename = os.path.join(self.test.workdir, filename)
|
||||
|
||||
self.junit = et.parse(filename)
|
||||
super().complete(returncode, res, stdo, stde)
|
||||
|
||||
TestRun.PROTOCOL_TO_CLASS[TestProtocol.GTEST] = TestRunGTest
|
||||
|
||||
|
||||
class TestRunTAP(TestRun):
|
||||
@property
|
||||
def needs_parsing(self) -> bool:
|
||||
return True
|
||||
|
||||
def complete(self, returncode: int, res: TestResult,
|
||||
stdo: str, stde: str) -> None:
|
||||
if returncode != 0 and not res.was_killed():
|
||||
res = TestResult.ERROR
|
||||
stde += '\n(test program exited with status code {})'.format(returncode,)
|
||||
|
||||
super().complete(returncode, res, stdo, stde)
|
||||
|
||||
async def parse(self, lines: T.AsyncIterator[str]) -> T.Tuple[TestResult, str]:
|
||||
res = TestResult.OK
|
||||
error = ''
|
||||
|
||||
async for i in TAPParser().parse_async(lines):
|
||||
if isinstance(i, TAPParser.Bailout):
|
||||
res = TestResult.ERROR
|
||||
elif isinstance(i, TAPParser.Test):
|
||||
self.results.append(i)
|
||||
if i.result.is_bad():
|
||||
res = TestResult.FAIL
|
||||
elif isinstance(i, TAPParser.Error):
|
||||
error = '\nTAP 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
|
||||
|
||||
TestRun.PROTOCOL_TO_CLASS[TestProtocol.TAP] = TestRunTAP
|
||||
|
||||
|
||||
class TestRunRust(TestRun):
|
||||
@property
|
||||
def needs_parsing(self) -> bool:
|
||||
return True
|
||||
|
||||
async def parse(self, lines: T.AsyncIterator[str]) -> T.Tuple[TestResult, str]:
|
||||
def parse_res(n: int, name: str, result: str) -> TAPParser.Test:
|
||||
if result == 'ok':
|
||||
return TAPParser.Test(n, name, TestResult.OK, None)
|
||||
elif result == 'ignored':
|
||||
return TAPParser.Test(n, name, TestResult.SKIP, None)
|
||||
elif result == 'FAILED':
|
||||
return TAPParser.Test(n, name, TestResult.FAIL, None)
|
||||
return TAPParser.Test(n, name, TestResult.ERROR,
|
||||
'Unsupported output from rust test: {}'.format(result))
|
||||
|
||||
n = 1
|
||||
async for line in lines:
|
||||
if line.startswith('test ') and not line.startswith('test result'):
|
||||
_, name, _, result = line.rstrip().split(' ')
|
||||
name = name.replace('::', '.')
|
||||
self.results.append(parse_res(n, name, result))
|
||||
n += 1
|
||||
|
||||
if all(t.result is TestResult.SKIP for t in self.results):
|
||||
# This includes the case where self.results is empty
|
||||
return TestResult.SKIP, ''
|
||||
elif any(t.result is TestResult.ERROR for t in self.results):
|
||||
return TestResult.ERROR, ''
|
||||
elif any(t.result is TestResult.FAIL for t in self.results):
|
||||
return TestResult.FAIL, ''
|
||||
return TestResult.OK, ''
|
||||
|
||||
TestRun.PROTOCOL_TO_CLASS[TestProtocol.RUST] = TestRunRust
|
||||
|
||||
|
||||
def decode(stream: T.Union[None, bytes]) -> str:
|
||||
if stream is None:
|
||||
return ''
|
||||
|
@ -914,6 +976,35 @@ def decode(stream: T.Union[None, bytes]) -> str:
|
|||
except UnicodeDecodeError:
|
||||
return stream.decode('iso-8859-1', errors='ignore')
|
||||
|
||||
async def read_decode(reader: asyncio.StreamReader, console_mode: ConsoleUser) -> str:
|
||||
if console_mode is not ConsoleUser.STDOUT:
|
||||
return decode(await reader.read(-1))
|
||||
|
||||
stdo_lines = []
|
||||
while not reader.at_eof():
|
||||
line = decode(await reader.readline())
|
||||
stdo_lines.append(line)
|
||||
print(line, end='', flush=True)
|
||||
return ''.join(stdo_lines)
|
||||
|
||||
# Extract lines out of the StreamReader. Print them
|
||||
# along the way if requested, and at the end collect
|
||||
# them all into a future.
|
||||
async def read_decode_lines(reader: asyncio.StreamReader, f: 'asyncio.Future[str]',
|
||||
console_mode: ConsoleUser) -> T.AsyncIterator[str]:
|
||||
stdo_lines = []
|
||||
try:
|
||||
while not reader.at_eof():
|
||||
line = decode(await reader.readline())
|
||||
stdo_lines.append(line)
|
||||
if console_mode is ConsoleUser.STDOUT:
|
||||
print(line, end='', flush=True)
|
||||
yield line
|
||||
except Exception as e:
|
||||
f.set_exception(e)
|
||||
finally:
|
||||
f.set_result(''.join(stdo_lines))
|
||||
|
||||
def run_with_mono(fname: str) -> bool:
|
||||
return fname.endswith('.exe') and not (is_windows() or is_cygwin())
|
||||
|
||||
|
@ -971,17 +1062,32 @@ async def complete_all(futures: T.Iterable[asyncio.Future]) -> None:
|
|||
f.result()
|
||||
|
||||
class TestSubprocess:
|
||||
def __init__(self, p: asyncio.subprocess.Process, postwait_fn: T.Callable[[], None] = None):
|
||||
def __init__(self, p: asyncio.subprocess.Process,
|
||||
stdout: T.Optional[int], stderr: T.Optional[int],
|
||||
postwait_fn: T.Callable[[], None] = None):
|
||||
self._process = p
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
self.stdo_task = None # type: T.Optional[T.Awaitable[str]]
|
||||
self.stde_task = None # type: T.Optional[T.Awaitable[str]]
|
||||
self.postwait_fn = postwait_fn # type: T.Callable[[], None]
|
||||
|
||||
@property
|
||||
def stdout(self) -> T.Optional[asyncio.StreamReader]:
|
||||
return self._process.stdout
|
||||
def stdout_lines(self, console_mode: ConsoleUser) -> T.AsyncIterator[str]:
|
||||
self.stdo_task = asyncio.get_event_loop().create_future()
|
||||
return read_decode_lines(self._process.stdout, self.stdo_task, console_mode)
|
||||
|
||||
@property
|
||||
def stderr(self) -> T.Optional[asyncio.StreamReader]:
|
||||
return self._process.stderr
|
||||
def communicate(self, console_mode: ConsoleUser) -> T.Tuple[T.Optional[T.Awaitable[str]],
|
||||
T.Optional[T.Awaitable[str]]]:
|
||||
# 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_task = read_decode(self._process.stdout, console_mode)
|
||||
self.stdo_task = asyncio.ensure_future(decode_task)
|
||||
if self.stderr is not None and self.stderr != asyncio.subprocess.STDOUT:
|
||||
decode_task = read_decode(self._process.stderr, console_mode)
|
||||
self.stde_task = asyncio.ensure_future(decode_task)
|
||||
|
||||
return self.stdo_task, self.stde_task
|
||||
|
||||
async def _kill(self) -> T.Optional[str]:
|
||||
# Python does not provide multiplatform support for
|
||||
|
@ -1061,6 +1167,13 @@ class SingleTestRunner:
|
|||
|
||||
self.runobj = TestRun(test, test_env, name, timeout)
|
||||
|
||||
if self.options.gdb:
|
||||
self.console_mode = ConsoleUser.GDB
|
||||
elif self.options.verbose and not self.runobj.needs_parsing:
|
||||
self.console_mode = ConsoleUser.STDOUT
|
||||
else:
|
||||
self.console_mode = ConsoleUser.LOGGER
|
||||
|
||||
def _get_cmd(self) -> T.Optional[T.List[str]]:
|
||||
if self.test.fname[0].endswith('.jar'):
|
||||
return ['java', '-jar'] + self.test.fname
|
||||
|
@ -1091,10 +1204,9 @@ class SingleTestRunner:
|
|||
|
||||
async def run(self) -> TestRun:
|
||||
cmd = self._get_cmd()
|
||||
self.runobj.start()
|
||||
if cmd is None:
|
||||
skip_stdout = 'Not run because can not execute cross compiled binaries.'
|
||||
self.runobj.complete(GNU_SKIP_RETURNCODE, TestResult.SKIP, skip_stdout, None, None)
|
||||
self.runobj.complete_skip(skip_stdout)
|
||||
else:
|
||||
wrap = TestHarness.get_wrapper(self.options)
|
||||
await self._run_cmd(wrap + cmd + self.test.cmd_args + self.options.test_args)
|
||||
|
@ -1131,7 +1243,8 @@ class SingleTestRunner:
|
|||
env=env,
|
||||
cwd=cwd,
|
||||
preexec_fn=preexec_fn if not is_windows() else None)
|
||||
return TestSubprocess(p, postwait_fn=postwait_fn if not is_windows() else None)
|
||||
return TestSubprocess(p, stdout=stdout, stderr=stderr,
|
||||
postwait_fn=postwait_fn if not is_windows() else None)
|
||||
|
||||
async def _run_cmd(self, cmd: T.List[str]) -> None:
|
||||
if self.test.extra_paths:
|
||||
|
@ -1154,14 +1267,15 @@ class SingleTestRunner:
|
|||
if ('MALLOC_PERTURB_' not in self.env or not self.env['MALLOC_PERTURB_']) and not self.options.benchmark:
|
||||
self.env['MALLOC_PERTURB_'] = str(random.randint(1, 255))
|
||||
|
||||
stdout = None
|
||||
stderr = None
|
||||
if self.test.protocol is TestProtocol.TAP:
|
||||
self.runobj.start(cmd)
|
||||
if self.console_mode is ConsoleUser.GDB:
|
||||
stdout = None
|
||||
stderr = None
|
||||
else:
|
||||
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
|
||||
stderr = asyncio.subprocess.STDOUT \
|
||||
if not self.options.split and not self.runobj.needs_parsing \
|
||||
else asyncio.subprocess.PIPE
|
||||
|
||||
extra_cmd = [] # type: T.List[str]
|
||||
if self.test.protocol is TestProtocol.GTEST:
|
||||
|
@ -1176,60 +1290,23 @@ class SingleTestRunner:
|
|||
env=self.env,
|
||||
cwd=self.test.workdir)
|
||||
|
||||
stdo = stde = ''
|
||||
stdo_task = stde_task = parse_task = None
|
||||
|
||||
# Extract lines out of the StreamReader and print them
|
||||
# along the way if requested
|
||||
async def lines() -> T.AsyncIterator[str]:
|
||||
stdo_lines = []
|
||||
reader = p.stdout
|
||||
while not reader.at_eof():
|
||||
line = decode(await reader.readline())
|
||||
stdo_lines.append(line)
|
||||
if self.options.verbose:
|
||||
print(line, end='')
|
||||
yield line
|
||||
|
||||
nonlocal stdo
|
||||
stdo = ''.join(stdo_lines)
|
||||
|
||||
if self.test.protocol is TestProtocol.TAP:
|
||||
parse_task = self.runobj.parse_tap(lines())
|
||||
elif self.test.protocol is TestProtocol.RUST:
|
||||
parse_task = self.runobj.parse_rust(lines())
|
||||
elif 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)
|
||||
parse_task = None
|
||||
if self.runobj.needs_parsing:
|
||||
parse_task = self.runobj.parse(p.stdout_lines(self.console_mode))
|
||||
|
||||
stdo_task, stde_task = p.communicate(self.console_mode)
|
||||
returncode, result, additional_error = await p.wait(self.runobj.timeout)
|
||||
if result is TestResult.TIMEOUT and self.options.verbose:
|
||||
print('{} time out (After {} seconds)'.format(self.test.name, self.runobj.timeout))
|
||||
|
||||
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
|
||||
|
||||
if parse_task is not None:
|
||||
res, error = await parse_task
|
||||
if error:
|
||||
stde += '\n' + error
|
||||
additional_error = join_lines(additional_error, error)
|
||||
result = result or res
|
||||
if self.test.protocol is TestProtocol.TAP:
|
||||
self.runobj.complete_tap(returncode, result, stdo, stde, cmd)
|
||||
return
|
||||
|
||||
if result:
|
||||
self.runobj.complete(returncode, result, stdo, stde, cmd)
|
||||
elif self.test.protocol is TestProtocol.EXITCODE:
|
||||
self.runobj.complete_exitcode(returncode, stdo, stde, cmd)
|
||||
elif self.test.protocol is TestProtocol.GTEST:
|
||||
self.runobj.complete_gtest(returncode, stdo, stde, cmd)
|
||||
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)
|
||||
|
||||
|
||||
class TestHarness:
|
||||
|
@ -1247,6 +1324,7 @@ class TestHarness:
|
|||
self.is_run = False
|
||||
self.loggers = [] # type: T.List[TestLogger]
|
||||
self.loggers.append(ConsoleLogger())
|
||||
self.need_console = False
|
||||
|
||||
if self.options.benchmark:
|
||||
self.tests = load_benchmarks(options.wd)
|
||||
|
@ -1397,6 +1475,9 @@ class TestHarness:
|
|||
runners = [self.get_test_runner(test) for test in tests]
|
||||
self.duration_max_len = max([len(str(int(runner.timeout or 99)))
|
||||
for runner in runners])
|
||||
# Disable the progress report if it gets in the way
|
||||
self.need_console = any((runner.console_mode is not ConsoleUser.LOGGER
|
||||
for runner in runners))
|
||||
self.run_tests(runners)
|
||||
finally:
|
||||
os.chdir(startdir)
|
||||
|
|
Loading…
Reference in New Issue