Commit | Line | Data |
---|---|---|
a66d733d MO |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | ||
3 | //! KUnit-based macros for Rust unit tests. | |
4 | //! | |
5 | //! C header: [`include/kunit/test.h`](../../../../../include/kunit/test.h) | |
6 | //! | |
7 | //! Reference: <https://docs.kernel.org/dev-tools/kunit/index.html> | |
8 | ||
9 | use core::{ffi::c_void, fmt}; | |
10 | ||
11 | /// Prints a KUnit error-level message. | |
12 | /// | |
13 | /// Public but hidden since it should only be used from KUnit generated code. | |
14 | #[doc(hidden)] | |
15 | pub fn err(args: fmt::Arguments<'_>) { | |
16 | // SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we | |
17 | // are passing. | |
18 | #[cfg(CONFIG_PRINTK)] | |
19 | unsafe { | |
20 | bindings::_printk( | |
21 | b"\x013%pA\0".as_ptr() as _, | |
22 | &args as *const _ as *const c_void, | |
23 | ); | |
24 | } | |
25 | } | |
26 | ||
27 | /// Prints a KUnit info-level message. | |
28 | /// | |
29 | /// Public but hidden since it should only be used from KUnit generated code. | |
30 | #[doc(hidden)] | |
31 | pub fn info(args: fmt::Arguments<'_>) { | |
32 | // SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we | |
33 | // are passing. | |
34 | #[cfg(CONFIG_PRINTK)] | |
35 | unsafe { | |
36 | bindings::_printk( | |
37 | b"\x016%pA\0".as_ptr() as _, | |
38 | &args as *const _ as *const c_void, | |
39 | ); | |
40 | } | |
41 | } | |
42 | ||
43 | /// Asserts that a boolean expression is `true` at runtime. | |
44 | /// | |
45 | /// Public but hidden since it should only be used from generated tests. | |
46 | /// | |
47 | /// Unlike the one in `core`, this one does not panic; instead, it is mapped to the KUnit | |
48 | /// facilities. See [`assert!`] for more details. | |
49 | #[doc(hidden)] | |
50 | #[macro_export] | |
51 | macro_rules! kunit_assert { | |
52 | ($name:literal, $file:literal, $diff:expr, $condition:expr $(,)?) => { | |
53 | 'out: { | |
54 | // Do nothing if the condition is `true`. | |
55 | if $condition { | |
56 | break 'out; | |
57 | } | |
58 | ||
59 | static FILE: &'static $crate::str::CStr = $crate::c_str!($file); | |
60 | static LINE: i32 = core::line!() as i32 - $diff; | |
61 | static CONDITION: &'static $crate::str::CStr = $crate::c_str!(stringify!($condition)); | |
62 | ||
63 | // SAFETY: FFI call without safety requirements. | |
64 | let kunit_test = unsafe { $crate::bindings::kunit_get_current_test() }; | |
65 | if kunit_test.is_null() { | |
66 | // The assertion failed but this task is not running a KUnit test, so we cannot call | |
67 | // KUnit, but at least print an error to the kernel log. This may happen if this | |
68 | // macro is called from an spawned thread in a test (see | |
69 | // `scripts/rustdoc_test_gen.rs`) or if some non-test code calls this macro by | |
70 | // mistake (it is hidden to prevent that). | |
71 | // | |
72 | // This mimics KUnit's failed assertion format. | |
73 | $crate::kunit::err(format_args!( | |
74 | " # {}: ASSERTION FAILED at {FILE}:{LINE}\n", | |
75 | $name | |
76 | )); | |
77 | $crate::kunit::err(format_args!( | |
78 | " Expected {CONDITION} to be true, but is false\n" | |
79 | )); | |
80 | $crate::kunit::err(format_args!( | |
81 | " Failure not reported to KUnit since this is a non-KUnit task\n" | |
82 | )); | |
83 | break 'out; | |
84 | } | |
85 | ||
86 | #[repr(transparent)] | |
87 | struct Location($crate::bindings::kunit_loc); | |
88 | ||
89 | #[repr(transparent)] | |
90 | struct UnaryAssert($crate::bindings::kunit_unary_assert); | |
91 | ||
92 | // SAFETY: There is only a static instance and in that one the pointer field points to | |
93 | // an immutable C string. | |
94 | unsafe impl Sync for Location {} | |
95 | ||
96 | // SAFETY: There is only a static instance and in that one the pointer field points to | |
97 | // an immutable C string. | |
98 | unsafe impl Sync for UnaryAssert {} | |
99 | ||
100 | static LOCATION: Location = Location($crate::bindings::kunit_loc { | |
101 | file: FILE.as_char_ptr(), | |
102 | line: LINE, | |
103 | }); | |
104 | static ASSERTION: UnaryAssert = UnaryAssert($crate::bindings::kunit_unary_assert { | |
105 | assert: $crate::bindings::kunit_assert {}, | |
106 | condition: CONDITION.as_char_ptr(), | |
107 | expected_true: true, | |
108 | }); | |
109 | ||
110 | // SAFETY: | |
111 | // - FFI call. | |
112 | // - The `kunit_test` pointer is valid because we got it from | |
113 | // `kunit_get_current_test()` and it was not null. This means we are in a KUnit | |
114 | // test, and that the pointer can be passed to KUnit functions and assertions. | |
115 | // - The string pointers (`file` and `condition` above) point to null-terminated | |
116 | // strings since they are `CStr`s. | |
117 | // - The function pointer (`format`) points to the proper function. | |
118 | // - The pointers passed will remain valid since they point to `static`s. | |
119 | // - The format string is allowed to be null. | |
120 | // - There are, however, problems with this: first of all, this will end up stopping | |
121 | // the thread, without running destructors. While that is problematic in itself, | |
122 | // it is considered UB to have what is effectively a forced foreign unwind | |
123 | // with `extern "C"` ABI. One could observe the stack that is now gone from | |
124 | // another thread. We should avoid pinning stack variables to prevent library UB, | |
125 | // too. For the moment, given that test failures are reported immediately before the | |
126 | // next test runs, that test failures should be fixed and that KUnit is explicitly | |
127 | // documented as not suitable for production environments, we feel it is reasonable. | |
128 | unsafe { | |
129 | $crate::bindings::__kunit_do_failed_assertion( | |
130 | kunit_test, | |
131 | core::ptr::addr_of!(LOCATION.0), | |
132 | $crate::bindings::kunit_assert_type_KUNIT_ASSERTION, | |
133 | core::ptr::addr_of!(ASSERTION.0.assert), | |
134 | Some($crate::bindings::kunit_unary_assert_format), | |
135 | core::ptr::null(), | |
136 | ); | |
137 | } | |
138 | ||
139 | // SAFETY: FFI call; the `test` pointer is valid because this hidden macro should only | |
140 | // be called by the generated documentation tests which forward the test pointer given | |
141 | // by KUnit. | |
142 | unsafe { | |
143 | $crate::bindings::__kunit_abort(kunit_test); | |
144 | } | |
145 | } | |
146 | }; | |
147 | } | |
148 | ||
149 | /// Asserts that two expressions are equal to each other (using [`PartialEq`]). | |
150 | /// | |
151 | /// Public but hidden since it should only be used from generated tests. | |
152 | /// | |
153 | /// Unlike the one in `core`, this one does not panic; instead, it is mapped to the KUnit | |
154 | /// facilities. See [`assert!`] for more details. | |
155 | #[doc(hidden)] | |
156 | #[macro_export] | |
157 | macro_rules! kunit_assert_eq { | |
158 | ($name:literal, $file:literal, $diff:expr, $left:expr, $right:expr $(,)?) => {{ | |
159 | // For the moment, we just forward to the expression assert because, for binary asserts, | |
160 | // KUnit supports only a few types (e.g. integers). | |
161 | $crate::kunit_assert!($name, $file, $diff, $left == $right); | |
162 | }}; | |
163 | } |