Skip to content

Commit

Permalink
add rust.doctest
Browse files Browse the repository at this point in the history
Signed-off-by: Paolo Bonzini <[email protected]>
  • Loading branch information
bonzini committed Nov 20, 2024
1 parent 925031b commit 06d0967
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 9 deletions.
22 changes: 22 additions & 0 deletions docs/markdown/Rust-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ 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.test(name, target, ...)
```

This function creates a new `test()` target from an existing rust
based target, which may be a library or executable. 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
This function also accepts all of the keyword arguments accepted by the
[[test]] function except `protocol`, it will set that automatically.
### bindgen()
This function wraps bindgen to simplify creating rust bindings around C
Expand Down
4 changes: 4 additions & 0 deletions docs/markdown/snippets/rustdoc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Running Rust doctests

A new function `doctest` in module rust creates a test that compiles another
target's doctests and runs them.
3 changes: 3 additions & 0 deletions mesonbuild/compilers/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,5 +355,8 @@ def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
def get_output_args(self, outputname: str) -> T.List[str]:
return []

def get_exe(self) -> str:
return self.exelist[0]

def get_rustc_args(self) -> T.List[str]:
return self.exelist[1:]
68 changes: 66 additions & 2 deletions mesonbuild/modules/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
from ..build import (BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList,
CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary)
from ..compilers.compilers import are_asserts_disabled, lang_suffixes
from ..compilers.rust import RustCompiler
from ..interpreter.type_checking import (
DEPENDENCIES_KW, LINK_WITH_KW, SHARED_LIB_KWS, TEST_KWS, 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
Expand Down Expand Up @@ -58,12 +60,14 @@ class RustModule(ExtensionModule):
"""A module that holds helper functions for rust."""

INFO = ModuleInfo('rust', '0.57.0', stabilized='1.0.0')
rustdoc: PerMachine[T.Optional[ExternalProgram]] = PerMachine(None, None)

def __init__(self, interpreter: Interpreter) -> None:
super().__init__(interpreter)
self._bindgen_bin: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None
self.methods.update({
'test': self.test,
'doctest': self.doctest,
'bindgen': self.bindgen,
'proc_macro': self.proc_macro,
})
Expand Down Expand Up @@ -189,6 +193,66 @@ def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: Func

return ModuleReturnValue(None, [new_target, test])

@typed_pos_args('rust.doctest', str, BuildTarget)
@typed_kwargs(
'rust.doctest',
*TEST_KWS,
DEPENDENCIES_KW,
LINK_WITH_KW,
KwargInfo(
'rust_args',
ContainerTypeInfo(list, str),
listify=True,
default=[],
),
KwargInfo('is_parallel', bool, default=False),
)
def doctest(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: FuncTest) -> 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(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.
for arg in tkwargs['args']:
if len(arg.split()) > 1:
raise InvalidArguments('Arguments to Rust doctests cannot contain spaces')

if tkwargs['args']:
tkwargs['args'] = ['--test-args=' + ' '.join(tkwargs['args'])]
if workdir:
tkwargs['args'] += ['--test-run-directory=' + workdir]

if self.rustdoc[base_target.for_machine] is None:
rustc = base_target.compilers['rust']
assert isinstance(rustc, RustCompiler)
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 = self.rustdoc[base_target.for_machine]
if not rustdoc.found():
raise MesonException(f'could not find rustdoc for {base_target.for_machine} machine')

doctests = self.interpreter.make_test(
self.interpreter.current_node, (name, rustdoc), 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',
Expand Down
11 changes: 4 additions & 7 deletions test cases/rust/9 unit tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,19 @@ 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'],
)
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_crate_type : 'lib')
Expand Down

0 comments on commit 06d0967

Please sign in to comment.