diff options
| author | Paolo Bonzini <pbonzini@redhat.com> | 2025-02-13 16:42:09 +0100 |
|---|---|---|
| committer | Dylan Baker <dylan@pnwbakers.com> | 2025-04-02 08:44:37 -0700 |
| commit | bf8d4927238a40dc0dca584e91988f456a970bbb (patch) | |
| tree | 742a1e206d52a8c542208a2b706e4f5e6d300e20 | |
| parent | 00dc6fa4dfee880a3c3c17f658e6d0b98a93bab6 (diff) | |
| download | meson-bf8d4927238a40dc0dca584e91988f456a970bbb.tar.gz | |
rust: add rust.doctest
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
| -rw-r--r-- | docs/markdown/Rust-module.md | 27 | ||||
| -rw-r--r-- | mesonbuild/modules/rust.py | 79 | ||||
| -rw-r--r-- | test cases/rust/9 unit tests/doctest1.rs | 2 | ||||
| -rw-r--r-- | test cases/rust/9 unit tests/meson.build | 13 |
4 files changed, 108 insertions, 13 deletions
diff --git a/docs/markdown/Rust-module.md b/docs/markdown/Rust-module.md index ee095e9d6..6e8202a6f 100644 --- a/docs/markdown/Rust-module.md +++ b/docs/markdown/Rust-module.md @@ -24,6 +24,8 @@ like Meson, rather than Meson work more like rust. rustmod.test(name, target, ...) ``` +*Since 1.8.0* + 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 @@ -41,6 +43,31 @@ It also takes the following keyword arguments: This function also accepts all of the keyword arguments accepted by the [[test]] function except `protocol`, it will set that automatically. +### doctest() + +```meson +rustmod.doctest(name, target, ...) +``` + +This function creates a new `test()` target from an existing rust +based library target. The test will use `rustdoc` to extract and run +the doctests that are included in `target`'s sources. + +This function takes two positional arguments, the first is the name of the +test and the second is the library or executable that is the rust based target. +It also takes the following keyword arguments: + +- `dependencies`: a list of test-only Dependencies +- `link_with`: a list of additional build Targets to link with +- `rust_args`: a list of extra arguments passed to the Rust compiler + +The target is linked automatically into the doctests. + +This function also accepts all of the keyword arguments accepted by the +[[test]] function except `protocol`, it will set that automatically. +However, arguments are limited to strings that do not contain spaces +due to limitations of `rustdoc`. + ### bindgen() This function wraps bindgen to simplify creating rust bindings around C diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 7e599505d..fdb18261e 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -4,6 +4,7 @@ from __future__ import annotations import itertools import os +import re import typing as T from mesonbuild.interpreterbase.decorators import FeatureNew @@ -11,15 +12,16 @@ from mesonbuild.interpreterbase.decorators import FeatureNew from . import ExtensionModule, ModuleReturnValue, ModuleInfo from .. import mesonlib, mlog from ..build import (BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, - CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary) + CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary, StaticLibrary) from ..compilers.compilers import are_asserts_disabled_for_subproject, lang_suffixes from ..interpreter.type_checking import ( - DEPENDENCIES_KW, LINK_WITH_KW, SHARED_LIB_KWS, TEST_KWS, OUTPUT_KW, + DEPENDENCIES_KW, LINK_WITH_KW, SHARED_LIB_KWS, TEST_KWS, TEST_KWS_NO_ARGS, OUTPUT_KW, INCLUDE_DIRECTORIES, SOURCES_VARARGS, NoneType, in_set_validator ) from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noPosargs, permittedKwargs -from ..mesonlib import File -from ..programs import ExternalProgram +from ..interpreter.interpreterobjects import Doctest +from ..mesonlib import File, MesonException, PerMachine +from ..programs import ExternalProgram, NonExistingExternalProgram if T.TYPE_CHECKING: from . import ModuleState @@ -45,6 +47,7 @@ if T.TYPE_CHECKING: rust_args: T.List[str] FuncTest = FuncRustTest[_kwargs.TestArgs] + FuncDoctest = FuncRustTest[str] class FuncBindgen(TypedDict): @@ -70,6 +73,11 @@ RUST_TEST_KWS: T.List[KwargInfo] = [ KwargInfo('is_parallel', bool, default=False), ] +def no_spaces_validator(arg: T.Optional[T.Union[str, T.List]]) -> T.Optional[str]: + if any(bool(re.search(r'\s', x)) for x in arg): + return 'must not contain spaces due to limitations of rustdoc' + return None + class RustModule(ExtensionModule): @@ -77,6 +85,7 @@ class RustModule(ExtensionModule): INFO = ModuleInfo('rust', '0.57.0', stabilized='1.0.0') _bindgen_rust_target: T.Optional[str] + rustdoc: PerMachine[T.Optional[ExternalProgram]] = PerMachine(None, None) def __init__(self, interpreter: Interpreter) -> None: super().__init__(interpreter) @@ -89,6 +98,7 @@ class RustModule(ExtensionModule): self._bindgen_set_std = False self.methods.update({ 'test': self.test, + 'doctest': self.doctest, 'bindgen': self.bindgen, 'proc_macro': self.proc_macro, }) @@ -207,6 +217,67 @@ class RustModule(ExtensionModule): return ModuleReturnValue(None, [new_target, test]) + @FeatureNew('rust.doctest', '1.8.0') + @typed_pos_args('rust.doctest', str, BuildTarget) + @typed_kwargs( + 'rust.doctest', + *TEST_KWS_NO_ARGS, + DEPENDENCIES_KW, + LINK_WITH_KW, + *RUST_TEST_KWS, + KwargInfo( + 'args', + ContainerTypeInfo(list, str), + listify=True, + default=[], + validator=no_spaces_validator, + ), + ) + def doctest(self, state: ModuleState, args: T.Tuple[str, T.Union[SharedLibrary, StaticLibrary]], kwargs: FuncDoctest) -> ModuleReturnValue: + name, base_target = args + + # Link the base target's crate into the tests + kwargs['link_with'].append(base_target) + kwargs['depends'].append(base_target) + workdir = kwargs['workdir'] + kwargs['workdir'] = None + new_target, tkwargs = self.test_common('doctest', state, args, kwargs) + + # added automatically by rustdoc; keep things simple + tkwargs['args'].remove('--test') + + # --test-args= is "parsed" simply via the Rust function split_whitespace(). + # This means no quoting nightmares (pfew) but it also means no spaces. + # Unfortunately it's pretty hard at this point to accept e.g. CustomTarget, + # because their paths may not be known. This is not a big deal because the + # user does not control the test harness, so make things easy and allow + # strings only. + if tkwargs['args']: + tkwargs['args'] = ['--test-args=' + ' '.join(T.cast('T.Sequence[str]', tkwargs['args']))] + if workdir: + tkwargs['args'].append('--test-run-directory=' + workdir) + + if self.rustdoc[base_target.for_machine] is None: + rustc = T.cast('RustCompiler', base_target.compilers['rust']) + rustdoc = rustc.get_rustdoc(state.environment) + if rustdoc: + self.rustdoc[base_target.for_machine] = ExternalProgram(rustdoc.get_exe()) + else: + self.rustdoc[base_target.for_machine] = NonExistingExternalProgram() + + rustdoc_prog = self.rustdoc[base_target.for_machine] + if not rustdoc_prog.found(): + raise MesonException(f'could not find rustdoc for {base_target.for_machine} machine') + + doctests: Doctest = self.interpreter.make_test( + self.interpreter.current_node, (name, rustdoc_prog), tkwargs, Doctest) + + # Note that the new_target is intentionally not returned, as it + # is only reached via the base_target and never built by "ninja" + doctests.target = new_target + base_target.doctests = doctests + return ModuleReturnValue(None, [doctests]) + @noPosargs @typed_kwargs( 'rust.bindgen', diff --git a/test cases/rust/9 unit tests/doctest1.rs b/test cases/rust/9 unit tests/doctest1.rs index d270f7d67..da42792b8 100644 --- a/test cases/rust/9 unit tests/doctest1.rs +++ b/test cases/rust/9 unit tests/doctest1.rs @@ -7,6 +7,6 @@ /// ```ignore /// this one will be skipped /// ``` -fn my_func() +pub fn my_func() { } diff --git a/test cases/rust/9 unit tests/meson.build b/test cases/rust/9 unit tests/meson.build index 4d04ee892..0fa2fa80b 100644 --- a/test cases/rust/9 unit tests/meson.build +++ b/test cases/rust/9 unit tests/meson.build @@ -1,4 +1,4 @@ -project('rust unit tests', 'rust', meson_version: '>=1.2.0') +project('rust unit tests', 'rust', meson_version: '>=1.8.0') t = executable( 'rust_test', @@ -31,14 +31,12 @@ test( suite : ['foo'], ) +rust = import('rust') + rustdoc = find_program('rustdoc', required: false) if rustdoc.found() - # rustdoc is invoked mostly like rustc. This is a simple example - # where it is easy enough to invoke it by hand. - test( - 'rust doctest', - rustdoc, - args : ['--test', '--crate-name', 'doctest1', '--crate-type', 'lib', files('doctest1.rs')], + doclib = static_library('rust_doc_lib', ['doctest1.rs'], build_by_default : false) + rust.doctest('rust doctests', doclib, protocol : 'rust', suite : ['doctests'], ) @@ -46,7 +44,6 @@ endif exe = executable('rust_exe', ['test2.rs', 'test.rs'], build_by_default : false) -rust = import('rust') rust.test('rust_test_from_exe', exe, should_fail : true) lib = static_library('rust_static', ['test.rs'], build_by_default : false, rust_abi: 'c') |
