|
|
|
@ -595,17 +595,17 @@ class JunitBuilder(TestLogger): |
|
|
|
|
'testsuite', |
|
|
|
|
name=suitename, |
|
|
|
|
tests=str(len(test.results)), |
|
|
|
|
errors=str(sum(1 for r in test.results if r in |
|
|
|
|
errors=str(sum(1 for r in test.results.values() if r in |
|
|
|
|
{TestResult.INTERRUPT, TestResult.ERROR})), |
|
|
|
|
failures=str(sum(1 for r in test.results if r in |
|
|
|
|
failures=str(sum(1 for r in test.results.values() if r in |
|
|
|
|
{TestResult.FAIL, TestResult.UNEXPECTEDPASS, TestResult.TIMEOUT})), |
|
|
|
|
skipped=str(sum(1 for r in test.results if r is TestResult.SKIP)), |
|
|
|
|
skipped=str(sum(1 for r in test.results.values() if r is TestResult.SKIP)), |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
for i, result in enumerate(test.results): |
|
|
|
|
for i, result in test.results.items(): |
|
|
|
|
# Both name and classname are required. Set them both to the |
|
|
|
|
# number of the test in a TAP test, as TAP doesn't give names. |
|
|
|
|
testcase = et.SubElement(suite, 'testcase', name=str(i), classname=str(i)) |
|
|
|
|
testcase = et.SubElement(suite, 'testcase', name=i, classname=i) |
|
|
|
|
if result is TestResult.SKIP: |
|
|
|
|
et.SubElement(testcase, 'skipped') |
|
|
|
|
elif result is TestResult.ERROR: |
|
|
|
@ -666,6 +666,28 @@ class JunitBuilder(TestLogger): |
|
|
|
|
tree.write(f, encoding='utf-8', xml_declaration=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_rust_test(stdout: str) -> T.Dict[str, TestResult]: |
|
|
|
|
"""Parse the output of rust tests.""" |
|
|
|
|
res = {} # type; T.Dict[str, TestResult] |
|
|
|
|
|
|
|
|
|
def parse_res(res: str) -> TestResult: |
|
|
|
|
if res == 'ok': |
|
|
|
|
return TestResult.OK |
|
|
|
|
elif res == 'ignored': |
|
|
|
|
return TestResult.SKIP |
|
|
|
|
elif res == 'FAILED': |
|
|
|
|
return TestResult.FAIL |
|
|
|
|
raise MesonException('Unsupported output from rust test: {}'.format(res)) |
|
|
|
|
|
|
|
|
|
for line in stdout.splitlines(): |
|
|
|
|
if line.startswith('test ') and not line.startswith('test result'): |
|
|
|
|
_, name, _, result = line.split(' ') |
|
|
|
|
name = name.replace('::', '.') |
|
|
|
|
res[name] = parse_res(result) |
|
|
|
|
|
|
|
|
|
return res |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestRun: |
|
|
|
|
TEST_NUM = 0 |
|
|
|
|
|
|
|
|
@ -675,7 +697,7 @@ class TestRun: |
|
|
|
|
self.test = test |
|
|
|
|
self._num = None # type: T.Optional[int] |
|
|
|
|
self.name = name |
|
|
|
|
self.results = list() # type: T.List[TestResult] |
|
|
|
|
self.results: T.Dict[str, TestResult] = {} |
|
|
|
|
self.returncode = 0 |
|
|
|
|
self.starttime = None # type: T.Optional[float] |
|
|
|
|
self.duration = None # type: T.Optional[float] |
|
|
|
@ -713,23 +735,23 @@ class TestRun: |
|
|
|
|
res = TestResult.EXPECTEDFAIL if bool(returncode) else TestResult.UNEXPECTEDPASS |
|
|
|
|
else: |
|
|
|
|
res = TestResult.FAIL if bool(returncode) else TestResult.OK |
|
|
|
|
self.complete(res, [], returncode, stdo, stde, cmd, **kwargs) |
|
|
|
|
self.complete(res, {}, returncode, stdo, stde, cmd, **kwargs) |
|
|
|
|
|
|
|
|
|
def complete_tap(self, returncode: int, stdo: str, stde: str, cmd: T.List[str]) -> None: |
|
|
|
|
res = None # type: T.Optional[TestResult] |
|
|
|
|
results = [] # type: T.List[TestResult] |
|
|
|
|
results = {} # type: T.Dict[str, TestResult] |
|
|
|
|
failed = False |
|
|
|
|
|
|
|
|
|
for i in TAPParser(io.StringIO(stdo)).parse(): |
|
|
|
|
for n, i in enumerate(TAPParser(io.StringIO(stdo)).parse()): |
|
|
|
|
if isinstance(i, TAPParser.Bailout): |
|
|
|
|
results.append(TestResult.ERROR) |
|
|
|
|
results[str(n)] = TestResult.ERROR |
|
|
|
|
failed = True |
|
|
|
|
elif isinstance(i, TAPParser.Test): |
|
|
|
|
results.append(i.result) |
|
|
|
|
results[str(n)] = i.result |
|
|
|
|
if i.result not in {TestResult.OK, TestResult.EXPECTEDFAIL, TestResult.SKIP}: |
|
|
|
|
failed = True |
|
|
|
|
elif isinstance(i, TAPParser.Error): |
|
|
|
|
results.append(TestResult.ERROR) |
|
|
|
|
results[str(n)] = TestResult.ERROR |
|
|
|
|
stde += '\nTAP parsing error: ' + i.message |
|
|
|
|
failed = True |
|
|
|
|
|
|
|
|
@ -739,7 +761,7 @@ class TestRun: |
|
|
|
|
|
|
|
|
|
if res is None: |
|
|
|
|
# Now determine the overall result of the test based on the outcome of the subcases |
|
|
|
|
if all(t is TestResult.SKIP for t in results): |
|
|
|
|
if all(t is TestResult.SKIP for t in results.values()): |
|
|
|
|
# This includes the case where num_tests is zero |
|
|
|
|
res = TestResult.SKIP |
|
|
|
|
elif self.should_fail: |
|
|
|
@ -749,6 +771,21 @@ class TestRun: |
|
|
|
|
|
|
|
|
|
self.complete(res, results, returncode, stdo, stde, cmd) |
|
|
|
|
|
|
|
|
|
def complete_rust(self, returncode: int, stdo: str, stde: str, cmd: T.List[str]) -> None: |
|
|
|
|
results = parse_rust_test(stdo) |
|
|
|
|
|
|
|
|
|
failed = TestResult.FAIL in results.values() |
|
|
|
|
# Now determine the overall result of the test based on the outcome of the subcases |
|
|
|
|
if all(t is TestResult.SKIP for t in results.values()): |
|
|
|
|
# This includes the case where num_tests is zero |
|
|
|
|
res = TestResult.SKIP |
|
|
|
|
elif self.should_fail: |
|
|
|
|
res = TestResult.EXPECTEDFAIL if failed else TestResult.UNEXPECTEDPASS |
|
|
|
|
else: |
|
|
|
|
res = TestResult.FAIL if failed else TestResult.OK |
|
|
|
|
|
|
|
|
|
self.complete(res, results, returncode, stdo, stde, cmd) |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def num(self) -> int: |
|
|
|
|
if self._num is None: |
|
|
|
@ -756,13 +793,13 @@ class TestRun: |
|
|
|
|
self._num = TestRun.TEST_NUM |
|
|
|
|
return self._num |
|
|
|
|
|
|
|
|
|
def complete(self, res: TestResult, results: T.List[TestResult], |
|
|
|
|
def complete(self, res: TestResult, results: T.Dict[str, TestResult], |
|
|
|
|
returncode: int, |
|
|
|
|
stdo: T.Optional[str], stde: T.Optional[str], |
|
|
|
|
cmd: T.List[str], *, junit: T.Optional[et.ElementTree] = None) -> None: |
|
|
|
|
assert isinstance(res, TestResult) |
|
|
|
|
self.res = res |
|
|
|
|
self.results = results |
|
|
|
|
self.results = results # May be empty |
|
|
|
|
self.returncode = returncode |
|
|
|
|
self.duration = time.time() - self.starttime |
|
|
|
|
self.stdo = stdo |
|
|
|
@ -906,7 +943,7 @@ class SingleTestRunner: |
|
|
|
|
self.runobj.start() |
|
|
|
|
if cmd is None: |
|
|
|
|
skip_stdout = 'Not run because can not execute cross compiled binaries.' |
|
|
|
|
self.runobj.complete(TestResult.SKIP, [], GNU_SKIP_RETURNCODE, skip_stdout, None, None) |
|
|
|
|
self.runobj.complete(TestResult.SKIP, {}, GNU_SKIP_RETURNCODE, skip_stdout, None, None) |
|
|
|
|
else: |
|
|
|
|
wrap = TestHarness.get_wrapper(self.options) |
|
|
|
|
if self.options.gdb: |
|
|
|
@ -1063,12 +1100,14 @@ class SingleTestRunner: |
|
|
|
|
stdo = "" |
|
|
|
|
stde = additional_error |
|
|
|
|
if result: |
|
|
|
|
self.runobj.complete(result, [], returncode, stdo, stde, cmd) |
|
|
|
|
self.runobj.complete(result, {}, returncode, stdo, stde, cmd) |
|
|
|
|
else: |
|
|
|
|
if 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) |
|
|
|
|
elif self.test.protocol is TestProtocol.RUST: |
|
|
|
|
return self.runobj.complete_rust(returncode, stdo, stde, cmd) |
|
|
|
|
else: |
|
|
|
|
if self.options.verbose: |
|
|
|
|
print(stdo, end='') |
|
|
|
|