aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Reference-manual.md1
-rw-r--r--docs/markdown/snippets/rust_test_format_support.md4
-rw-r--r--mesonbuild/backend/backends.py5
-rw-r--r--mesonbuild/interpreter.py4
-rw-r--r--mesonbuild/mtest.py39
-rw-r--r--test cases/rust/9 unit tests/meson.build32
-rw-r--r--test cases/rust/9 unit tests/test.rs24
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);
+ }
+}