| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- // 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())
- }
|