Skip to content

Commit 3137389

Browse files
Merge patch series "rust: kunit: Support KUnit tests with a user-space like syntax"
David Gow <davidgow@google.com> says: This series was originally written by José Expósito, and can be found here: Rust-for-Linux#950 Add support for writing KUnit tests in Rust. While Rust doctests are already converted to KUnit tests and run, they're really better suited for examples, rather than as first-class unit tests. This series implements a series of direct Rust bindings for KUnit tests, as well as a new macro which allows KUnit tests to be written using a close variant of normal Rust unit test syntax. The only change required is replacing '#[cfg(test)]' with '#[kunit_tests(kunit_test_suite_name)]' An example test would look like: #[kunit_tests(rust_kernel_hid_driver)] mod tests { use super::*; use crate::{c_str, driver, hid, prelude::*}; use core::ptr; struct SimpleTestDriver; impl Driver for SimpleTestDriver { type Data = (); } #[test] fn rust_test_hid_driver_adapter() { let mut hid = bindings::hid_driver::default(); let name = c_str!("SimpleTestDriver"); static MODULE: ThisModule = unsafe { ThisModule::from_ptr(ptr::null_mut()) }; let res = unsafe { <hid::Adapter<SimpleTestDriver> as driver::DriverOps>::register(&mut hid, name, &MODULE) }; assert_eq!(res, Err(ENODEV)); // The mock returns -19 } } Changes since the GitHub PR: - Rebased on top of kselftest/kunit - Add const_mut_refs feature This may conflict with https://lore.kernel.org/lkml/20230503090708.2524310-6-nmi@metaspace.dk/ - Add rust/macros/kunit.rs to the KUnit MAINTAINERS entry Link: https://lore.kernel.org/r/20230720-rustbind-v1-0-c80db349e3b5@google.com
2 parents 97ab3e8 + b24f5f7 commit 3137389

File tree

5 files changed

+361
-0
lines changed

5 files changed

+361
-0
lines changed

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11837,6 +11837,7 @@ F: Documentation/dev-tools/kunit/
1183711837
F: include/kunit/
1183811838
F: lib/kunit/
1183911839
F: rust/kernel/kunit.rs
11840+
F: rust/macros/kunit.rs
1184011841
F: scripts/rustdoc_test_*
1184111842
F: tools/testing/kunit/
1184211843

rust/kernel/kunit.rs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ pub fn info(args: fmt::Arguments<'_>) {
4040
}
4141
}
4242

