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