xref: /linux-6.15/rust/kernel/kunit.rs (revision 100af58c)
1a66d733dSMiguel Ojeda // SPDX-License-Identifier: GPL-2.0
2a66d733dSMiguel Ojeda 
3a66d733dSMiguel Ojeda //! KUnit-based macros for Rust unit tests.
4a66d733dSMiguel Ojeda //!
5bc2e7d5cSMiguel Ojeda //! C header: [`include/kunit/test.h`](srctree/include/kunit/test.h)
6a66d733dSMiguel Ojeda //!
7a66d733dSMiguel Ojeda //! Reference: <https://docs.kernel.org/dev-tools/kunit/index.html>
8a66d733dSMiguel Ojeda 
9a66d733dSMiguel Ojeda use core::{ffi::c_void, fmt};
10a66d733dSMiguel Ojeda 
11a66d733dSMiguel Ojeda /// Prints a KUnit error-level message.
12a66d733dSMiguel Ojeda ///
13a66d733dSMiguel Ojeda /// Public but hidden since it should only be used from KUnit generated code.
14a66d733dSMiguel Ojeda #[doc(hidden)]
err(args: fmt::Arguments<'_>)15a66d733dSMiguel Ojeda pub fn err(args: fmt::Arguments<'_>) {
16a66d733dSMiguel Ojeda     // SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we
17a66d733dSMiguel Ojeda     // are passing.
18a66d733dSMiguel Ojeda     #[cfg(CONFIG_PRINTK)]
19a66d733dSMiguel Ojeda     unsafe {
20a66d733dSMiguel Ojeda         bindings::_printk(
2105cef2c4SMiguel Ojeda             c"\x013%pA".as_ptr() as _,
22a66d733dSMiguel Ojeda             &args as *const _ as *const c_void,
23a66d733dSMiguel Ojeda         );
24a66d733dSMiguel Ojeda     }
25a66d733dSMiguel Ojeda }
26a66d733dSMiguel Ojeda 
27a66d733dSMiguel Ojeda /// Prints a KUnit info-level message.
28a66d733dSMiguel Ojeda ///
29a66d733dSMiguel Ojeda /// Public but hidden since it should only be used from KUnit generated code.
30a66d733dSMiguel Ojeda #[doc(hidden)]
info(args: fmt::Arguments<'_>)31a66d733dSMiguel Ojeda pub fn info(args: fmt::Arguments<'_>) {
32a66d733dSMiguel Ojeda     // SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we
33a66d733dSMiguel Ojeda     // are passing.
34a66d733dSMiguel Ojeda     #[cfg(CONFIG_PRINTK)]
35a66d733dSMiguel Ojeda     unsafe {
36a66d733dSMiguel Ojeda         bindings::_printk(
3705cef2c4SMiguel Ojeda             c"\x016%pA".as_ptr() as _,
38a66d733dSMiguel Ojeda             &args as *const _ as *const c_void,
39a66d733dSMiguel Ojeda         );
40a66d733dSMiguel Ojeda     }
41a66d733dSMiguel Ojeda }
42a66d733dSMiguel Ojeda 
43c0010452SJosé Expósito use macros::kunit_tests;
44c0010452SJosé Expósito 
45a66d733dSMiguel Ojeda /// Asserts that a boolean expression is `true` at runtime.
46a66d733dSMiguel Ojeda ///
47a66d733dSMiguel Ojeda /// Public but hidden since it should only be used from generated tests.
48a66d733dSMiguel Ojeda ///
49a66d733dSMiguel Ojeda /// Unlike the one in `core`, this one does not panic; instead, it is mapped to the KUnit
50a66d733dSMiguel Ojeda /// facilities. See [`assert!`] for more details.
51a66d733dSMiguel Ojeda #[doc(hidden)]
52a66d733dSMiguel Ojeda #[macro_export]
53a66d733dSMiguel Ojeda macro_rules! kunit_assert {
54a66d733dSMiguel Ojeda     ($name:literal, $file:literal, $diff:expr, $condition:expr $(,)?) => {
55a66d733dSMiguel Ojeda         'out: {
56a66d733dSMiguel Ojeda             // Do nothing if the condition is `true`.
57a66d733dSMiguel Ojeda             if $condition {
58a66d733dSMiguel Ojeda                 break 'out;
59a66d733dSMiguel Ojeda             }
60a66d733dSMiguel Ojeda 
61a66d733dSMiguel Ojeda             static FILE: &'static $crate::str::CStr = $crate::c_str!($file);
62a66d733dSMiguel Ojeda             static LINE: i32 = core::line!() as i32 - $diff;
63a66d733dSMiguel Ojeda             static CONDITION: &'static $crate::str::CStr = $crate::c_str!(stringify!($condition));
64a66d733dSMiguel Ojeda 
65a66d733dSMiguel Ojeda             // SAFETY: FFI call without safety requirements.
66a66d733dSMiguel Ojeda             let kunit_test = unsafe { $crate::bindings::kunit_get_current_test() };
67a66d733dSMiguel Ojeda             if kunit_test.is_null() {
68a66d733dSMiguel Ojeda                 // The assertion failed but this task is not running a KUnit test, so we cannot call
69a66d733dSMiguel Ojeda                 // KUnit, but at least print an error to the kernel log. This may happen if this
70a66d733dSMiguel Ojeda                 // macro is called from an spawned thread in a test (see
71a66d733dSMiguel Ojeda                 // `scripts/rustdoc_test_gen.rs`) or if some non-test code calls this macro by
72a66d733dSMiguel Ojeda                 // mistake (it is hidden to prevent that).
73a66d733dSMiguel Ojeda                 //
74a66d733dSMiguel Ojeda                 // This mimics KUnit's failed assertion format.
75a66d733dSMiguel Ojeda                 $crate::kunit::err(format_args!(
76a66d733dSMiguel Ojeda                     "    # {}: ASSERTION FAILED at {FILE}:{LINE}\n",
77a66d733dSMiguel Ojeda                     $name
78a66d733dSMiguel Ojeda                 ));
79a66d733dSMiguel Ojeda                 $crate::kunit::err(format_args!(
80a66d733dSMiguel Ojeda                     "    Expected {CONDITION} to be true, but is false\n"
81a66d733dSMiguel Ojeda                 ));
82a66d733dSMiguel Ojeda                 $crate::kunit::err(format_args!(
83a66d733dSMiguel Ojeda                     "    Failure not reported to KUnit since this is a non-KUnit task\n"
84a66d733dSMiguel Ojeda                 ));
85a66d733dSMiguel Ojeda                 break 'out;
86a66d733dSMiguel Ojeda             }
87a66d733dSMiguel Ojeda 
88a66d733dSMiguel Ojeda             #[repr(transparent)]
89a66d733dSMiguel Ojeda             struct Location($crate::bindings::kunit_loc);
90a66d733dSMiguel Ojeda 
91a66d733dSMiguel Ojeda             #[repr(transparent)]
92a66d733dSMiguel Ojeda             struct UnaryAssert($crate::bindings::kunit_unary_assert);
93a66d733dSMiguel Ojeda 
94a66d733dSMiguel Ojeda             // SAFETY: There is only a static instance and in that one the pointer field points to
95a66d733dSMiguel Ojeda             // an immutable C string.
96a66d733dSMiguel Ojeda             unsafe impl Sync for Location {}
97a66d733dSMiguel Ojeda 
98a66d733dSMiguel Ojeda             // SAFETY: There is only a static instance and in that one the pointer field points to
99a66d733dSMiguel Ojeda             // an immutable C string.
100a66d733dSMiguel Ojeda             unsafe impl Sync for UnaryAssert {}
101a66d733dSMiguel Ojeda 
102a66d733dSMiguel Ojeda             static LOCATION: Location = Location($crate::bindings::kunit_loc {
103a66d733dSMiguel Ojeda                 file: FILE.as_char_ptr(),
104a66d733dSMiguel Ojeda                 line: LINE,
105a66d733dSMiguel Ojeda             });
106a66d733dSMiguel Ojeda             static ASSERTION: UnaryAssert = UnaryAssert($crate::bindings::kunit_unary_assert {
107a66d733dSMiguel Ojeda                 assert: $crate::bindings::kunit_assert {},
108a66d733dSMiguel Ojeda                 condition: CONDITION.as_char_ptr(),
109a66d733dSMiguel Ojeda                 expected_true: true,
110a66d733dSMiguel Ojeda             });
111a66d733dSMiguel Ojeda 
112a66d733dSMiguel Ojeda             // SAFETY:
113a66d733dSMiguel Ojeda             //   - FFI call.
114a66d733dSMiguel Ojeda             //   - The `kunit_test` pointer is valid because we got it from
115a66d733dSMiguel Ojeda             //     `kunit_get_current_test()` and it was not null. This means we are in a KUnit
116a66d733dSMiguel Ojeda             //     test, and that the pointer can be passed to KUnit functions and assertions.
117a66d733dSMiguel Ojeda             //   - The string pointers (`file` and `condition` above) point to null-terminated
118a66d733dSMiguel Ojeda             //     strings since they are `CStr`s.
119a66d733dSMiguel Ojeda             //   - The function pointer (`format`) points to the proper function.
120a66d733dSMiguel Ojeda             //   - The pointers passed will remain valid since they point to `static`s.
121a66d733dSMiguel Ojeda             //   - The format string is allowed to be null.
122a66d733dSMiguel Ojeda             //   - There are, however, problems with this: first of all, this will end up stopping
123a66d733dSMiguel Ojeda             //     the thread, without running destructors. While that is problematic in itself,
124a66d733dSMiguel Ojeda             //     it is considered UB to have what is effectively a forced foreign unwind
125a66d733dSMiguel Ojeda             //     with `extern "C"` ABI. One could observe the stack that is now gone from
126a66d733dSMiguel Ojeda             //     another thread. We should avoid pinning stack variables to prevent library UB,
127a66d733dSMiguel Ojeda             //     too. For the moment, given that test failures are reported immediately before the
128a66d733dSMiguel Ojeda             //     next test runs, that test failures should be fixed and that KUnit is explicitly
129a66d733dSMiguel Ojeda             //     documented as not suitable for production environments, we feel it is reasonable.
130a66d733dSMiguel Ojeda             unsafe {
131a66d733dSMiguel Ojeda                 $crate::bindings::__kunit_do_failed_assertion(
132a66d733dSMiguel Ojeda                     kunit_test,
133a66d733dSMiguel Ojeda                     core::ptr::addr_of!(LOCATION.0),
134a66d733dSMiguel Ojeda                     $crate::bindings::kunit_assert_type_KUNIT_ASSERTION,
135a66d733dSMiguel Ojeda                     core::ptr::addr_of!(ASSERTION.0.assert),
136a66d733dSMiguel Ojeda                     Some($crate::bindings::kunit_unary_assert_format),
137a66d733dSMiguel Ojeda                     core::ptr::null(),
138a66d733dSMiguel Ojeda                 );
139a66d733dSMiguel Ojeda             }
140a66d733dSMiguel Ojeda 
141a66d733dSMiguel Ojeda             // SAFETY: FFI call; the `test` pointer is valid because this hidden macro should only
142a66d733dSMiguel Ojeda             // be called by the generated documentation tests which forward the test pointer given
143a66d733dSMiguel Ojeda             // by KUnit.
144a66d733dSMiguel Ojeda             unsafe {
145a66d733dSMiguel Ojeda                 $crate::bindings::__kunit_abort(kunit_test);
146a66d733dSMiguel Ojeda             }
147a66d733dSMiguel Ojeda         }
148a66d733dSMiguel Ojeda     };
149a66d733dSMiguel Ojeda }
150a66d733dSMiguel Ojeda 
151a66d733dSMiguel Ojeda /// Asserts that two expressions are equal to each other (using [`PartialEq`]).
152a66d733dSMiguel Ojeda ///
153a66d733dSMiguel Ojeda /// Public but hidden since it should only be used from generated tests.
154a66d733dSMiguel Ojeda ///
155a66d733dSMiguel Ojeda /// Unlike the one in `core`, this one does not panic; instead, it is mapped to the KUnit
156a66d733dSMiguel Ojeda /// facilities. See [`assert!`] for more details.
157a66d733dSMiguel Ojeda #[doc(hidden)]
158a66d733dSMiguel Ojeda #[macro_export]
159a66d733dSMiguel Ojeda macro_rules! kunit_assert_eq {
160a66d733dSMiguel Ojeda     ($name:literal, $file:literal, $diff:expr, $left:expr, $right:expr $(,)?) => {{
161a66d733dSMiguel Ojeda         // For the moment, we just forward to the expression assert because, for binary asserts,
162a66d733dSMiguel Ojeda         // KUnit supports only a few types (e.g. integers).
163a66d733dSMiguel Ojeda         $crate::kunit_assert!($name, $file, $diff, $left == $right);
164a66d733dSMiguel Ojeda     }};
165a66d733dSMiguel Ojeda }
16622097b96SJosé Expósito 
16722097b96SJosé Expósito /// Represents an individual test case.
16822097b96SJosé Expósito ///
16922097b96SJosé Expósito /// The [`kunit_unsafe_test_suite!`] macro expects a NULL-terminated list of valid test cases.
17022097b96SJosé Expósito /// Use [`kunit_case_null`] to generate such a delimiter.
17122097b96SJosé Expósito #[doc(hidden)]
kunit_case( name: &'static kernel::str::CStr, run_case: unsafe extern "C" fn(*mut kernel::bindings::kunit), ) -> kernel::bindings::kunit_case17222097b96SJosé Expósito pub const fn kunit_case(
17322097b96SJosé Expósito     name: &'static kernel::str::CStr,
17422097b96SJosé Expósito     run_case: unsafe extern "C" fn(*mut kernel::bindings::kunit),
17522097b96SJosé Expósito ) -> kernel::bindings::kunit_case {
17622097b96SJosé Expósito     kernel::bindings::kunit_case {
17722097b96SJosé Expósito         run_case: Some(run_case),
17822097b96SJosé Expósito         name: name.as_char_ptr(),
17922097b96SJosé Expósito         attr: kernel::bindings::kunit_attributes {
18022097b96SJosé Expósito             speed: kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
18122097b96SJosé Expósito         },
18222097b96SJosé Expósito         generate_params: None,
18322097b96SJosé Expósito         status: kernel::bindings::kunit_status_KUNIT_SUCCESS,
18422097b96SJosé Expósito         module_name: core::ptr::null_mut(),
18522097b96SJosé Expósito         log: core::ptr::null_mut(),
18622097b96SJosé Expósito     }
18722097b96SJosé Expósito }
18822097b96SJosé Expósito 
18922097b96SJosé Expósito /// Represents the NULL test case delimiter.
19022097b96SJosé Expósito ///
19122097b96SJosé Expósito /// The [`kunit_unsafe_test_suite!`] macro expects a NULL-terminated list of test cases. This
19222097b96SJosé Expósito /// function returns such a delimiter.
19322097b96SJosé Expósito #[doc(hidden)]
kunit_case_null() -> kernel::bindings::kunit_case19422097b96SJosé Expósito pub const fn kunit_case_null() -> kernel::bindings::kunit_case {
19522097b96SJosé Expósito     kernel::bindings::kunit_case {
19622097b96SJosé Expósito         run_case: None,
19722097b96SJosé Expósito         name: core::ptr::null_mut(),
19822097b96SJosé Expósito         generate_params: None,
19922097b96SJosé Expósito         attr: kernel::bindings::kunit_attributes {
20022097b96SJosé Expósito             speed: kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
20122097b96SJosé Expósito         },
20222097b96SJosé Expósito         status: kernel::bindings::kunit_status_KUNIT_SUCCESS,
20322097b96SJosé Expósito         module_name: core::ptr::null_mut(),
20422097b96SJosé Expósito         log: core::ptr::null_mut(),
20522097b96SJosé Expósito     }
20622097b96SJosé Expósito }
20722097b96SJosé Expósito 
20822097b96SJosé Expósito /// Registers a KUnit test suite.
20922097b96SJosé Expósito ///
21022097b96SJosé Expósito /// # Safety
21122097b96SJosé Expósito ///
21222097b96SJosé Expósito /// `test_cases` must be a NULL terminated array of valid test cases,
21322097b96SJosé Expósito /// whose lifetime is at least that of the test suite (i.e., static).
21422097b96SJosé Expósito ///
21522097b96SJosé Expósito /// # Examples
21622097b96SJosé Expósito ///
21722097b96SJosé Expósito /// ```ignore
21822097b96SJosé Expósito /// extern "C" fn test_fn(_test: *mut kernel::bindings::kunit) {
21922097b96SJosé Expósito ///     let actual = 1 + 1;
22022097b96SJosé Expósito ///     let expected = 2;
22122097b96SJosé Expósito ///     assert_eq!(actual, expected);
22222097b96SJosé Expósito /// }
22322097b96SJosé Expósito ///
22422097b96SJosé Expósito /// static mut KUNIT_TEST_CASES: [kernel::bindings::kunit_case; 2] = [
22522097b96SJosé Expósito ///     kernel::kunit::kunit_case(kernel::c_str!("name"), test_fn),
22622097b96SJosé Expósito ///     kernel::kunit::kunit_case_null(),
22722097b96SJosé Expósito /// ];
22822097b96SJosé Expósito /// kernel::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
22922097b96SJosé Expósito /// ```
23022097b96SJosé Expósito #[doc(hidden)]
23122097b96SJosé Expósito #[macro_export]
23222097b96SJosé Expósito macro_rules! kunit_unsafe_test_suite {
23322097b96SJosé Expósito     ($name:ident, $test_cases:ident) => {
23422097b96SJosé Expósito         const _: () = {
23522097b96SJosé Expósito             const KUNIT_TEST_SUITE_NAME: [::kernel::ffi::c_char; 256] = {
23622097b96SJosé Expósito                 let name_u8 = ::core::stringify!($name).as_bytes();
23722097b96SJosé Expósito                 let mut ret = [0; 256];
23822097b96SJosé Expósito 
23922097b96SJosé Expósito                 if name_u8.len() > 255 {
24022097b96SJosé Expósito                     panic!(concat!(
24122097b96SJosé Expósito                         "The test suite name `",
24222097b96SJosé Expósito                         ::core::stringify!($name),
24322097b96SJosé Expósito                         "` exceeds the maximum length of 255 bytes."
24422097b96SJosé Expósito                     ));
24522097b96SJosé Expósito                 }
24622097b96SJosé Expósito 
24722097b96SJosé Expósito                 let mut i = 0;
24822097b96SJosé Expósito                 while i < name_u8.len() {
24922097b96SJosé Expósito                     ret[i] = name_u8[i] as ::kernel::ffi::c_char;
25022097b96SJosé Expósito                     i += 1;
25122097b96SJosé Expósito                 }
25222097b96SJosé Expósito 
25322097b96SJosé Expósito                 ret
25422097b96SJosé Expósito             };
25522097b96SJosé Expósito 
25622097b96SJosé Expósito             static mut KUNIT_TEST_SUITE: ::kernel::bindings::kunit_suite =
25722097b96SJosé Expósito                 ::kernel::bindings::kunit_suite {
25822097b96SJosé Expósito                     name: KUNIT_TEST_SUITE_NAME,
25922097b96SJosé Expósito                     #[allow(unused_unsafe)]
26022097b96SJosé Expósito                     // SAFETY: `$test_cases` is passed in by the user, and
26122097b96SJosé Expósito                     // (as documented) must be valid for the lifetime of
26222097b96SJosé Expósito                     // the suite (i.e., static).
26322097b96SJosé Expósito                     test_cases: unsafe {
26422097b96SJosé Expósito                         ::core::ptr::addr_of_mut!($test_cases)
26522097b96SJosé Expósito                             .cast::<::kernel::bindings::kunit_case>()
26622097b96SJosé Expósito                     },
26722097b96SJosé Expósito                     suite_init: None,
26822097b96SJosé Expósito                     suite_exit: None,
26922097b96SJosé Expósito                     init: None,
27022097b96SJosé Expósito                     exit: None,
27122097b96SJosé Expósito                     attr: ::kernel::bindings::kunit_attributes {
27222097b96SJosé Expósito                         speed: ::kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
27322097b96SJosé Expósito                     },
27422097b96SJosé Expósito                     status_comment: [0; 256usize],
27522097b96SJosé Expósito                     debugfs: ::core::ptr::null_mut(),
27622097b96SJosé Expósito                     log: ::core::ptr::null_mut(),
27722097b96SJosé Expósito                     suite_init_err: 0,
27822097b96SJosé Expósito                     is_init: false,
27922097b96SJosé Expósito                 };
28022097b96SJosé Expósito 
28122097b96SJosé Expósito             #[used]
28222097b96SJosé Expósito             #[allow(unused_unsafe)]
28322097b96SJosé Expósito             #[cfg_attr(not(target_os = "macos"), link_section = ".kunit_test_suites")]
28422097b96SJosé Expósito             static mut KUNIT_TEST_SUITE_ENTRY: *const ::kernel::bindings::kunit_suite =
28522097b96SJosé Expósito                 // SAFETY: `KUNIT_TEST_SUITE` is static.
28622097b96SJosé Expósito                 unsafe { ::core::ptr::addr_of_mut!(KUNIT_TEST_SUITE) };
28722097b96SJosé Expósito         };
28822097b96SJosé Expósito     };
28922097b96SJosé Expósito }
290c0010452SJosé Expósito 
291*100af58cSJosé Expósito /// Returns whether we are currently running a KUnit test.
292*100af58cSJosé Expósito ///
293*100af58cSJosé Expósito /// In some cases, you need to call test-only code from outside the test case, for example, to
294*100af58cSJosé Expósito /// create a function mock. This function allows to change behavior depending on whether we are
295*100af58cSJosé Expósito /// currently running a KUnit test or not.
296*100af58cSJosé Expósito ///
297*100af58cSJosé Expósito /// # Examples
298*100af58cSJosé Expósito ///
299*100af58cSJosé Expósito /// This example shows how a function can be mocked to return a well-known value while testing:
300*100af58cSJosé Expósito ///
301*100af58cSJosé Expósito /// ```
302*100af58cSJosé Expósito /// # use kernel::kunit::in_kunit_test;
303*100af58cSJosé Expósito /// fn fn_mock_example(n: i32) -> i32 {
304*100af58cSJosé Expósito ///     if in_kunit_test() {
305*100af58cSJosé Expósito ///         return 100;
306*100af58cSJosé Expósito ///     }
307*100af58cSJosé Expósito ///
308*100af58cSJosé Expósito ///     n + 1
309*100af58cSJosé Expósito /// }
310*100af58cSJosé Expósito ///
311*100af58cSJosé Expósito /// let mock_res = fn_mock_example(5);
312*100af58cSJosé Expósito /// assert_eq!(mock_res, 100);
313*100af58cSJosé Expósito /// ```
in_kunit_test() -> bool314*100af58cSJosé Expósito pub fn in_kunit_test() -> bool {
315*100af58cSJosé Expósito     // SAFETY: `kunit_get_current_test()` is always safe to call (it has fallbacks for
316*100af58cSJosé Expósito     // when KUnit is not enabled).
317*100af58cSJosé Expósito     !unsafe { bindings::kunit_get_current_test() }.is_null()
318*100af58cSJosé Expósito }
319*100af58cSJosé Expósito 
320c0010452SJosé Expósito #[kunit_tests(rust_kernel_kunit)]
321c0010452SJosé Expósito mod tests {
322*100af58cSJosé Expósito     use super::*;
323*100af58cSJosé Expósito 
324c0010452SJosé Expósito     #[test]
rust_test_kunit_example_test()325c0010452SJosé Expósito     fn rust_test_kunit_example_test() {
326c0010452SJosé Expósito         #![expect(clippy::eq_op)]
327c0010452SJosé Expósito         assert_eq!(1 + 1, 2);
328c0010452SJosé Expósito     }
329*100af58cSJosé Expósito 
330*100af58cSJosé Expósito     #[test]
rust_test_kunit_in_kunit_test()331*100af58cSJosé Expósito     fn rust_test_kunit_in_kunit_test() {
332*100af58cSJosé Expósito         assert!(in_kunit_test());
333*100af58cSJosé Expósito     }
334c0010452SJosé Expósito }
335