43+
use crate::task::Task;
44+
use core::ops::Deref;
45+
use macros::kunit_tests;
46+
4347
/// Asserts that a boolean expression is `true` at runtime.
4448
///
4549
/// Public but hidden since it should only be used from generated tests.
@@ -161,3 +165,180 @@ macro_rules! kunit_assert_eq {
161165
$crate::kunit_assert!($name, $file, $diff, $left == $right);
162166
}};
163167
}
168+
169+
/// Represents an individual test case.
170+
///
171+
/// The test case should have the signature
172+
/// `unsafe extern "C" fn test_case(test: *mut crate::bindings::kunit)`.
173+
///
174+
/// The `kunit_unsafe_test_suite!` macro expects a NULL-terminated list of test cases. This macro
175+
/// can be invoked without parameters to generate the delimiter.
176+
#[macro_export]
177+
macro_rules! kunit_case {
178+
() => {
179+
$crate::bindings::kunit_case {
180+
run_case: None,
181+
name: core::ptr::null_mut(),
182+
generate_params: None,
183+
status: $crate::bindings::kunit_status_KUNIT_SUCCESS,
184+
log: core::ptr::null_mut(),
185+
}
186+
};
187+
($name:ident, $run_case:ident) => {
188+
$crate::bindings::kunit_case {
189+
run_case: Some($run_case),
190+
name: $crate::c_str!(core::stringify!($name)).as_char_ptr(),
191+
generate_params: None,
192+
status: $crate::bindings::kunit_status_KUNIT_SUCCESS,
193+
log: core::ptr::null_mut(),
194+
}
195+
};
196+
}
197+
198+
/// Registers a KUnit test suite.
199+
///
200+
/// # Safety
201+
///
202+
/// `test_cases` must be a NULL terminated array of test cases.
203+
///
204+
/// # Examples
205+
///
206+
/// ```ignore
207+
/// unsafe extern "C" fn test_fn(_test: *mut crate::bindings::kunit) {
208+
/// let actual = 1 + 1;
209+
/// let expected = 2;
210+
/// assert_eq!(actual, expected);
211+
/// }
212+
///
213+
/// static mut KUNIT_TEST_CASE: crate::bindings::kunit_case = crate::kunit_case!(name, test_fn);
214+
/// static mut KUNIT_NULL_CASE: crate::bindings::kunit_case = crate::kunit_case!();
215+
/// static mut KUNIT_TEST_CASES: &mut[crate::bindings::kunit_case] = unsafe {
216+
/// &mut[KUNIT_TEST_CASE, KUNIT_NULL_CASE]
217+
/// };
218+
/// crate::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
219+
/// ```
220+
#[macro_export]
221+
macro_rules! kunit_unsafe_test_suite {
222+
($name:ident, $test_cases:ident) => {
223+
const _: () = {
224+
static KUNIT_TEST_SUITE_NAME: [i8; 256] = {
225+
let name_u8 = core::stringify!($name).as_bytes();
226+
let mut ret = [0; 256];
227+
228+
let mut i = 0;
229+
while i < name_u8.len() {
230+
ret[i] = name_u8[i] as i8;
231+
i += 1;
232+
}
233+
234+
ret
235+
};
236+
237+
// SAFETY: `test_cases` is valid as it should be static.
238+
static mut KUNIT_TEST_SUITE: core::cell::UnsafeCell<$crate::bindings::kunit_suite> =
239+
core::cell::UnsafeCell::new($crate::bindings::kunit_suite {
240+
name: KUNIT_TEST_SUITE_NAME,
241+
test_cases: unsafe { $test_cases.as_mut_ptr() },
242+
suite_init: None,
243+
suite_exit: None,
244+
init: None,
245+
exit: None,
246+
status_comment: [0; 256usize],
247+
debugfs: core::ptr::null_mut(),
248+
log: core::ptr::null_mut(),
249+
suite_init_err: 0,
250+
});
251+
252+
// SAFETY: `KUNIT_TEST_SUITE` is static.
253+
#[used]
254+
#[link_section = ".kunit_test_suites"]
255+
static mut KUNIT_TEST_SUITE_ENTRY: *const $crate::bindings::kunit_suite =
256+
unsafe { KUNIT_TEST_SUITE.get() };
257+
};
258+
};
259+
}
260+
261+
/// In some cases, you need to call test-only code from outside the test case, for example, to
262+
/// create a function mock. This function can be invoked to know whether we are currently running a
263+
/// KUnit test or not.
264+
///
265+
/// # Examples
266+
///
267+
/// This example shows how a function can be mocked to return a well-known value while testing:
268+
///
269+
/// ```
270+
/// # use kernel::kunit::in_kunit_test;
271+
/// #
272+
/// fn fn_mock_example(n: i32) -> i32 {
273+
/// if in_kunit_test() {
274+
/// 100
275+
/// } else {
276+
/// n + 1
277+
/// }
278+
/// }
279+
///
280+
/// let mock_res = fn_mock_example(5);
281+
/// assert_eq!(mock_res, 100);
282+
/// ```
283+
///
284+
/// Sometimes, you don't control the code that needs to be mocked. This example shows how the
285+
/// `bindings` module can be mocked:
286+
///
287+
/// ```
288+
/// // Import our mock naming it as the real module.
289+
/// #[cfg(CONFIG_KUNIT)]
290+
/// use bindings_mock_example as bindings;
291+
///
292+
/// // This module mocks `bindings`.
293+
/// mod bindings_mock_example {
294+
/// use kernel::kunit::in_kunit_test;
295+
/// use kernel::bindings::u64_;
296+
///
297+
/// // Make the other binding functions available.
298+
/// pub(crate) use kernel::bindings::*;
299+
///
300+
/// // Mock `ktime_get_boot_fast_ns` to return a well-known value when running a KUnit test.
301+
/// pub(crate) unsafe fn ktime_get_boot_fast_ns() -> u64_ {
302+
/// if in_kunit_test() {
303+
/// 1234
304+
/// } else {
305+
/// unsafe { kernel::bindings::ktime_get_boot_fast_ns() }
306+
/// }
307+
/// }
308+
/// }
309+
///
310+
/// // This is the function we want to test. Since `bindings` has been mocked, we can use its
311+
/// // functions seamlessly.
312+
/// fn get_boot_ns() -> u64 {
313+
/// unsafe { bindings::ktime_get_boot_fast_ns() }
314+
/// }
315+
///
316+
/// let time = get_boot_ns();
317+
/// assert_eq!(time, 1234);
318+
/// ```
319+
pub fn in_kunit_test() -> bool {
320+
if cfg!(CONFIG_KUNIT) {
321+
// SAFETY: By the type invariant, we know that `*Task::current().deref().0` is valid.
322+
let test = unsafe { (*Task::current().deref().0.get()).kunit_test };
323+
!test.is_null()
324+
} else {
325+
false
326+
}
327+
}
328+
329+
#[kunit_tests(rust_kernel_kunit)]
330+
mod tests {
331+
use super::*;
332+
333+
#[test]
334+
fn rust_test_kunit_kunit_tests() {
335+
let running = true;
336+
assert_eq!(running, true);
337+
}
338+
339+
#[test]
340+
fn rust_test_kunit_in_kunit_test() {
341+
let in_kunit = in_kunit_test();
342+
assert_eq!(in_kunit, true);
343+
}
344+
}

