diff options
-rw-r--r-- | docs/markdown/Reference-manual.md | 1 | ||||
-rw-r--r-- | docs/markdown/snippets/rust_test_format_support.md | 4 | ||||
-rw-r--r-- | mesonbuild/backend/backends.py | 5 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 4 | ||||
-rw-r--r-- | mesonbuild/mtest.py | 39 | ||||
-rw-r--r-- | test cases/rust/9 unit tests/meson.build | 32 | ||||
-rw-r--r-- | test cases/rust/9 unit tests/test.rs | 24 |
7 files changed, 107 insertions, 2 deletions
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 924047c..525c3da 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1745,6 +1745,7 @@ test(..., env: nomalloc, ...) to record the outcome of the test). - `tap`: [Test Anything Protocol](https://www.testanything.org/). - `gtest` *(since 0.55.0)*: for Google Tests. + - `rust` *(since 0.56.0)*: for native rust tests - `priority` *(since 0.52.0)*:specifies the priority of a test. Tests with a higher priority are *started* before tests with a lower priority. diff --git a/docs/markdown/snippets/rust_test_format_support.md b/docs/markdown/snippets/rust_test_format_support.md new file mode 100644 index 0000000..69e9aa1 --- /dev/null +++ b/docs/markdown/snippets/rust_test_format_support.md @@ -0,0 +1,4 @@ +## Meson test() now accepts `protocol : 'rust'` + +This allows native rust tests to be run and parsed by meson, simply set the +protocol to `rust` and meson takes care of the rest. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index ec3aca6..9bb870c 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -46,6 +46,7 @@ class TestProtocol(enum.Enum): EXITCODE = 0 TAP = 1 GTEST = 2 + RUST = 3 @classmethod def from_str(cls, string: str) -> 'TestProtocol': @@ -55,6 +56,8 @@ class TestProtocol(enum.Enum): return cls.TAP elif string == 'gtest': return cls.GTEST + elif string == 'rust': + return cls.RUST raise MesonException('unknown test format {}'.format(string)) def __str__(self) -> str: @@ -62,6 +65,8 @@ class TestProtocol(enum.Enum): return 'exitcode' elif self is self.GTEST: return 'gtest' + elif self is self.RUST: + return 'rust' return 'tap' diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index c20c205..0c6bb99 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -4159,8 +4159,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', 'gtest'}: - raise InterpreterException('Protocol must be "exitcode", "tap", or "gtest".') + if protocol not in {'exitcode', 'tap', 'gtest', 'rust'}: + raise InterpreterException('Protocol must be one of "exitcode", "tap", "gtest", or "rust".') 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 c31ad1a..9db271e 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -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 @@ -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: @@ -1069,6 +1106,8 @@ class SingleTestRunner: 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='') diff --git a/test cases/rust/9 unit tests/meson.build b/test cases/rust/9 unit tests/meson.build new file mode 100644 index 0000000..44fd6b6 --- /dev/null +++ b/test cases/rust/9 unit tests/meson.build @@ -0,0 +1,32 @@ +project('rust unit tests', 'rust') + +t = executable( + 'rust_test', + ['test.rs'], + rust_args : ['--test'], +) + +test( + 'rust test (should fail)', + t, + protocol : 'rust', + suite : ['foo'], + should_fail : true, +) + +test( + 'rust test (should pass)', + t, + args : ['--skip', 'test_add_intentional_fail'], + protocol : 'rust', + suite : ['foo'], +) + + +test( + 'rust test (should skip)', + t, + args : ['--skip', 'test_add'], + protocol : 'rust', + suite : ['foo'], +) diff --git a/test cases/rust/9 unit tests/test.rs b/test cases/rust/9 unit tests/test.rs new file mode 100644 index 0000000..0225be5 --- /dev/null +++ b/test cases/rust/9 unit tests/test.rs @@ -0,0 +1,24 @@ +pub fn add(a: i32, b: i32) -> i32 { + return a + b; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add() { + assert_eq!(add(1, 2), 3); + } + + #[test] + fn test_add_intentional_fail() { + assert_eq!(add(1, 2), 5); + } + + #[test] + #[ignore] + fn test_add_intentional_fail2() { + assert_eq!(add(1, 7), 5); + } +} |