Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

rust: kunit: support KUnit-mapped `assert!` macros in `#[test]`s

The KUnit `#[test]` support that landed recently is very basic and does
not map the `assert*!` macros into KUnit like the doctests do, so they
panic at the moment.

Thus implement the custom mapping in a similar way to doctests, reusing
the infrastructure there.

In Rust 1.88.0, the `file()` method in `Span` may be stable [1]. However,
it was changed recently (from `SourceFile`), so we need to do something
different in previous versions. Thus create a helper for it and use it
to get the path.

With this, a failing test suite like:

#[kunit_tests(my_test_suite)]
mod tests {
use super::*;

#[test]
fn my_first_test() {
assert_eq!(42, 43);
}

#[test]
fn my_second_test() {
assert!(42 >= 43);
}
}

will properly map back to KUnit, printing something like:

[ 1.924325] KTAP version 1
[ 1.924421] # Subtest: my_test_suite
[ 1.924506] # speed: normal
[ 1.924525] 1..2
[ 1.926385] # my_first_test: ASSERTION FAILED at rust/kernel/lib.rs:251
[ 1.926385] Expected 42 == 43 to be true, but is false
[ 1.928026] # my_first_test.speed: normal
[ 1.928075] not ok 1 my_first_test
[ 1.928723] # my_second_test: ASSERTION FAILED at rust/kernel/lib.rs:256
[ 1.928723] Expected 42 >= 43 to be true, but is false
[ 1.929834] # my_second_test.speed: normal
[ 1.929868] not ok 2 my_second_test
[ 1.930032] # my_test_suite: pass:0 fail:2 skip:0 total:2
[ 1.930153] # Totals: pass:0 fail:2 skip:0 total

Link: https://github.com/rust-lang/rust/pull/140514 [1]
Reviewed-by: David Gow <davidgow@google.com>
Acked-by: Danilo Krummrich <dakr@kernel.org>
Link: https://lore.kernel.org/r/20250502215133.1923676-2-ojeda@kernel.org
[ Required `KUNIT=y` like for doctests. Used the `cfg_attr` from the
TODO comment and clarified its comment now that the stabilization is
in beta and thus quite likely stable in Rust 1.88.0. Simplified the
`new_body` code by introducing a new variable. Added
`#[allow(clippy::incompatible_msrv)]`. - Miguel ]
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>

