xref: /linux/rust/kernel/kunit.rs (revision 3593e678f5e8b21b8f54b4267027bd55b19cb7b8)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 //! KUnit-based macros for Rust unit tests.
4 //!
5 //! C header: [`include/kunit/test.h`](srctree/include/kunit/test.h)
6 //!
7 //! Reference: <https://docs.kernel.org/dev-tools/kunit/index.html>
8 
9 use crate::fmt;
10 use crate::prelude::*;
11 
12 /// Prints a KUnit error-level message.
13 ///
14 /// Public but hidden since it should only be used from KUnit generated code.
15 #[doc(hidden)]
err(args: fmt::Arguments<'_>)16 pub fn err(args: fmt::Arguments<'_>) {
17     // `args` is unused if `CONFIG_PRINTK` is not set - this avoids a build-time warning.
18     #[cfg(not(CONFIG_PRINTK))]
19     let _ = args;
20 
21     // SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we
22     // are passing.
23     #[cfg(CONFIG_PRINTK)]
24     unsafe {
25         bindings::_printk(
26             c"\x013%pA".as_char_ptr(),
27             core::ptr::from_ref(&args).cast::<c_void>(),
28         );
29     }
30 }
31 
32 /// Prints a KUnit info-level message.
33 ///
34 /// Public but hidden since it should only be used from KUnit generated code.
35 #[doc(hidden)]
info(args: fmt::Arguments<'_>)36 pub fn info(args: fmt::Arguments<'_>) {
37     // `args` is unused if `CONFIG_PRINTK` is not set - this avoids a build-time warning.
38     #[cfg(not(CONFIG_PRINTK))]
39     let _ = args;
40 
41     // SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we
42     // are passing.
43     #[cfg(CONFIG_PRINTK)]
44     unsafe {
45         bindings::_printk(
46             c"\x016%pA".as_char_ptr(),
47             core::ptr::from_ref(&args).cast::<c_void>(),
48         );
49     }
50 }
51 
52 /// Asserts that a boolean expression is `true` at runtime.
53 ///
54 /// Public but hidden since it should only be used from generated tests.
55 ///
56 /// Unlike the one in `core`, this one does not panic; instead, it is mapped to the KUnit
57 /// facilities. See [`assert!`] for more details.
58 #[doc(hidden)]
59 #[macro_export]
60 macro_rules! kunit_assert {
61     ($name:literal, $file:literal, $diff:expr, $condition:expr $(,)?) => {
62         'out: {
63             // Do nothing if the condition is `true`.
64             if $condition {
65                 break 'out;
66             }
67 
68             static FILE: &'static $crate::str::CStr = $file;
69             static LINE: i32 = ::core::line!() as i32 - $diff;
70             static CONDITION: &'static $crate::str::CStr = $crate::c_str!(stringify!($condition));
71 
72             // SAFETY: FFI call without safety requirements.
73             let kunit_test = unsafe { $crate::bindings::kunit_get_current_test() };
74             if kunit_test.is_null() {
75                 // The assertion failed but this task is not running a KUnit test, so we cannot call
76                 // KUnit, but at least print an error to the kernel log. This may happen if this
77                 // macro is called from an spawned thread in a test (see
78                 // `scripts/rustdoc_test_gen.rs`) or if some non-test code calls this macro by
79                 // mistake (it is hidden to prevent that).
80                 //
81                 // This mimics KUnit's failed assertion format.
82                 $crate::kunit::err($crate::prelude::fmt!(
83                     "    # {}: ASSERTION FAILED at {FILE}:{LINE}\n",
84                     $name
85                 ));
86                 $crate::kunit::err($crate::prelude::fmt!(
87                     "    Expected {CONDITION} to be true, but is false\n"
88                 ));
89                 $crate::kunit::err($crate::prelude::fmt!(
90                     "    Failure not reported to KUnit since this is a non-KUnit task\n"
91                 ));
92                 break 'out;
93             }
94 
95             #[repr(transparent)]
96             struct Location($crate::bindings::kunit_loc);
97 
98             #[repr(transparent)]
99             struct UnaryAssert($crate::bindings::kunit_unary_assert);
100 
101             // SAFETY: There is only a static instance and in that one the pointer field points to
102             // an immutable C string.
103             unsafe impl Sync for Location {}
104 
105             // SAFETY: There is only a static instance and in that one the pointer field points to
106             // an immutable C string.
107             unsafe impl Sync for UnaryAssert {}
108 
109             static LOCATION: Location = Location($crate::bindings::kunit_loc {
110                 file: $crate::str::as_char_ptr_in_const_context(FILE),
111                 line: LINE,
112             });
113             static ASSERTION: UnaryAssert = UnaryAssert($crate::bindings::kunit_unary_assert {
114                 assert: $crate::bindings::kunit_assert {},
115                 condition: $crate::str::as_char_ptr_in_const_context(CONDITION),
116                 expected_true: true,
117             });
118 
119             // SAFETY:
120             //   - FFI call.
121             //   - The `kunit_test` pointer is valid because we got it from
122             //     `kunit_get_current_test()` and it was not null. This means we are in a KUnit
123             //     test, and that the pointer can be passed to KUnit functions and assertions.
124             //   - The string pointers (`file` and `condition` above) point to null-terminated
125             //     strings since they are `CStr`s.
126             //   - The function pointer (`format`) points to the proper function.
127             //   - The pointers passed will remain valid since they point to `static`s.
128             //   - The format string is allowed to be null.
129             //   - There are, however, problems with this: first of all, this will end up stopping
130             //     the thread, without running destructors. While that is problematic in itself,
131             //     it is considered UB to have what is effectively a forced foreign unwind
132             //     with `extern "C"` ABI. One could observe the stack that is now gone from
133             //     another thread. We should avoid pinning stack variables to prevent library UB,
134             //     too. For the moment, given that test failures are reported immediately before the
135             //     next test runs, that test failures should be fixed and that KUnit is explicitly
136             //     documented as not suitable for production environments, we feel it is reasonable.
137             unsafe {
138                 $crate::bindings::__kunit_do_failed_assertion(
139                     kunit_test,
140                     ::core::ptr::addr_of!(LOCATION.0),
141                     $crate::bindings::kunit_assert_type_KUNIT_ASSERTION,
142                     ::core::ptr::addr_of!(ASSERTION.0.assert),
143                     Some($crate::bindings::kunit_unary_assert_format),
144                     ::core::ptr::null(),
145                 );
146             }
147 
148             // SAFETY: FFI call; the `test` pointer is valid because this hidden macro should only
149             // be called by the generated documentation tests which forward the test pointer given
150             // by KUnit.
151             unsafe {
152                 $crate::bindings::__kunit_abort(kunit_test);
153             }
154         }
155     };
156 }
157 
158 /// Asserts that two expressions are equal to each other (using [`PartialEq`]).
159 ///
160 /// Public but hidden since it should only be used from generated tests.
161 ///
162 /// Unlike the one in `core`, this one does not panic; instead, it is mapped to the KUnit
163 /// facilities. See [`assert!`] for more details.
164 #[doc(hidden)]
165 #[macro_export]
166 macro_rules! kunit_assert_eq {
167     ($name:literal, $file:literal, $diff:expr, $left:expr, $right:expr $(,)?) => {{
168         // For the moment, we just forward to the expression assert because, for binary asserts,
169         // KUnit supports only a few types (e.g. integers).
170         $crate::kunit_assert!($name, $file, $diff, $left == $right);
171     }};
172 }
173 
174 trait TestResult {
is_test_result_ok(&self) -> bool175     fn is_test_result_ok(&self) -> bool;
176 }
177 
178 impl TestResult for () {
is_test_result_ok(&self) -> bool179     fn is_test_result_ok(&self) -> bool {
180         true
181     }
182 }
183 
184 impl<T, E> TestResult for Result<T, E> {
is_test_result_ok(&self) -> bool185     fn is_test_result_ok(&self) -> bool {
186         self.is_ok()
187     }
188 }
189 
190 /// Returns whether a test result is to be considered OK.
191 ///
192 /// This will be `assert!`ed from the generated tests.
193 #[doc(hidden)]
194 #[expect(private_bounds)]
is_test_result_ok(t: impl TestResult) -> bool195 pub fn is_test_result_ok(t: impl TestResult) -> bool {
196     t.is_test_result_ok()
197 }
198 
199 /// Represents an individual test case.
200 #[doc(hidden)]
kunit_case( name: &'static kernel::str::CStr, run_case: unsafe extern "C" fn(*mut kernel::bindings::kunit), ) -> kernel::bindings::kunit_case201 pub const fn kunit_case(
202     name: &'static kernel::str::CStr,
203     run_case: unsafe extern "C" fn(*mut kernel::bindings::kunit),
204 ) -> kernel::bindings::kunit_case {
205     kernel::bindings::kunit_case {
206         run_case: Some(run_case),
207         name: kernel::str::as_char_ptr_in_const_context(name),
208         attr: kernel::bindings::kunit_attributes {
209             speed: kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
210         },
211         generate_params: None,
212         status: kernel::bindings::kunit_status_KUNIT_SUCCESS,
213         module_name: core::ptr::null_mut(),
214         log: core::ptr::null_mut(),
215         param_init: None,
216         param_exit: None,
217     }
218 }
219 
220 /// Registers a KUnit test suite.
221 ///
222 /// # Safety
223 ///
224 /// `test_cases` must be a `NULL` terminated array of valid test cases,
225 /// whose lifetime is at least that of the test suite (i.e., static).
226 ///
227 /// # Examples
228 ///
229 /// ```ignore
230 /// extern "C" fn test_fn(_test: *mut kernel::bindings::kunit) {
231 ///     let actual = 1 + 1;
232 ///     let expected = 2;
233 ///     assert_eq!(actual, expected);
234 /// }
235 ///
236 /// static mut KUNIT_TEST_CASES: [kernel::bindings::kunit_case; 2] = [
237 ///     kernel::kunit::kunit_case(c"name", test_fn),
238 ///     pin_init::zeroed(),
239 /// ];
240 /// kernel::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
241 /// ```
242 #[doc(hidden)]
243 #[macro_export]
244 macro_rules! kunit_unsafe_test_suite {
245     ($name:ident, $test_cases:ident) => {
246         const _: () = {
247             const KUNIT_TEST_SUITE_NAME: [::kernel::ffi::c_char; 256] = {
248                 let name_u8 = ::core::stringify!($name).as_bytes();
249                 let mut ret = [0; 256];
250 
251                 if name_u8.len() > 255 {
252                     panic!(concat!(
253                         "The test suite name `",
254                         ::core::stringify!($name),
255                         "` exceeds the maximum length of 255 bytes."
256                     ));
257                 }
258 
259                 let mut i = 0;
260                 while i < name_u8.len() {
261                     ret[i] = name_u8[i] as ::kernel::ffi::c_char;
262                     i += 1;
263                 }
264 
265                 ret
266             };
267 
268             static mut KUNIT_TEST_SUITE: ::kernel::bindings::kunit_suite =
269                 ::kernel::bindings::kunit_suite {
270                     name: KUNIT_TEST_SUITE_NAME,
271                     #[allow(unused_unsafe)]
272                     // SAFETY: `$test_cases` is passed in by the user, and
273                     // (as documented) must be valid for the lifetime of
274                     // the suite (i.e., static).
275                     test_cases: unsafe {
276                         ::core::ptr::addr_of_mut!($test_cases)
277                             .cast::<::kernel::bindings::kunit_case>()
278                     },
279                     suite_init: None,
280                     suite_exit: None,
281                     init: None,
282                     exit: None,
283                     attr: ::kernel::bindings::kunit_attributes {
284                         speed: ::kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
285                     },
286                     status_comment: [0; 256usize],
287                     debugfs: ::core::ptr::null_mut(),
288                     log: ::core::ptr::null_mut(),
289                     suite_init_err: 0,
290                     is_init: false,
291                 };
292 
293             #[used(compiler)]
294             #[allow(unused_unsafe)]
295             #[cfg_attr(not(target_os = "macos"), link_section = ".kunit_test_suites")]
296             static mut KUNIT_TEST_SUITE_ENTRY: *const ::kernel::bindings::kunit_suite =
297                 // SAFETY: `KUNIT_TEST_SUITE` is static.
298                 unsafe { ::core::ptr::addr_of_mut!(KUNIT_TEST_SUITE) };
299         };
300     };
301 }
302 
303 /// Returns whether we are currently running a KUnit test.
304 ///
305 /// In some cases, you need to call test-only code from outside the test case, for example, to
306 /// create a function mock. This function allows to change behavior depending on whether we are
307 /// currently running a KUnit test or not.
308 ///
309 /// # Examples
310 ///
311 /// This example shows how a function can be mocked to return a well-known value while testing:
312 ///
313 /// ```
314 /// # use kernel::kunit::in_kunit_test;
315 /// fn fn_mock_example(n: i32) -> i32 {
316 ///     if in_kunit_test() {
317 ///         return 100;
318 ///     }
319 ///
320 ///     n + 1
321 /// }
322 ///
323 /// let mock_res = fn_mock_example(5);
324 /// assert_eq!(mock_res, 100);
325 /// ```
in_kunit_test() -> bool326 pub fn in_kunit_test() -> bool {
327     // SAFETY: `kunit_get_current_test()` is always safe to call (it has fallbacks for
328     // when KUnit is not enabled).
329     !unsafe { bindings::kunit_get_current_test() }.is_null()
330 }
331 
332 #[kunit_tests(rust_kernel_kunit)]
333 mod tests {
334     use super::*;
335 
336     #[test]
rust_test_kunit_example_test()337     fn rust_test_kunit_example_test() {
338         assert_eq!(1 + 1, 2);
339     }
340 
341     #[test]
rust_test_kunit_in_kunit_test()342     fn rust_test_kunit_in_kunit_test() {
343         assert!(in_kunit_test());
344     }
345 
346     #[test]
347     #[cfg(not(all()))]
rust_test_kunit_always_disabled_test()348     fn rust_test_kunit_always_disabled_test() {
349         // This test should never run because of the `cfg`.
350         assert!(false);
351     }
352 }
353