rust/kernel/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#![feature(new_uninit)]
1818
#![feature(receiver_trait)]
1919
#![feature(unsize)]
20+
#![feature(const_mut_refs)]
2021

2122
// Ensure conditional compilation based on the kernel configuration works;
2223
// otherwise we may silently break things like initcall handling.

rust/macros/kunit.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Procedural macro to run KUnit tests using a user-space like syntax.
4+
//!
5+
//! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
6+
7+
use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
8+
use std::fmt::Write;
9+
10+
pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
11+
if attr.to_string().is_empty() {
12+
panic!("Missing test name in #[kunit_tests(test_name)] macro")
13+
}
14+
15+
let mut tokens: Vec<_> = ts.into_iter().collect();
16+
17+
// Scan for the "mod" keyword.
18+
tokens
19+
.iter()
20+
.find_map(|token| match token {
21+
TokenTree::Ident(ident) => match ident.to_string().as_str() {
22+
"mod" => Some(true),
23+
_ => None,
24+
},
25+
_ => None,
26+
})
27+
.expect("#[kunit_tests(test_name)] attribute should only be applied to modules");
28+
29+
// Retrieve the main body. The main body should be the last token tree.
30+
let body = match tokens.pop() {
31+
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group,
32+
_ => panic!("cannot locate main body of module"),
33+
};
34+
35+
// Get the functions set as tests. Search for `[test]` -> `fn`.
36+
let mut body_it = body.stream().into_iter();
37+
let mut tests = Vec::new();
38+
while let Some(token) = body_it.next() {
39+
match token {
40+
TokenTree::Group(ident) if ident.to_string() == "[test]" => match body_it.next() {
41+
Some(TokenTree::Ident(ident)) if ident.to_string() == "fn" => {
42+
let test_name = match body_it.next() {
43+
Some(TokenTree::Ident(ident)) => ident.to_string(),
44+
_ => continue,
45+
};
46+
tests.push(test_name);
47+
}
48+
_ => continue,
49+
},
50+
_ => (),
51+
}
52+
}
53+
54+
// Add `#[cfg(CONFIG_KUNIT)]` before the module declaration.
55+
let config_kunit = "#[cfg(CONFIG_KUNIT)]".to_owned().parse().unwrap();
56+
tokens.insert(
57+
0,
58+
TokenTree::Group(Group::new(Delimiter::None, config_kunit)),
59+
);
60+
61+
// Generate the test KUnit test suite and a test case for each `#[test]`.
62+
// The code generated for the following test module:
63+
//
64+
// ```
65+
// #[kunit_tests(kunit_test_suit_name)]
66+
// mod tests {
67+
// #[test]
68+
// fn foo() {
69+
// assert_eq!(1, 1);
70+
// }
71+
//
72+
// #[test]
73+
// fn bar() {
74+
// assert_eq!(2, 2);
75+
// }
76+
// ```
77+
//
78+
// Looks like:
79+
//
80+
// ```
81+
// unsafe extern "C" fn kunit_rust_wrapper_foo(_test: *mut kernel::bindings::kunit) {
82+
// foo();
83+
// }
84+
// static mut KUNIT_CASE_FOO: kernel::bindings::kunit_case =
85+
// kernel::kunit_case!(foo, kunit_rust_wrapper_foo);
86+
//
87+
// unsafe extern "C" fn kunit_rust_wrapper_bar(_test: * mut kernel::bindings::kunit) {
88+
// bar();
89+
// }
90+
// static mut KUNIT_CASE_BAR: kernel::bindings::kunit_case =
91+
// kernel::kunit_case!(bar, kunit_rust_wrapper_bar);
92+
//
93+
// static mut KUNIT_CASE_NULL: kernel::bindings::kunit_case = kernel::kunit_case!();
94+
//
95+
// static mut TEST_CASES : &mut[kernel::bindings::kunit_case] = unsafe {
96+
// &mut [KUNIT_CASE_FOO, KUNIT_CASE_BAR, KUNIT_CASE_NULL]
97+
// };
98+
//
99+
// kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
100+
// ```
101+
let mut kunit_macros = "".to_owned();
102+
let mut test_cases = "".to_owned();
103+
for test in tests {
104+
let kunit_wrapper_fn_name = format!("kunit_rust_wrapper_{}", test);
105+
let kunit_case_name = format!("KUNIT_CASE_{}", test.to_uppercase());
106+
let kunit_wrapper = format!(
107+
"unsafe extern \"C\" fn {}(_test: *mut kernel::bindings::kunit) {{ {}(); }}",
108+
kunit_wrapper_fn_name, test
109+
);
110+
let kunit_case = format!(
111+
"static mut {}: kernel::bindings::kunit_case = kernel::kunit_case!({}, {});",
112+
kunit_case_name, test, kunit_wrapper_fn_name
113+
);
114+
writeln!(kunit_macros, "{kunit_wrapper}").unwrap();
115+
writeln!(kunit_macros, "{kunit_case}").unwrap();
116+
writeln!(test_cases, "{kunit_case_name},").unwrap();
117+
}
118+
119+
writeln!(
120+
kunit_macros,
121+
"static mut KUNIT_CASE_NULL: kernel::bindings::kunit_case = kernel::kunit_case!();"
122+
)
123+
.unwrap();
124+
125+
writeln!(
126+
kunit_macros,
127+
"static mut TEST_CASES : &mut[kernel::bindings::kunit_case] = unsafe {{ &mut[{test_cases} KUNIT_CASE_NULL] }};"
128+
)
129+
.unwrap();
130+
131+
writeln!(
132+
kunit_macros,
133+
"kernel::kunit_unsafe_test_suite!({attr}, TEST_CASES);"
134+
)
135+
.unwrap();
136+
137+
let new_body: TokenStream = vec![body.stream(), kunit_macros.parse().unwrap()]
138+
.into_iter()
139+
.collect();
140+
141+
// Remove the `#[test]` macros.
142+
let new_body = new_body.to_string().replace("#[test]", "");
143+
tokens.push(TokenTree::Group(Group::new(
144+
Delimiter::Brace,
145+
new_body.parse().unwrap(),
146+
)));
147+
148+
tokens.into_iter().collect()
149+
}