+57 -7
+3
init/Kconfig
··· 140 140 config RUSTC_HAS_COERCE_POINTEE 141 141 def_bool RUSTC_VERSION >= 108400 142 142 143 + config RUSTC_HAS_SPAN_FILE 144 + def_bool RUSTC_VERSION >= 108800 145 + 143 146 config RUSTC_HAS_UNNECESSARY_TRANSMUTES 144 147 def_bool RUSTC_VERSION >= 108800 145 148
+2 -1
rust/Makefile
··· 404 404 -Clink-args='$(call escsq,$(KBUILD_PROCMACROLDFLAGS))' \ 405 405 --emit=dep-info=$(depfile) --emit=link=$@ --extern proc_macro \ 406 406 --crate-type proc-macro \ 407 - --crate-name $(patsubst lib%.$(libmacros_extension),%,$(notdir $@)) $< 407 + --crate-name $(patsubst lib%.$(libmacros_extension),%,$(notdir $@)) \ 408 + @$(objtree)/include/generated/rustc_cfg $< 408 409 409 410 # Procedural macros can only be used with the `rustc` that compiled it. 410 411 $(obj)/$(libmacros_name): $(src)/macros/lib.rs FORCE
-1
rust/kernel/kunit.rs
··· 323 323 324 324 #[test] 325 325 fn rust_test_kunit_example_test() { 326 - #![expect(clippy::eq_op)] 327 326 assert_eq!(1 + 1, 2); 328 327 } 329 328
+17
rust/macros/helpers.rs
··· 86 86 } 87 87 None 88 88 } 89 + 90 + pub(crate) fn file() -> String { 91 + #[cfg(not(CONFIG_RUSTC_HAS_SPAN_FILE))] 92 + { 93 + proc_macro::Span::call_site() 94 + .source_file() 95 + .path() 96 + .to_string_lossy() 97 + .into_owned() 98 + } 99 + 100 + #[cfg(CONFIG_RUSTC_HAS_SPAN_FILE)] 101 + #[allow(clippy::incompatible_msrv)] 102 + { 103 + proc_macro::Span::call_site().file() 104 + } 105 + }
+30 -5
rust/macros/kunit.rs
··· 57 57 } 58 58 } 59 59 60 - // Add `#[cfg(CONFIG_KUNIT)]` before the module declaration. 61 - let config_kunit = "#[cfg(CONFIG_KUNIT)]".to_owned().parse().unwrap(); 60 + // Add `#[cfg(CONFIG_KUNIT="y")]` before the module declaration. 61 + let config_kunit = "#[cfg(CONFIG_KUNIT=\"y\")]".to_owned().parse().unwrap(); 62 62 tokens.insert( 63 63 0, 64 64 TokenTree::Group(Group::new(Delimiter::None, config_kunit)), ··· 98 98 // ``` 99 99 let mut kunit_macros = "".to_owned(); 100 100 let mut test_cases = "".to_owned(); 101 + let mut assert_macros = "".to_owned(); 102 + let path = crate::helpers::file(); 101 103 for test in &tests { 102 104 let kunit_wrapper_fn_name = format!("kunit_rust_wrapper_{test}"); 103 105 let kunit_wrapper = format!( ··· 109 107 writeln!( 110 108 test_cases, 111 109 " ::kernel::kunit::kunit_case(::kernel::c_str!(\"{test}\"), {kunit_wrapper_fn_name})," 110 + ) 111 + .unwrap(); 112 + writeln!( 113 + assert_macros, 114 + r#" 115 + /// Overrides the usual [`assert!`] macro with one that calls KUnit instead. 116 + #[allow(unused)] 117 + macro_rules! assert {{ 118 + ($cond:expr $(,)?) => {{{{ 119 + kernel::kunit_assert!("{test}", "{path}", 0, $cond); 120 + }}}} 121 + }} 122 + 123 + /// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead. 124 + #[allow(unused)] 125 + macro_rules! assert_eq {{ 126 + ($left:expr, $right:expr $(,)?) => {{{{ 127 + kernel::kunit_assert_eq!("{test}", "{path}", 0, $left, $right); 128 + }}}} 129 + }} 130 + "# 112 131 ) 113 132 .unwrap(); 114 133 } ··· 170 147 } 171 148 } 172 149 173 - let mut new_body = TokenStream::from_iter(new_body); 174 - new_body.extend::<TokenStream>(kunit_macros.parse().unwrap()); 150 + let mut final_body = TokenStream::new(); 151 + final_body.extend::<TokenStream>(assert_macros.parse().unwrap()); 152 + final_body.extend(new_body); 153 + final_body.extend::<TokenStream>(kunit_macros.parse().unwrap()); 175 154 176 - tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body))); 155 + tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, final_body))); 177 156 178 157 tokens.into_iter().collect() 179 158 }
+5
rust/macros/lib.rs
··· 6 6 // and thus add a dependency on `include/config/RUSTC_VERSION_TEXT`, which is 7 7 // touched by Kconfig when the version string from the compiler changes. 8 8 9 + // Stable since Rust 1.88.0 under a different name, `proc_macro_span_file`, 10 + // which was added in Rust 1.88.0. This is why `cfg_attr` is used here, i.e. 11 + // to avoid depending on the full `proc_macro_span` on Rust >= 1.88.0. 12 + #![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))] 13 + 9 14 #[macro_use] 10 15 mod quote; 11 16 mod concat_idents;