Skip to content

Commit c652dc4

Browse files
fee1-deadshuahkh
authored andcommitted
rust: kunit: allow cfg on tests
The `kunit_test` proc macro only checks for the `test` attribute immediately preceding a `fn`. If the function is disabled via a `cfg`, the generated code would result in a compile error referencing a non-existent function [1]. This collects attributes and specifically cherry-picks `cfg` attributes to be duplicated inside KUnit wrapper functions such that a test function disabled via `cfg` compiles and is marked as skipped in KUnit correctly. Link: https://lore.kernel.org/r/20250916021259.115578-1-ent3rm4n@gmail.com Link: https://lore.kernel.org/rust-for-linux/CANiq72==48=69hYiDo1321pCzgn_n1_jg=ez5UYXX91c+g5JVQ@mail.gmail.com/ [1] Closes: Rust-for-Linux/linux#1185 Suggested-by: Miguel Ojeda <ojeda@kernel.org> Suggested-by: David Gow <davidgow@google.com> Signed-off-by: Kaibo Ma <ent3rm4n@gmail.com> Reviewed-by: David Gow <davidgow@google.com> Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
1 parent f20e264 commit c652dc4

File tree

2 files changed

+43
-12
lines changed

2 files changed

+43
-12
lines changed

rust/kernel/kunit.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,4 +361,11 @@ mod tests {
361361
fn rust_test_kunit_in_kunit_test() {
362362
assert!(in_kunit_test());
363363
}
364+
365+
#[test]
366+
#[cfg(not(all()))]
367+
fn rust_test_kunit_always_disabled_test() {
368+
// This test should never run because of the `cfg`.
369+
assert!(false);
370+
}
364371
}

rust/macros/kunit.rs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
66
77
use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
8+
use std::collections::HashMap;
89
use std::fmt::Write;
910

1011
pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
@@ -41,20 +42,32 @@ pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
4142
// Get the functions set as tests. Search for `[test]` -> `fn`.
4243
let mut body_it = body.stream().into_iter();
4344
let mut tests = Vec::new();
45+
let mut attributes: HashMap<String, TokenStream> = HashMap::new();
4446
while let Some(token) = body_it.next() {
4547
match token {
46-
TokenTree::Group(ident) if ident.to_string() == "[test]" => match body_it.next() {
47-
Some(TokenTree::Ident(ident)) if ident.to_string() == "fn" => {
48-
let test_name = match body_it.next() {
49-
Some(TokenTree::Ident(ident)) => ident.to_string(),
50-
_ => continue,
51-
};
52-
tests.push(test_name);
48+
TokenTree::Punct(ref p) if p.as_char() == '#' => match body_it.next() {
49+
Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket => {
50+
if let Some(TokenTree::Ident(name)) = g.stream().into_iter().next() {
51+
// Collect attributes because we need to find which are tests. We also
52+
// need to copy `cfg` attributes so tests can be conditionally enabled.
53+
attributes
54+
.entry(name.to_string())
55+
.or_default()
56+
.extend([token, TokenTree::Group(g)]);
57+
}
58+
continue;
5359
}
54-
_ => continue,
60+
_ => (),
5561
},
62+
TokenTree::Ident(i) if i.to_string() == "fn" && attributes.contains_key("test") => {
63+
if let Some(TokenTree::Ident(test_name)) = body_it.next() {
64+
tests.push((test_name, attributes.remove("cfg").unwrap_or_default()))
65+
}
66+
}
67+
5668
_ => (),
5769
}
70+
attributes.clear();
5871
}
5972

6073
// Add `#[cfg(CONFIG_KUNIT="y")]` before the module declaration.
@@ -100,11 +113,22 @@ pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
100113
let mut test_cases = "".to_owned();
101114
let mut assert_macros = "".to_owned();
102115
let path = crate::helpers::file();
103-
for test in &tests {
116+
let num_tests = tests.len();
117+
for (test, cfg_attr) in tests {
104118
let kunit_wrapper_fn_name = format!("kunit_rust_wrapper_{test}");
105-
// An extra `use` is used here to reduce the length of the message.
119+
// Append any `cfg` attributes the user might have written on their tests so we don't
120+
// attempt to call them when they are `cfg`'d out. An extra `use` is used here to reduce
121+
// the length of the assert message.
106122
let kunit_wrapper = format!(
107-
"unsafe extern \"C\" fn {kunit_wrapper_fn_name}(_test: *mut ::kernel::bindings::kunit) {{ use ::kernel::kunit::is_test_result_ok; assert!(is_test_result_ok({test}())); }}",
123+
r#"unsafe extern "C" fn {kunit_wrapper_fn_name}(_test: *mut ::kernel::bindings::kunit)
124+
{{
125+
(*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
126+
{cfg_attr} {{
127+
(*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
128+
use ::kernel::kunit::is_test_result_ok;
129+
assert!(is_test_result_ok({test}()));
130+
}}
131+
}}"#,
108132
);
109133
writeln!(kunit_macros, "{kunit_wrapper}").unwrap();
110134
writeln!(
@@ -139,7 +163,7 @@ macro_rules! assert_eq {{
139163
writeln!(
140164
kunit_macros,
141165
"static mut TEST_CASES: [::kernel::bindings::kunit_case; {}] = [\n{test_cases} ::kernel::kunit::kunit_case_null(),\n];",
142-
tests.len() + 1
166+
num_tests + 1
143167
)
144168
.unwrap();
145169

0 commit comments

Comments
 (0)