rust/macros/lib.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
mod quote;
77
mod concat_idents;
88
mod helpers;
9+
mod kunit;
910
mod module;
1011
mod paste;
1112
mod pin_data;
@@ -405,3 +406,31 @@ pub fn paste(input: TokenStream) -> TokenStream {
405406
pub fn derive_zeroable(input: TokenStream) -> TokenStream {
406407
zeroable::derive(input)
407408
}
409+
410+
/// Registers a KUnit test suite and its test cases using a user-space like syntax.
411+
///
412+
/// This macro should be used on modules. If `CONFIG_KUNIT` (in `.config`) is `n`, the target module
413+
/// is ignored.
414+
///
415+
/// # Examples
416+
///
417+
/// ```ignore
418+
/// # use macros::kunit_tests;
419+
///
420+
/// #[kunit_tests(kunit_test_suit_name)]
421+
/// mod tests {
422+
/// #[test]
423+
/// fn foo() {
424+
/// assert_eq!(1, 1);
425+
/// }
426+
///
427+
/// #[test]
428+
/// fn bar() {
429+
/// assert_eq!(2, 2);
430+
/// }
431+
/// }
432+
/// ```
433+
#[proc_macro_attribute]
434+
pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
435+
kunit::kunit_tests(attr, ts)
436+
}

0 commit comments

Comments
 (0)