From d89ec98b4763cda13da0ae22515c27f4dfe5c1b9 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 16 Oct 2020 12:37:30 -0700 Subject: mtest: Add support for rust unit tests Rust has it's own built in unit test format, which is invoked by compiling a rust executable with the `--test` flag to rustc. The tests are then run by simply invoking that binary. They output a custom test format, which this patch adds parsing support for. This means that we can report each subtest in the junit we generate correctly, which should be helpful for orchestration systems like gitlab and jenkins which can parse junit XML. --- docs/markdown/Reference-manual.md | 1 + docs/markdown/snippets/rust_test_format_support.md | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 docs/markdown/snippets/rust_test_format_support.md (limited to 'docs') diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 924047ca3..525c3dae9 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 000000000..69e9aa14a --- /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. -- cgit v1.2.3 From 3d80a88bd3c3dc8f9e20bbda485b0b436fd79fb3 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Wed, 21 Oct 2020 16:07:31 -0700 Subject: modules: Add an unstable-rust module Like other language specific modules this module is module for holding rust specific helpers. This commit adds a test() function, which simplifies using rust's internal unittest mechanism. Rust tests are generally placed in the same code files as they are testing, in contrast to languages like C/C++ and python which generally place the tests in separate translation units. For meson this is somewhat problematic from a repetition point of view, as the only changes are generally adding --test, and possibly some dependencies. The rustmod.test() method provides a mechanism to remove the repatition: it takes a rust target, copies it, and then addes the `--test` option, then creates a Test() target with the `rust` protocol. You can pass additional dependencies via the `dependencies` keyword. This all makes for a nice, DRY, test definition. --- docs/markdown/Rust-module.md | 35 +++++++ docs/markdown/_Sidebar.md | 1 + docs/markdown/snippets/unstable-rust-module.md | 4 + docs/sitemap.txt | 11 +- docs/theme/extra/templates/navbar_links.html | 45 ++++---- mesonbuild/modules/unstable_rust.py | 137 +++++++++++++++++++++++++ run_mypy.py | 1 + test cases/rust/9 unit tests/meson.build | 11 ++ test cases/rust/9 unit tests/test2.rs | 11 ++ 9 files changed, 229 insertions(+), 27 deletions(-) create mode 100644 docs/markdown/Rust-module.md create mode 100644 docs/markdown/snippets/unstable-rust-module.md create mode 100644 mesonbuild/modules/unstable_rust.py create mode 100644 test cases/rust/9 unit tests/test2.rs (limited to 'docs') diff --git a/docs/markdown/Rust-module.md b/docs/markdown/Rust-module.md new file mode 100644 index 000000000..0fdba946c --- /dev/null +++ b/docs/markdown/Rust-module.md @@ -0,0 +1,35 @@ +--- +short-description: Rust language integration module +authors: + - name: Dylan Baker + email: dylan@pnwbakers.com + years: [2020] +... + +# Unstable Rust module + +*(new in 0.57.0)* + +**Note** Unstable modules make no backwards compatible API guarantees. + +The rust module provides helper to integrate rust code into meson. The goal +is to make using rust in meson more pleasant, while still remaining mesonic, +this means that it attempts to make rust work more like meson, rather than +meson work more like rust. + +## Functions + +### test(name: string, target: library | executable, dependencies: []Dependency) + +This function creates a new rust unittest target from an existing rust based +target, which may be a library or executable. It does this by copying the +sources and arguments passed to the original target and adding the `--test` +argument to the compilation, then creates a new test target which calls that +executable, using the rust test protocol. + +This accepts all of the keyword arguments as the +[`test`](Reference-manual.md#test) function except `protocol`, it will set +that automatically. + +Additional, test only dependencies may be passed via the dependencies +argument. diff --git a/docs/markdown/_Sidebar.md b/docs/markdown/_Sidebar.md index 2637d686d..0ca1762b5 100644 --- a/docs/markdown/_Sidebar.md +++ b/docs/markdown/_Sidebar.md @@ -12,3 +12,4 @@ * [gnome](Gnome-module.md) * [i18n](i18n-module.md) * [pkgconfig](Pkgconfig-module.md) +* [rust](Rust-module.md) diff --git a/docs/markdown/snippets/unstable-rust-module.md b/docs/markdown/snippets/unstable-rust-module.md new file mode 100644 index 000000000..15a7ecbd5 --- /dev/null +++ b/docs/markdown/snippets/unstable-rust-module.md @@ -0,0 +1,4 @@ +## Untable Rust module + +A new unstable module has been added to make using rust with meson easier. +Currently it adds a single function to ease defining rust tests. diff --git a/docs/sitemap.txt b/docs/sitemap.txt index 4cae9fe45..3164440aa 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -34,24 +34,25 @@ index.md Disabler.md Modules.md CMake-module.md + Cuda-module.md Dlang-module.md + External-Project-module.md Fs-module.md Gnome-module.md Hotdoc-module.md - i18n-module.md Icestorm-module.md + Keyval-module.md Pkgconfig-module.md - Python-module.md Python-3-module.md + Python-module.md Qt4-module.md Qt5-module.md RPM-module.md + Rust-module.md Simd-module.md SourceSet-module.md Windows-module.md - Cuda-module.md - Keyval-module.md - External-Project-module.md + i18n-module.md Java.md Vala.md D.md diff --git a/docs/theme/extra/templates/navbar_links.html b/docs/theme/extra/templates/navbar_links.html index 832bd2c49..8df082f1a 100644 --- a/docs/theme/extra/templates/navbar_links.html +++ b/docs/theme/extra/templates/navbar_links.html @@ -5,28 +5,29 @@ Modules \ diff --git a/mesonbuild/modules/unstable_rust.py b/mesonbuild/modules/unstable_rust.py new file mode 100644 index 000000000..72b521788 --- /dev/null +++ b/mesonbuild/modules/unstable_rust.py @@ -0,0 +1,137 @@ +# Copyright © 2020 Intel Corporation + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing as T + +from . import ExtensionModule, ModuleReturnValue +from .. import mlog +from ..build import BuildTarget, Executable, InvalidArguments +from ..dependencies import Dependency, ExternalLibrary +from ..interpreter import ExecutableHolder, permitted_kwargs +from ..interpreterbase import InterpreterException, permittedKwargs, FeatureNew +from ..mesonlib import stringlistify, unholder, listify + +if T.TYPE_CHECKING: + from ..interpreter import ModuleState, Interpreter + + +class RustModule(ExtensionModule): + + """A module that holds helper functions for rust.""" + + @FeatureNew('rust module', '0.57.0') + def __init__(self, interpreter: 'Interpreter') -> None: + super().__init__(interpreter) + + @permittedKwargs(permitted_kwargs['test'] | {'dependencies'} ^ {'protocol'}) + def test(self, state: 'ModuleState', args: T.List, kwargs: T.Dict[str, T.Any]) -> ModuleReturnValue: + """Generate a rust test target from a given rust target. + + Rust puts it's unitests inside it's main source files, unlike most + languages that put them in external files. This means that normally + you have to define two seperate targets with basically the same + arguments to get tests: + + ```meson + rust_lib_sources = [...] + rust_lib = static_library( + 'rust_lib', + rust_lib_sources, + ) + + rust_lib_test = executable( + 'rust_lib_test', + rust_lib_sources, + rust_args : ['--test'], + ) + + test( + 'rust_lib_test', + rust_lib_test, + protocol : 'rust', + ) + ``` + + This is all fine, but not very DRY. This method makes it much easier + to define rust tests: + + ```meson + rust = import('unstable-rust') + + rust_lib = static_library( + 'rust_lib', + [sources], + ) + + rust.test('rust_lib_test', rust_lib) + ``` + """ + if len(args) != 2: + raise InterpreterException('rustmod.test() takes exactly 2 positional arguments') + name: str = args[0] + if not isinstance(name, str): + raise InterpreterException('First positional argument to rustmod.test() must be a string') + base_target: BuildTarget = unholder(args[1]) + if not isinstance(base_target, BuildTarget): + raise InterpreterException('Second positional argument to rustmod.test() must be a library or executable') + if not base_target.get_using_rustc(): + raise InterpreterException('Second positional argument to rustmod.test() must be a rust based target') + extra_args = stringlistify(kwargs.get('args', [])) + + # Delete any arguments we don't want passed + if '--test' in extra_args: + mlog.warning('Do not add --test to rustmod.test arguments') + extra_args.remove('--test') + if '--format' in extra_args: + mlog.warning('Do not add --format to rustmod.test arguments') + i = extra_args.index('--format') + # Also delete the argument to --format + del extra_args[i + 1] + del extra_args[i] + for i, a in enumerate(extra_args): + if a.startswith('--format='): + del extra_args[i] + break + + dependencies = unholder(listify(kwargs.get('dependencies', []))) + for d in dependencies: + if not isinstance(d, (Dependency, ExternalLibrary)): + raise InvalidArguments('dependencies must be a dependency or external library') + + kwargs['args'] = extra_args + ['--test', '--format', 'pretty'] + kwargs['protocol'] = 'rust' + + new_target_kwargs = base_target.kwargs.copy() + # Don't mutate the shallow copied list, instead replace it with a new + # one + new_target_kwargs['rust_args'] = new_target_kwargs.get('rust_args', []) + ['--test'] + new_target_kwargs['install'] = False + new_target_kwargs['dependencies'] = new_target_kwargs.get('dependencies', []) + dependencies + + new_target = Executable( + name, base_target.subdir, state.subproject, + base_target.for_machine, base_target.sources, + base_target.objects, base_target.environment, + new_target_kwargs + ) + + e = ExecutableHolder(new_target, self.interpreter) + test = self.interpreter.make_test( + self.interpreter.current_node, [name, e], kwargs) + + return ModuleReturnValue([], [e, test]) + + +def initialize(*args: T.List, **kwargs: T.Dict) -> RustModule: + return RustModule(*args, **kwargs) # type: ignore diff --git a/run_mypy.py b/run_mypy.py index 01fa9ffcc..888403c14 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -29,6 +29,7 @@ modules = [ 'mesonbuild/mintro.py', 'mesonbuild/mlog.py', 'mesonbuild/modules/fs.py', + 'mesonbuild/modules/unstable_rust.py', 'mesonbuild/mparser.py', 'mesonbuild/msetup.py', 'mesonbuild/mtest.py', diff --git a/test cases/rust/9 unit tests/meson.build b/test cases/rust/9 unit tests/meson.build index 44fd6b67b..b649abb85 100644 --- a/test cases/rust/9 unit tests/meson.build +++ b/test cases/rust/9 unit tests/meson.build @@ -30,3 +30,14 @@ test( protocol : 'rust', suite : ['foo'], ) + +exe = executable('rust_exe', ['test2.rs', 'test.rs']) + +rust = import('unstable-rust') +rust.test('rust_test_from_exe', exe, should_fail : true) + +lib = static_library('rust_static', ['test.rs']) +rust.test('rust_test_from_static', lib, args: ['--skip', 'test_add_intentional_fail']) + +lib = shared_library('rust_shared', ['test.rs']) +rust.test('rust_test_from_shared', lib, args: ['--skip', 'test_add_intentional_fail']) diff --git a/test cases/rust/9 unit tests/test2.rs b/test cases/rust/9 unit tests/test2.rs new file mode 100644 index 000000000..9623c7c24 --- /dev/null +++ b/test cases/rust/9 unit tests/test2.rs @@ -0,0 +1,11 @@ +mod test; +use std::env; + +fn main() { + let args: Vec = env::args().collect(); + let first = args[1].parse::().expect("Invliad value for first argument."); + let second = args[2].parse::().expect("Invliad value for second argument."); + + let new = test::add(first, second); + println!("New value: {}", new); +} -- cgit v1.2.3