diff options
author | Dylan Baker <dylan@pnwbakers.com> | 2020-04-30 15:36:17 -0700 |
---|---|---|
committer | Dylan Baker <dylan@pnwbakers.com> | 2020-05-04 11:33:19 -0700 |
commit | 083c5f635741a29f93f95c817601dbc66207699d (patch) | |
tree | b0bcbb26bc160bb0fd6dcc496e733c5317a555da /mesonbuild | |
parent | 0c51762463abd72526ac84f3cfeaa286186ae1d7 (diff) | |
download | meson-083c5f635741a29f93f95c817601dbc66207699d.zip meson-083c5f635741a29f93f95c817601dbc66207699d.tar.gz meson-083c5f635741a29f93f95c817601dbc66207699d.tar.bz2 |
Add native support for gtest tests
Gtest can output junit results with a command line switch. We can parse
this to get more detailed results than the returncode, and put those in
our own Junit output. We basically just throw away the top level
'testsuites' object, then fixup the names of the tests, and shove that
into our junit.
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/backend/backends.py | 5 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 6 | ||||
-rw-r--r-- | mesonbuild/mtest.py | 54 |
3 files changed, 56 insertions, 9 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index ad01011..d41cef1 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -42,6 +42,7 @@ class TestProtocol(enum.Enum): EXITCODE = 0 TAP = 1 + GTEST = 2 @classmethod def from_str(cls, string: str) -> 'TestProtocol': @@ -49,11 +50,15 @@ class TestProtocol(enum.Enum): return cls.EXITCODE elif string == 'tap': return cls.TAP + elif string == 'gtest': + return cls.GTEST raise MesonException('unknown test format {}'.format(string)) def __str__(self) -> str: if self is self.EXITCODE: return 'exitcode' + elif self is self.GTEST: + return 'gtest' return 'tap' diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 7b8ca63..c0be92a 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -3772,6 +3772,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self @FeatureNewKwargs('test', '0.52.0', ['priority']) @permittedKwargs(permitted_kwargs['test']) def func_test(self, node, args, kwargs): + if kwargs.get('protocol') == 'gtest': + FeatureNew('"gtest" protocol for tests', '0.55.0').use(self.subproject) self.add_test(node, args, kwargs, True) def unpack_env_kwarg(self, kwargs) -> build.EnvironmentVariables: @@ -3823,8 +3825,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self if not isinstance(timeout, int): raise InterpreterException('Timeout must be an integer.') protocol = kwargs.get('protocol', 'exitcode') - if protocol not in ('exitcode', 'tap'): - raise InterpreterException('Protocol must be "exitcode" or "tap".') + if protocol not in {'exitcode', 'tap', 'gtest'}: + raise InterpreterException('Protocol must be "exitcode", "tap", or "gtest".') suite = [] prj = self.subproject if self.is_subproject() else self.build.project_name for s in mesonlib.stringlistify(kwargs.get('suite', '')): diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 69da400..4592c90 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -94,7 +94,10 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: help='List available tests.') parser.add_argument('--wrapper', default=None, dest='wrapper', type=split_args, help='wrapper to run tests with (e.g. Valgrind)') - parser.add_argument('-C', default='.', dest='wd', type=os.path.abspath, + parser.add_argument('-C', default='.', dest='wd', + # https://github.com/python/typeshed/issues/3107 + # https://github.com/python/mypy/issues/7177 + type=os.path.abspath, # type: ignore help='directory to cd into before running') parser.add_argument('--suite', default=[], dest='include_suites', action='append', metavar='SUITE', help='Only run tests belonging to the given suite.') @@ -349,6 +352,19 @@ class JunitBuilder: def log(self, name: str, test: 'TestRun') -> None: """Log a single test case.""" + if test.junit is not None: + for suite in test.junit.findall('.//testsuite'): + # Assume that we don't need to merge anything here... + suite.attrib['name'] = '{}.{}.{}'.format(test.project, name, suite.attrib['name']) + + # GTest can inject invalid attributes + for case in suite.findall('.//testcase[@result]'): + del case.attrib['result'] + for case in suite.findall('.//testcase[@timestamp]'): + del case.attrib['timestamp'] + self.root.append(suite) + return + # In this case we have a test binary with multiple results. # We want to record this so that each result is recorded # separately @@ -430,10 +446,24 @@ class JunitBuilder: class TestRun: @classmethod + def make_gtest(cls, test: 'TestSerialisation', test_env: T.Dict[str, str], + returncode: int, starttime: float, duration: float, + stdo: T.Optional[str], stde: T.Optional[str], + cmd: T.Optional[T.List[str]]) -> 'TestRun': + filename = '{}.xml'.format(test.name) + if test.workdir: + filename = os.path.join(test.workdir, filename) + tree = et.parse(filename) + + return cls.make_exitcode( + test, test_env, returncode, starttime, duration, stdo, stde, cmd, + junit=tree) + + @classmethod def make_exitcode(cls, test: 'TestSerialisation', test_env: T.Dict[str, str], returncode: int, starttime: float, duration: float, stdo: T.Optional[str], stde: T.Optional[str], - cmd: T.Optional[T.List[str]]) -> 'TestRun': + cmd: T.Optional[T.List[str]], **kwargs) -> 'TestRun': if returncode == GNU_SKIP_RETURNCODE: res = TestResult.SKIP elif returncode == GNU_ERROR_RETURNCODE: @@ -442,15 +472,15 @@ class TestRun: res = TestResult.EXPECTEDFAIL if bool(returncode) else TestResult.UNEXPECTEDPASS else: res = TestResult.FAIL if bool(returncode) else TestResult.OK - return cls(test, test_env, res, [], returncode, starttime, duration, stdo, stde, cmd) + return cls(test, test_env, res, [], returncode, starttime, duration, stdo, stde, cmd, **kwargs) @classmethod def make_tap(cls, test: 'TestSerialisation', test_env: T.Dict[str, str], returncode: int, starttime: float, duration: float, stdo: str, stde: str, cmd: T.Optional[T.List[str]]) -> 'TestRun': - res = None # T.Optional[TestResult] - results = [] # T.List[TestResult] + res = None # type: T.Optional[TestResult] + results = [] # type: T.List[TestResult] failed = False for i in TAPParser(io.StringIO(stdo)).parse(): @@ -486,7 +516,7 @@ class TestRun: res: TestResult, results: T.List[TestResult], returncode: int, starttime: float, duration: float, stdo: T.Optional[str], stde: T.Optional[str], - cmd: T.Optional[T.List[str]]): + cmd: T.Optional[T.List[str]], *, junit: T.Optional[et.ElementTree] = None): assert isinstance(res, TestResult) self.res = res self.results = results # May be an empty list @@ -499,6 +529,7 @@ class TestRun: self.env = test_env self.should_fail = test.should_fail self.project = test.project_name + self.junit = junit def get_log(self) -> str: res = '--- command ---\n' @@ -652,7 +683,14 @@ class SingleTestRunner: # errors avoid not being able to use the terminal. os.setsid() # type: ignore - p = subprocess.Popen(cmd, + extra_cmd = [] # type: T.List[str] + if self.test.protocol is TestProtocol.GTEST: + gtestname = '{}.xml'.format(self.test.name) + if self.test.workdir: + gtestname = '{}:{}'.format(self.test.workdir, self.test.name) + extra_cmd.append('--gtest_output=xml:{}'.format(gtestname)) + + p = subprocess.Popen(cmd + extra_cmd, stdout=stdout, stderr=stderr, env=self.env, @@ -744,6 +782,8 @@ class SingleTestRunner: else: if self.test.protocol is TestProtocol.EXITCODE: return TestRun.make_exitcode(self.test, self.test_env, p.returncode, starttime, duration, stdo, stde, cmd) + elif self.test.protocol is TestProtocol.GTEST: + return TestRun.make_gtest(self.test, self.test_env, p.returncode, starttime, duration, stdo, stde, cmd) else: if self.options.verbose: print(stdo, end='') |