aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHemmo Nieminen <hemmo.nieminen@iki.fi>2022-02-01 00:00:00 +0200
committerHemmo Nieminen <hemmo.nieminen@iki.fi>2022-03-23 00:00:00 +0200
commit2aa3250ae4c973d3d12e36717d9d0224e04d8888 (patch)
tree6a8a7a69dd7cbec078f0dda0b0e5fa7ab3eacbc8
parent8a5ba78702d855e47eb22ba6e6355616e3866a75 (diff)
downloadmeson-2aa3250ae4c973d3d12e36717d9d0224e04d8888.zip
meson-2aa3250ae4c973d3d12e36717d9d0224e04d8888.tar.gz
meson-2aa3250ae4c973d3d12e36717d9d0224e04d8888.tar.bz2
mtest: store all test results directly to TestRun
Store return code, test result and additional error directly to the relevant TestRun instance. This reduces the number of individual arguments to other relevant functions that need to be passed around and thus simplifies the code. The test output (and error) were earlier similarly moved to be stored directly to the TestRun instance for the same reason.
-rw-r--r--mesonbuild/mtest.py117
1 files changed, 57 insertions, 60 deletions
diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py
index bce7cc9..58fb8cb 100644
--- a/mesonbuild/mtest.py
+++ b/mesonbuild/mtest.py
@@ -885,11 +885,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
@@ -935,17 +936,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
@@ -958,14 +958,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'
@@ -973,7 +975,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':
@@ -984,30 +986,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)
@@ -1020,7 +1021,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
@@ -1030,17 +1031,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):
@@ -1052,13 +1051,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
@@ -1068,7 +1069,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)
@@ -1089,14 +1090,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
@@ -1304,26 +1309,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:
@@ -1483,7 +1486,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)
@@ -1492,21 +1494,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: