diff options
Diffstat (limited to 'rust/kernel/kunit.rs')
| -rw-r--r-- | rust/kernel/kunit.rs | 171 | 
1 files changed, 171 insertions, 0 deletions
| diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs index 824da0e9738a..1604fb6a5b1b 100644 --- a/rust/kernel/kunit.rs +++ b/rust/kernel/kunit.rs @@ -40,6 +40,8 @@ pub fn info(args: fmt::Arguments<'_>) {      }  } +use macros::kunit_tests; +  /// Asserts that a boolean expression is `true` at runtime.  ///  /// Public but hidden since it should only be used from generated tests. @@ -161,3 +163,172 @@ macro_rules! kunit_assert_eq {          $crate::kunit_assert!($name, $file, $diff, $left == $right);      }};  } + +/// Represents an individual test case. +/// +/// The [`kunit_unsafe_test_suite!`] macro expects a NULL-terminated list of valid test cases. +/// Use [`kunit_case_null`] to generate such a delimiter. +#[doc(hidden)] +pub const fn kunit_case( +    name: &'static kernel::str::CStr, +    run_case: unsafe extern "C" fn(*mut kernel::bindings::kunit), +) -> kernel::bindings::kunit_case { +    kernel::bindings::kunit_case { +        run_case: Some(run_case), +        name: name.as_char_ptr(), +        attr: kernel::bindings::kunit_attributes { +            speed: kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL, +        }, +        generate_params: None, +        status: kernel::bindings::kunit_status_KUNIT_SUCCESS, +        module_name: core::ptr::null_mut(), +        log: core::ptr::null_mut(), +    } +} + +/// Represents the NULL test case delimiter. +/// +/// The [`kunit_unsafe_test_suite!`] macro expects a NULL-terminated list of test cases. This +/// function returns such a delimiter. +#[doc(hidden)] +pub const fn kunit_case_null() -> kernel::bindings::kunit_case { +    kernel::bindings::kunit_case { +        run_case: None, +        name: core::ptr::null_mut(), +        generate_params: None, +        attr: kernel::bindings::kunit_attributes { +            speed: kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL, +        }, +        status: kernel::bindings::kunit_status_KUNIT_SUCCESS, +        module_name: core::ptr::null_mut(), +        log: core::ptr::null_mut(), +    } +} + +/// Registers a KUnit test suite. +/// +/// # Safety +/// +/// `test_cases` must be a NULL terminated array of valid test cases, +/// whose lifetime is at least that of the test suite (i.e., static). +/// +/// # Examples +/// +/// ```ignore +/// extern "C" fn test_fn(_test: *mut kernel::bindings::kunit) { +///     let actual = 1 + 1; +///     let expected = 2; +///     assert_eq!(actual, expected); +/// } +/// +/// static mut KUNIT_TEST_CASES: [kernel::bindings::kunit_case; 2] = [ +///     kernel::kunit::kunit_case(kernel::c_str!("name"), test_fn), +///     kernel::kunit::kunit_case_null(), +/// ]; +/// kernel::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES); +/// ``` +#[doc(hidden)] +#[macro_export] +macro_rules! kunit_unsafe_test_suite { +    ($name:ident, $test_cases:ident) => { +        const _: () = { +            const KUNIT_TEST_SUITE_NAME: [::kernel::ffi::c_char; 256] = { +                let name_u8 = ::core::stringify!($name).as_bytes(); +                let mut ret = [0; 256]; + +                if name_u8.len() > 255 { +                    panic!(concat!( +                        "The test suite name `", +                        ::core::stringify!($name), +                        "` exceeds the maximum length of 255 bytes." +                    )); +                } + +                let mut i = 0; +                while i < name_u8.len() { +                    ret[i] = name_u8[i] as ::kernel::ffi::c_char; +                    i += 1; +                } + +                ret +            }; + +            static mut KUNIT_TEST_SUITE: ::kernel::bindings::kunit_suite = +                ::kernel::bindings::kunit_suite { +                    name: KUNIT_TEST_SUITE_NAME, +                    #[allow(unused_unsafe)] +                    // SAFETY: `$test_cases` is passed in by the user, and +                    // (as documented) must be valid for the lifetime of +                    // the suite (i.e., static). +                    test_cases: unsafe { +                        ::core::ptr::addr_of_mut!($test_cases) +                            .cast::<::kernel::bindings::kunit_case>() +                    }, +                    suite_init: None, +                    suite_exit: None, +                    init: None, +                    exit: None, +                    attr: ::kernel::bindings::kunit_attributes { +                        speed: ::kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL, +                    }, +                    status_comment: [0; 256usize], +                    debugfs: ::core::ptr::null_mut(), +                    log: ::core::ptr::null_mut(), +                    suite_init_err: 0, +                    is_init: false, +                }; + +            #[used] +            #[allow(unused_unsafe)] +            #[cfg_attr(not(target_os = "macos"), link_section = ".kunit_test_suites")] +            static mut KUNIT_TEST_SUITE_ENTRY: *const ::kernel::bindings::kunit_suite = +                // SAFETY: `KUNIT_TEST_SUITE` is static. +                unsafe { ::core::ptr::addr_of_mut!(KUNIT_TEST_SUITE) }; +        }; +    }; +} + +/// Returns whether we are currently running a KUnit test. +/// +/// In some cases, you need to call test-only code from outside the test case, for example, to +/// create a function mock. This function allows to change behavior depending on whether we are +/// currently running a KUnit test or not. +/// +/// # Examples +/// +/// This example shows how a function can be mocked to return a well-known value while testing: +/// +/// ``` +/// # use kernel::kunit::in_kunit_test; +/// fn fn_mock_example(n: i32) -> i32 { +///     if in_kunit_test() { +///         return 100; +///     } +/// +///     n + 1 +/// } +/// +/// let mock_res = fn_mock_example(5); +/// assert_eq!(mock_res, 100); +/// ``` +pub fn in_kunit_test() -> bool { +    // SAFETY: `kunit_get_current_test()` is always safe to call (it has fallbacks for +    // when KUnit is not enabled). +    !unsafe { bindings::kunit_get_current_test() }.is_null() +} + +#[kunit_tests(rust_kernel_kunit)] +mod tests { +    use super::*; + +    #[test] +    fn rust_test_kunit_example_test() { +        #![expect(clippy::eq_op)] +        assert_eq!(1 + 1, 2); +    } + +    #[test] +    fn rust_test_kunit_in_kunit_test() { +        assert!(in_kunit_test()); +    } +} | 
