mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-03-03 18:28:01 +01:00
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>
172 lines
5.5 KiB
Rust
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())
|
|
}
|