Files
linux-stable-mirror/rust/macros/kunit.rs
Gary Guo be97f3c820 rust: kunit: use pin_init::zeroed instead of custom null value
The last null element can be created (constly) using `pin_init::zeroed`,
so prefer to use it instead of adding a custom way of building it.

Reviewed-by: Tamir Duberstein <tamird@gmail.com>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: David Gow <davidgow@google.com>
Link: https://patch.msgid.link/20260112170919.1888584-12-gary@kernel.org
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
2026-01-28 13:44:17 +01:00

172 lines
5.5 KiB
Rust

// SPDX-License-Identifier: GPL-2.0
//! Procedural macro to run KUnit tests using a user-space like syntax.
//!
//! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
use std::ffi::CString;
use proc_macro2::TokenStream;
use quote::{
format_ident,
quote,
ToTokens, //
};
use syn::{
parse_quote,
Error,
Ident,
Item,
ItemMod,
LitCStr,
Result, //
};
pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<TokenStream> {
if test_suite.to_string().len() > 255 {
return Err(Error::new_spanned(
test_suite,
"test suite names cannot exceed the maximum length of 255 bytes",
));
}
// We cannot handle modules that defer to another file (e.g. `mod foo;`).
let Some((module_brace, module_items)) = module.content.take() else {
Err(Error::new_spanned(
module,
"`#[kunit_tests(test_name)]` attribute should only be applied to inline modules",
))?
};
// Make the entire module gated behind `CONFIG_KUNIT`.
module
.attrs
.insert(0, parse_quote!(#[cfg(CONFIG_KUNIT="y")]));
let mut processed_items = Vec::new();
let mut test_cases = Vec::new();
// Generate the test KUnit test suite and a test case for each `#[test]`.
//
// The code generated for the following test module:
//
// ```
// #[kunit_tests(kunit_test_suit_name)]
// mod tests {
// #[test]
// fn foo() {
// assert_eq!(1, 1);
// }
//
// #[test]
// fn bar() {
// assert_eq!(2, 2);
// }
// }
// ```
//
// Looks like:
//
// ```
// unsafe extern "C" fn kunit_rust_wrapper_foo(_test: *mut ::kernel::bindings::kunit) { foo(); }
// unsafe extern "C" fn kunit_rust_wrapper_bar(_test: *mut ::kernel::bindings::kunit) { bar(); }
//
// static mut TEST_CASES: [::kernel::bindings::kunit_case; 3] = [
// ::kernel::kunit::kunit_case(c"foo", kunit_rust_wrapper_foo),
// ::kernel::kunit::kunit_case(c"bar", kunit_rust_wrapper_bar),
// ::pin_init::zeroed(),
// ];
//
// ::kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
// ```
//
// Non-function items (e.g. imports) are preserved.
for item in module_items {
let Item::Fn(mut f) = item else {
processed_items.push(item);
continue;
};
// TODO: Replace below with `extract_if` when MSRV is bumped above 1.85.
let before_len = f.attrs.len();
f.attrs.retain(|attr| !attr.path().is_ident("test"));
if f.attrs.len() == before_len {
processed_items.push(Item::Fn(f));
continue;
}
let test = f.sig.ident.clone();
// Retrieve `#[cfg]` applied on the function which needs to be present on derived items too.
let cfg_attrs: Vec<_> = f
.attrs
.iter()
.filter(|attr| attr.path().is_ident("cfg"))
.cloned()
.collect();
// Before the test, override usual `assert!` and `assert_eq!` macros with ones that call
// KUnit instead.
let test_str = test.to_string();
let path = CString::new(crate::helpers::file()).expect("file path cannot contain NUL");
processed_items.push(parse_quote! {
#[allow(unused)]
macro_rules! assert {
($cond:expr $(,)?) => {{
kernel::kunit_assert!(#test_str, #path, 0, $cond);
}}
}
});
processed_items.push(parse_quote! {
#[allow(unused)]
macro_rules! assert_eq {
($left:expr, $right:expr $(,)?) => {{
kernel::kunit_assert_eq!(#test_str, #path, 0, $left, $right);
}}
}
});
// Add back the test item.
processed_items.push(Item::Fn(f));
let kunit_wrapper_fn_name = format_ident!("kunit_rust_wrapper_{test}");
let test_cstr = LitCStr::new(
&CString::new(test_str.as_str()).expect("identifier cannot contain NUL"),
test.span(),
);
processed_items.push(parse_quote! {
unsafe extern "C" fn #kunit_wrapper_fn_name(_test: *mut ::kernel::bindings::kunit) {
(*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
// Append any `cfg` attributes the user might have written on their tests so we
// don't attempt to call them when they are `cfg`'d out. An extra `use` is used
// here to reduce the length of the assert message.
#(#cfg_attrs)*
{
(*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
use ::kernel::kunit::is_test_result_ok;
assert!(is_test_result_ok(#test()));
}
}
});
test_cases.push(quote!(
::kernel::kunit::kunit_case(#test_cstr, #kunit_wrapper_fn_name)
));
}
let num_tests_plus_1 = test_cases.len() + 1;
processed_items.push(parse_quote! {
static mut TEST_CASES: [::kernel::bindings::kunit_case; #num_tests_plus_1] = [
#(#test_cases,)*
::pin_init::zeroed(),
];
});
processed_items.push(parse_quote! {
::kernel::kunit_unsafe_test_suite!(#test_suite, TEST_CASES);
});
module.content = Some((module_brace, processed_items));
Ok(module.to_token_stream())
}