* [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax
@ 2025-03-07 9:00 David Gow
2025-03-07 9:00 ` [PATCH v8 1/3] rust: kunit: add KUnit case and suite macros David Gow
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: David Gow @ 2025-03-07 9:00 UTC (permalink / raw)
To: Miguel Ojeda, José Expósito, Rae Moar, Boqun Feng,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, Brendan Higgins, Tamir Duberstein
Cc: David Gow, kunit-dev, linux-kselftest, rust-for-linux,
linux-kernel
Hi all,
This is v8 of the Rust/KUnit integration patch. I think all of the
suggestions have at least been responded to (even if there are a few I'm
leaving as either future projects or matters of taste). Hopefully this
is good-to-go for 6.15, so we can start using it concurrently with
making any additional improvements we may wish.
This series was originally written by José Expósito, and has been
modified and updated by Matt Gilbride, Miguel Ojeda, and myself. The
original version can be found here:
https://github.com/Rust-for-Linux/linux/pull/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
}
}
Please give this a go, and make sure I haven't broken it! There's almost
certainly a lot of improvements which can be made -- and there's a fair
case to be made for replacing some of this with generated C code which
can use the C macros -- but this is hopefully an adequate implementation
for now, and the interface can (with luck) remain the same even if the
implementation changes.
A few small notable missing features:
- Attributes (like the speed of a test) are hardcoded to the default
value.
- Similarly, the module name attribute is hardcoded to NULL. In C, we
use the KBUILD_MODNAME macro, but I couldn't find a way to use this
from Rust which wasn't more ugly than just disabling it.
- Assertions are not automatically rewritten to use KUnit assertions.
---
Changes since v7:
https://lore.kernel.org/rust-for-linux/20250214074051.1619256-1-davidgow@google.com/
- Reworked the SAFETY comment for addr_of_mut! use with statics in
kunit_unsafe_test_suite!() (again)
- Removed the second mocking example, which was causing confusion.
The first example of in_kunit_test() should be clear enough.
Changes since v6:
https://lore.kernel.org/rust-for-linux/20250214074051.1619256-1-davidgow@google.com/
- Fixed an [allow(unused_unsafe)] which ended up in patch 2 instead of
patch 1. (Thanks, Tamir!)
- Doc comments now have several useful links. (Thanks, Tamir!)
- Fix a potential compile error under macos. (Thanks, Tamir!)
- Several small tidy-ups to limit unsafe usage. (Thanks, Tamir!)
Changes since v5:
https://lore.kernel.org/all/20241213081035.2069066-1-davidgow@google.com/
- Rebased against 6.14-rc1
- Fixed a bunch of warnings / clippy lints introduced in Rust 1.83 and
1.84.
- No longer needs static_mut_refs / const_mut_refs, and is much cleaned
up as a result. (Thanks, Miguel)
- Major documentation and example fixes. (Thanks, Miguel)
Changes since v4:
https://lore.kernel.org/linux-kselftest/20241101064505.3820737-1-davidgow@google.com/
- Rebased against 6.13-rc1
- Allowed an unused_unsafe warning after the behaviour of addr_of_mut!()
changed in Rust 1.82. (Thanks Boqun, Miguel)
- "Expect" that the sample assert_eq!(1+1, 2) produces a clippy warning
due to a redundant assertion. (Thanks Boqun, Miguel)
- Fix some missing safety comments, and remove some unneeded 'unsafe'
blocks. (Thanks Boqun)
- Fix a couple of minor rustfmt issues which were triggering checkpatch
warnings.
Changes since v3:
https://lore.kernel.org/linux-kselftest/20241030045719.3085147-2-davidgow@google.com/T/
- The kunit_unsafe_test_suite!() macro now panic!s if the suite name is
too long, triggering a compile error. (Thanks, Alice!)
- The #[kunit_tests()] macro now preserves span information, so
errors can be better reported. (Thanks, Boqun!)
- The example tests have been updated to no longer use assert_eq!() with
a constant bool argument (which triggered a clippy warning now we
have the span info).
Changes since v2:
https://lore.kernel.org/linux-kselftest/20241029092422.2884505-1-davidgow@google.com/T/
- Include missing rust/macros/kunit.rs file from v2. (Thanks Boqun!)
- The kunit_unsafe_test_suite!() macro will truncate the name of the
suite if it is too long. (Thanks Alice!)
- The proc macro now emits an error if the suite name is too long.
- We no longer needlessly use UnsafeCell<> in
kunit_unsafe_test_suite!(). (Thanks Alice!)
Changes since v1:
https://lore.kernel.org/lkml/20230720-rustbind-v1-0-c80db349e3b5@google.com/T/
- Rebase on top of the latest rust-next (commit 718c4069896c)
- Make kunit_case a const fn, rather than a macro (Thanks Boqun)
- As a result, the null terminator is now created with
kernel::kunit::kunit_case_null()
- Use the C kunit_get_current_test() function to implement
in_kunit_test(), rather than re-implementing it (less efficiently)
ourselves.
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
---
José Expósito (3):
rust: kunit: add KUnit case and suite macros
rust: macros: add macro to easily run KUnit tests
rust: kunit: allow to know if we are in a test
MAINTAINERS | 1 +
rust/kernel/kunit.rs | 171 +++++++++++++++++++++++++++++++++++++++++++
rust/macros/kunit.rs | 161 ++++++++++++++++++++++++++++++++++++++++
rust/macros/lib.rs | 29 ++++++++
4 files changed, 362 insertions(+)
create mode 100644 rust/macros/kunit.rs
--
2.49.0.rc0.332.g42c0ae87b1-goog
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v8 1/3] rust: kunit: add KUnit case and suite macros
2025-03-07 9:00 [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
@ 2025-03-07 9:00 ` David Gow
2025-03-07 9:00 ` [PATCH v8 2/3] rust: macros: add macro to easily run KUnit tests David Gow
` (3 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: David Gow @ 2025-03-07 9:00 UTC (permalink / raw)
To: Miguel Ojeda, José Expósito, Rae Moar, Boqun Feng,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, Brendan Higgins, Tamir Duberstein
Cc: kunit-dev, linux-kselftest, rust-for-linux, linux-kernel,
David Gow
From: José Expósito <jose.exposito89@gmail.com>
Add a couple of Rust const functions and macros to allow to develop
KUnit tests without relying on generated C code:
- The `kunit_unsafe_test_suite!` Rust macro is similar to the
`kunit_test_suite` C macro. It requires a NULL-terminated array of
test cases (see below).
- The `kunit_case` Rust function is similar to the `KUNIT_CASE` C macro.
It generates as case from the name and function.
- The `kunit_case_null` Rust function generates a NULL test case, which
is to be used as delimiter in `kunit_test_suite!`.
While these functions and macros can be used on their own, a future
patch will introduce another macro to create KUnit tests using a
user-space like syntax.
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Co-developed-by: Matt Gilbride <mattgilbride@google.com>
Signed-off-by: Matt Gilbride <mattgilbride@google.com>
Co-developed-by: Miguel Ojeda <ojeda@kernel.org>
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Co-developed-by: David Gow <davidgow@google.com>
Signed-off-by: David Gow <davidgow@google.com>
---
Changes since v7:
https://lore.kernel.org/rust-for-linux/20250214074051.1619256-2-davidgow@google.com/
- Reworked the SAFETY comment for addr_of_mut! use with statics in
kunit_unsafe_test_suite!() (again).
- Equally, updated the macro documentation to make the safety
precondition (that $test_cases should be static) clearer.
Changes since v6:
https://lore.kernel.org/rust-for-linux/20250214074051.1619256-2-davidgow@google.com/
- Fixed an [allow(unused_unsafe)] which ended up in patch 2 instead of
patch 1. (Thanks, Tamir!)
- Doc comments now have several useful links. (Thanks, Tamir!)
- Fix a potential compile error under macos. (Thanks, Tamir!)
- Several small tidy-ups to limit unsafe usage. (Thanks, Tamir!)
Changes since v5:
https://lore.kernel.org/all/20241213081035.2069066-2-davidgow@google.com/
- Rebased against 6.14-rc1
- Several documentation touch-ups, including noting that the example
test function need not be unsafe. (Thanks, Miguel)
- Remove the need for static_mut_refs, by using core::addr_of_mut!()
combined with a cast. (Thanks, Miguel)
- While this should also avoid the need for const_mut_refs, it seems
to have been enabled for other users anyway.
- Use ::kernel::ffi::c_char for C strings, rather than i8 directly.
(Thanks, Miguel)
Changes since v4:
https://lore.kernel.org/linux-kselftest/20241101064505.3820737-2-davidgow@google.com/
- Rebased against 6.13-rc1
- Allowed an unused_unsafe warning after the behaviour of addr_of_mut!()
changed in Rust 1.82. (Thanks Boqun, Miguel)
- Fix a couple of minor rustfmt issues which were triggering checkpatch
warnings.
Changes since v3:
https://lore.kernel.org/linux-kselftest/20241030045719.3085147-4-davidgow@google.com/
- The kunit_unsafe_test_suite!() macro now panic!s if the suite name is
too long, triggering a compile error. (Thanks, Alice!)
Changes since v2:
https://lore.kernel.org/linux-kselftest/20241029092422.2884505-2-davidgow@google.com/
- The kunit_unsafe_test_suite!() macro will truncate the name of the
suite if it is too long. (Thanks Alice!)
- We no longer needlessly use UnsafeCell<> in
kunit_unsafe_test_suite!(). (Thanks Alice!)
Changes since v1:
https://lore.kernel.org/lkml/20230720-rustbind-v1-1-c80db349e3b5@google.com/
- Rebase on top of rust-next
- As a result, KUnit attributes are new set. These are hardcoded to the
defaults of "normal" speed and no module name.
- Split the kunit_case!() macro into two const functions, kunit_case()
and kunit_case_null() (for the NULL terminator).
---
Changes since v6:
https://lore.kernel.org/rust-for-linux/20250214074051.1619256-2-davidgow@google.com/
- Fixed an [allow(unused_unsafe)] which ended up in patch 2 instead of
patch 1. (Thanks, Tamir!)
- Doc comments now have several useful links. (Thanks, Tamir!)
- Fix a potential compile error under macos. (Thanks, Tamir!)
- Several small tidy-ups to limit unsafe usage. (Thanks, Tamir!)
Changes since v5:
https://lore.kernel.org/all/20241213081035.2069066-2-davidgow@google.com/
- Rebased against 6.14-rc1
- Several documentation touch-ups, including noting that the example
test function need not be unsafe. (Thanks, Miguel)
- Remove the need for static_mut_refs, by using core::addr_of_mut!()
combined with a cast. (Thanks, Miguel)
- While this should also avoid the need for const_mut_refs, it seems
to have been enabled for other users anyway.
- Use ::kernel::ffi::c_char for C strings, rather than i8 directly.
(Thanks, Miguel)
Changes since v4:
https://lore.kernel.org/linux-kselftest/20241101064505.3820737-2-davidgow@google.com/
- Rebased against 6.13-rc1
- Allowed an unused_unsafe warning after the behaviour of addr_of_mut!()
changed in Rust 1.82. (Thanks Boqun, Miguel)
- Fix a couple of minor rustfmt issues which were triggering checkpatch
warnings.
Changes since v3:
https://lore.kernel.org/linux-kselftest/20241030045719.3085147-4-davidgow@google.com/
- The kunit_unsafe_test_suite!() macro now panic!s if the suite name is
too long, triggering a compile error. (Thanks, Alice!)
Changes since v2:
https://lore.kernel.org/linux-kselftest/20241029092422.2884505-2-davidgow@google.com/
- The kunit_unsafe_test_suite!() macro will truncate the name of the
suite if it is too long. (Thanks Alice!)
- We no longer needlessly use UnsafeCell<> in
kunit_unsafe_test_suite!(). (Thanks Alice!)
Changes since v1:
https://lore.kernel.org/lkml/20230720-rustbind-v1-1-c80db349e3b5@google.com/
- Rebase on top of rust-next
- As a result, KUnit attributes are new set. These are hardcoded to the
defaults of "normal" speed and no module name.
- Split the kunit_case!() macro into two const functions, kunit_case()
and kunit_case_null() (for the NULL terminator).
---
rust/kernel/kunit.rs | 124 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 124 insertions(+)
diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
index 824da0e9738a..1b9b52892562 100644
--- a/rust/kernel/kunit.rs
+++ b/rust/kernel/kunit.rs
@@ -161,3 +161,127 @@ macro_rules! kunit_assert_eq {
$crate::kunit_assert!($name, $file, $diff, $left == $right);
}};
}
+
+/// Represents an individual test case.
+///
+/// The [`kunit_unsafe_test_suite!`] macro expects a NULL-terminated list of valid test cases.
+/// Use [`kunit_case_null`] to generate such a delimiter.
+#[doc(hidden)]
+pub const fn kunit_case(
+ name: &'static kernel::str::CStr,
+ run_case: unsafe extern "C" fn(*mut kernel::bindings::kunit),
+) -> kernel::bindings::kunit_case {
+ kernel::bindings::kunit_case {
+ run_case: Some(run_case),
+ name: name.as_char_ptr(),
+ attr: kernel::bindings::kunit_attributes {
+ speed: kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
+ },
+ generate_params: None,
+ status: kernel::bindings::kunit_status_KUNIT_SUCCESS,
+ module_name: core::ptr::null_mut(),
+ log: core::ptr::null_mut(),
+ }
+}
+
+/// Represents the NULL test case delimiter.
+///
+/// The [`kunit_unsafe_test_suite!`] macro expects a NULL-terminated list of test cases. This
+/// function returns such a delimiter.
+#[doc(hidden)]
+pub const fn kunit_case_null() -> kernel::bindings::kunit_case {
+ kernel::bindings::kunit_case {
+ run_case: None,
+ name: core::ptr::null_mut(),
+ generate_params: None,
+ attr: kernel::bindings::kunit_attributes {
+ speed: kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
+ },
+ status: kernel::bindings::kunit_status_KUNIT_SUCCESS,
+ module_name: core::ptr::null_mut(),
+ log: core::ptr::null_mut(),
+ }
+}
+
+/// Registers a KUnit test suite.
+///
+/// # Safety
+///
+/// `test_cases` must be a NULL terminated array of valid test cases,
+/// whose lifetime is at least that of the test suite (i.e., static).
+///
+/// # Examples
+///
+/// ```ignore
+/// extern "C" fn test_fn(_test: *mut kernel::bindings::kunit) {
+/// let actual = 1 + 1;
+/// let expected = 2;
+/// assert_eq!(actual, expected);
+/// }
+///
+/// static mut KUNIT_TEST_CASES: [kernel::bindings::kunit_case; 2] = [
+/// kernel::kunit::kunit_case(kernel::c_str!("name"), test_fn),
+/// kernel::kunit::kunit_case_null(),
+/// ];
+/// kernel::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
+/// ```
+#[doc(hidden)]
+#[macro_export]
+macro_rules! kunit_unsafe_test_suite {
+ ($name:ident, $test_cases:ident) => {
+ const _: () = {
+ const KUNIT_TEST_SUITE_NAME: [::kernel::ffi::c_char; 256] = {
+ let name_u8 = ::core::stringify!($name).as_bytes();
+ let mut ret = [0; 256];
+
+ if name_u8.len() > 255 {
+ panic!(concat!(
+ "The test suite name `",
+ ::core::stringify!($name),
+ "` exceeds the maximum length of 255 bytes."
+ ));
+ }
+
+ let mut i = 0;
+ while i < name_u8.len() {
+ ret[i] = name_u8[i] as ::kernel::ffi::c_char;
+ i += 1;
+ }
+
+ ret
+ };
+
+ static mut KUNIT_TEST_SUITE: ::kernel::bindings::kunit_suite =
+ ::kernel::bindings::kunit_suite {
+ name: KUNIT_TEST_SUITE_NAME,
+ #[allow(unused_unsafe)]
+ // SAFETY: $test_cases is passed in by the user, and
+ // (as documented) must be valid for the lifetime of
+ // the suite (i.e., static).
+ test_cases: unsafe {
+ ::core::ptr::addr_of_mut!($test_cases)
+ .cast::<::kernel::bindings::kunit_case>()
+ },
+ suite_init: None,
+ suite_exit: None,
+ init: None,
+ exit: None,
+ attr: ::kernel::bindings::kunit_attributes {
+ speed: ::kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
+ },
+ status_comment: [0; 256usize],
+ debugfs: ::core::ptr::null_mut(),
+ log: ::core::ptr::null_mut(),
+ suite_init_err: 0,
+ is_init: false,
+ };
+
+ #[used]
+ #[allow(unused_unsafe)]
+ #[cfg_attr(not(target_os = "macos"), link_section = ".kunit_test_suites")]
+ static mut KUNIT_TEST_SUITE_ENTRY: *const ::kernel::bindings::kunit_suite =
+ // SAFETY: `KUNIT_TEST_SUITE` is static.
+ unsafe { ::core::ptr::addr_of_mut!(KUNIT_TEST_SUITE) };
+ };
+ };
+}
--
2.49.0.rc0.332.g42c0ae87b1-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v8 2/3] rust: macros: add macro to easily run KUnit tests
2025-03-07 9:00 [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
2025-03-07 9:00 ` [PATCH v8 1/3] rust: kunit: add KUnit case and suite macros David Gow
@ 2025-03-07 9:00 ` David Gow
2025-03-07 9:00 ` [PATCH v8 3/3] rust: kunit: allow to know if we are in a test David Gow
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: David Gow @ 2025-03-07 9:00 UTC (permalink / raw)
To: Miguel Ojeda, José Expósito, Rae Moar, Boqun Feng,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, Brendan Higgins, Tamir Duberstein
Cc: kunit-dev, linux-kselftest, rust-for-linux, linux-kernel,
David Gow
From: José Expósito <jose.exposito89@gmail.com>
Add a new procedural macro (`#[kunit_tests(kunit_test_suit_name)]`) to
run KUnit tests using a user-space like syntax.
The macro, that should be used on modules, transforms every `#[test]`
in a `kunit_case!` and adds a `kunit_unsafe_test_suite!` registering
all of them.
The only difference with user-space tests is that instead of using
`#[cfg(test)]`, `#[kunit_tests(kunit_test_suit_name)]` is used.
Note that `#[cfg(CONFIG_KUNIT)]` is added so the test module is not
compiled when `CONFIG_KUNIT` is set to `n`.
Reviewed-by: David Gow <davidgow@google.com>
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Co-developed-by: Boqun Feng <boqun.feng@gmail.com>
Signed-off-by: Boqun Feng <boqun.feng@gmail.com>
Co-developed-by: Miguel Ojeda <ojeda@kernel.org>
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Reviewed-by: Tamir Duberstein <tamird@gmail.com>
Signed-off-by: David Gow <davidgow@google.com>
---
No changes since v7:
https://lore.kernel.org/rust-for-linux/20250214074051.1619256-3-davidgow@google.com/
Changes since v6:
https://lore.kernel.org/rust-for-linux/20250214074051.1619256-3-davidgow@google.com/
- Fixed an [allow(unused_unsafe)] which ended up in patch 2 instead of
patch 1. (Thanks, Tamir!)
- Add Tamir's Reviewed-by tag.
Changes since v5:
https://lore.kernel.org/all/20241213081035.2069066-3-davidgow@google.com/
- Fix some rustfmt-related formatting shenanigans. (Thanks, Miguel)
- Fix some documentation comment formatting as well. (Thanks, Miguel)
- Tidy up the generated code to avoid unneeded &mut[] everywhere.
(Thanks, Miguel)
- Fix a new clippy warning for using .as_bytes().len() instead of .len()
directly.
Changes since v4:
https://lore.kernel.org/linux-kselftest/20241101064505.3820737-3-davidgow@google.com/
- Rebased against 6.13-rc1
- "Expect" that the sample assert_eq!(1+1, 2) produces a clippy warning
due to a redundant assertion. (Thanks Boqun, Miguel)
Changes since v3:
https://lore.kernel.org/linux-kselftest/20241030045719.3085147-6-davidgow@google.com/
- The #[kunit_tests()] macro now preserves span information, so
errors can be better reported. (Thanks, Boqun!)
- The example test has been replaced to no longer use assert_eq!() with
a constant bool argument (which triggered a clippy warning now we
have the span info). It now checks 1 + 1 == 2, to match the C example.
- (The in_kunit_test() test in the next patch uses assert!() to check
a bool, so having something different here seemed to make a better
example.)
Changes since v2:
https://lore.kernel.org/linux-kselftest/20241029092422.2884505-3-davidgow@google.com/
- Include missing rust/macros/kunit.rs file from v2. (Thanks Boqun!)
- The proc macro now emits an error if the suite name is too long.
Changes since v1:
https://lore.kernel.org/lkml/20230720-rustbind-v1-2-c80db349e3b5@google.com/
- Rebased on top of rust-next
- Make use of the new const functions, rather than the kunit_case!()
macro.
---
MAINTAINERS | 1 +
rust/kernel/kunit.rs | 11 +++
rust/macros/kunit.rs | 161 +++++++++++++++++++++++++++++++++++++++++++
rust/macros/lib.rs | 29 ++++++++
4 files changed, 202 insertions(+)
create mode 100644 rust/macros/kunit.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index c8d9e8187eb0..4e7a6d2f2c49 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12677,6 +12677,7 @@ F: Documentation/dev-tools/kunit/
F: include/kunit/
F: lib/kunit/
F: rust/kernel/kunit.rs
+F: rust/macros/kunit.rs
F: scripts/rustdoc_test_*
F: tools/testing/kunit/
diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
index 1b9b52892562..97e99b52e4a9 100644
--- a/rust/kernel/kunit.rs
+++ b/rust/kernel/kunit.rs
@@ -40,6 +40,8 @@ pub fn info(args: fmt::Arguments<'_>) {
}
}
+use macros::kunit_tests;
+
/// Asserts that a boolean expression is `true` at runtime.
///
/// Public but hidden since it should only be used from generated tests.
@@ -285,3 +287,12 @@ macro_rules! kunit_unsafe_test_suite {
};
};
}
+
+#[kunit_tests(rust_kernel_kunit)]
+mod tests {
+ #[test]
+ fn rust_test_kunit_example_test() {
+ #![expect(clippy::eq_op)]
+ assert_eq!(1 + 1, 2);
+ }
+}
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
new file mode 100644
index 000000000000..4f553ecf40c0
--- /dev/null
+++ b/rust/macros/kunit.rs
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Procedural macro to run KUnit tests using a user-space like syntax.
+//!
+//! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
+
+use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
+use std::fmt::Write;
+
+pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
+ let attr = attr.to_string();
+
+ if attr.is_empty() {
+ panic!("Missing test name in `#[kunit_tests(test_name)]` macro")
+ }
+
+ if attr.len() > 255 {
+ panic!(
+ "The test suite name `{}` exceeds the maximum length of 255 bytes",
+ attr
+ )
+ }
+
+ let mut tokens: Vec<_> = ts.into_iter().collect();
+
+ // Scan for the `mod` keyword.
+ tokens
+ .iter()
+ .find_map(|token| match token {
+ TokenTree::Ident(ident) => match ident.to_string().as_str() {
+ "mod" => Some(true),
+ _ => None,
+ },
+ _ => None,
+ })
+ .expect("`#[kunit_tests(test_name)]` attribute should only be applied to modules");
+
+ // Retrieve the main body. The main body should be the last token tree.
+ let body = match tokens.pop() {
+ Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group,
+ _ => panic!("Cannot locate main body of module"),
+ };
+
+ // Get the functions set as tests. Search for `[test]` -> `fn`.
+ let mut body_it = body.stream().into_iter();
+ let mut tests = Vec::new();
+ while let Some(token) = body_it.next() {
+ match token {
+ TokenTree::Group(ident) if ident.to_string() == "[test]" => match body_it.next() {
+ Some(TokenTree::Ident(ident)) if ident.to_string() == "fn" => {
+ let test_name = match body_it.next() {
+ Some(TokenTree::Ident(ident)) => ident.to_string(),
+ _ => continue,
+ };
+ tests.push(test_name);
+ }
+ _ => continue,
+ },
+ _ => (),
+ }
+ }
+
+ // Add `#[cfg(CONFIG_KUNIT)]` before the module declaration.
+ let config_kunit = "#[cfg(CONFIG_KUNIT)]".to_owned().parse().unwrap();
+ tokens.insert(
+ 0,
+ TokenTree::Group(Group::new(Delimiter::None, config_kunit)),
+ );
+
+ // Generate the test KUnit test suite and a test case for each `#[test]`.
+ // The code generated for the following test module:
+ //
+ // ```
+ // #[kunit_tests(kunit_test_suit_name)]
+ // mod tests {
+ // #[test]
+ // fn foo() {
+ // assert_eq!(1, 1);
+ // }
+ //
+ // #[test]
+ // fn bar() {
+ // assert_eq!(2, 2);
+ // }
+ // }
+ // ```
+ //
+ // Looks like:
+ //
+ // ```
+ // unsafe extern "C" fn kunit_rust_wrapper_foo(_test: *mut kernel::bindings::kunit) { foo(); }
+ // unsafe extern "C" fn kunit_rust_wrapper_bar(_test: *mut kernel::bindings::kunit) { bar(); }
+ //
+ // static mut TEST_CASES: [kernel::bindings::kunit_case; 3] = [
+ // kernel::kunit::kunit_case(kernel::c_str!("foo"), kunit_rust_wrapper_foo),
+ // kernel::kunit::kunit_case(kernel::c_str!("bar"), kunit_rust_wrapper_bar),
+ // kernel::kunit::kunit_case_null(),
+ // ];
+ //
+ // kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
+ // ```
+ let mut kunit_macros = "".to_owned();
+ let mut test_cases = "".to_owned();
+ for test in &tests {
+ let kunit_wrapper_fn_name = format!("kunit_rust_wrapper_{}", test);
+ let kunit_wrapper = format!(
+ "unsafe extern \"C\" fn {}(_test: *mut kernel::bindings::kunit) {{ {}(); }}",
+ kunit_wrapper_fn_name, test
+ );
+ writeln!(kunit_macros, "{kunit_wrapper}").unwrap();
+ writeln!(
+ test_cases,
+ " kernel::kunit::kunit_case(kernel::c_str!(\"{}\"), {}),",
+ test, kunit_wrapper_fn_name
+ )
+ .unwrap();
+ }
+
+ writeln!(kunit_macros).unwrap();
+ writeln!(
+ kunit_macros,
+ "static mut TEST_CASES: [kernel::bindings::kunit_case; {}] = [\n{test_cases} kernel::kunit::kunit_case_null(),\n];",
+ tests.len() + 1
+ )
+ .unwrap();
+
+ writeln!(
+ kunit_macros,
+ "kernel::kunit_unsafe_test_suite!({attr}, TEST_CASES);"
+ )
+ .unwrap();
+
+ // Remove the `#[test]` macros.
+ // We do this at a token level, in order to preserve span information.
+ let mut new_body = vec![];
+ let mut body_it = body.stream().into_iter();
+
+ while let Some(token) = body_it.next() {
+ match token {
+ TokenTree::Punct(ref c) if c.as_char() == '#' => match body_it.next() {
+ Some(TokenTree::Group(group)) if group.to_string() == "[test]" => (),
+ Some(next) => {
+ new_body.extend([token, next]);
+ }
+ _ => {
+ new_body.push(token);
+ }
+ },
+ _ => {
+ new_body.push(token);
+ }
+ }
+ }
+
+ let mut new_body = TokenStream::from_iter(new_body);
+ new_body.extend::<TokenStream>(kunit_macros.parse().unwrap());
+
+ tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body)));
+
+ tokens.into_iter().collect()
+}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index d61bc6a56425..50b58259c577 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -10,6 +10,7 @@
mod quote;
mod concat_idents;
mod helpers;
+mod kunit;
mod module;
mod paste;
mod pin_data;
@@ -492,3 +493,31 @@ pub fn paste(input: TokenStream) -> TokenStream {
pub fn derive_zeroable(input: TokenStream) -> TokenStream {
zeroable::derive(input)
}
+
+/// Registers a KUnit test suite and its test cases using a user-space like syntax.
+///
+/// This macro should be used on modules. If `CONFIG_KUNIT` (in `.config`) is `n`, the target module
+/// is ignored.
+///
+/// # Examples
+///
+/// ```ignore
+/// # use macros::kunit_tests;
+///
+/// #[kunit_tests(kunit_test_suit_name)]
+/// mod tests {
+/// #[test]
+/// fn foo() {
+/// assert_eq!(1, 1);
+/// }
+///
+/// #[test]
+/// fn bar() {
+/// assert_eq!(2, 2);
+/// }
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
+ kunit::kunit_tests(attr, ts)
+}
--
2.49.0.rc0.332.g42c0ae87b1-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v8 3/3] rust: kunit: allow to know if we are in a test
2025-03-07 9:00 [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
2025-03-07 9:00 ` [PATCH v8 1/3] rust: kunit: add KUnit case and suite macros David Gow
2025-03-07 9:00 ` [PATCH v8 2/3] rust: macros: add macro to easily run KUnit tests David Gow
@ 2025-03-07 9:00 ` David Gow
2025-03-18 8:14 ` [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
2025-03-20 11:24 ` Miguel Ojeda
4 siblings, 0 replies; 8+ messages in thread
From: David Gow @ 2025-03-07 9:00 UTC (permalink / raw)
To: Miguel Ojeda, José Expósito, Rae Moar, Boqun Feng,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, Brendan Higgins, Tamir Duberstein
Cc: kunit-dev, linux-kselftest, rust-for-linux, linux-kernel,
David Gow
From: José Expósito <jose.exposito89@gmail.com>
In some cases, we need to call test-only code from outside the test
case, for example, to mock a function or a module.
In order to check whether we are in a test or not, we need to test if
`CONFIG_KUNIT` is set.
Unfortunately, we cannot rely only on this condition because:
- a test could be running in another thread,
- some distros compile KUnit in production kernels, so checking at runtime
that `current->kunit_test != NULL` is required.
Forturately, KUnit provides an optimised check in
`kunit_get_current_test()`, which checks CONFIG_KUNIT, a global static
key, and then the current thread's running KUnit test.
Add a safe wrapper function around this to know whether or not we are in
a KUnit test and examples showing how to mock a function and a module.
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Co-developed-by: Miguel Ojeda <ojeda@kernel.org>
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Co-developed-by: David Gow <davidgow@google.com>
Signed-off-by: David Gow <davidgow@google.com>
---
Changes since v7:
https://lore.kernel.org/rust-for-linux/20250214074051.1619256-4-davidgow@google.com/
- Remove the second example, which shadowed a function in order to mock
it, as it was (a) unclear that it is only useful for code in the same
file as the test, and (b) didn't use in_kunit_test(), which would've
made it more useful (but still limited). The first example should be
good enough to point people in the right direction for now. (Thanks,
Tamir)
Changes since v6:
https://lore.kernel.org/rust-for-linux/20250214074051.1619256-4-davidgow@google.com/
- Doc comments now have a useful link. (Thanks, Tamir!)
- A small tidy-up to limit unsafe usage. (Thanks, Tamir!)
Changes since v5:
https://lore.kernel.org/all/20241213081035.2069066-4-davidgow@google.com/
- Greatly improved documentation, which is both clearer and better
matches the rustdoc norm. (Thanks, Miguel)
- The examples and safety comments are also both more idiomatic an
cleaner. (Thanks, Miguel)
- More things sit appropriately behind CONFIG_KUNIT (Thanks, Miguel)
Changes since v4:
https://lore.kernel.org/linux-kselftest/20241101064505.3820737-4-davidgow@google.com/
- Rebased against 6.13-rc1
- Fix some missing safety comments, and remove some unneeded 'unsafe'
blocks. (Thanks Boqun)
Changes since v3:
https://lore.kernel.org/linux-kselftest/20241030045719.3085147-8-davidgow@google.com/
- The example test has been updated to no longer use assert_eq!() with
a constant bool argument (fixes a clippy warning).
No changes since v2:
https://lore.kernel.org/linux-kselftest/20241029092422.2884505-4-davidgow@google.com/
Changes since v1:
https://lore.kernel.org/lkml/20230720-rustbind-v1-3-c80db349e3b5@google.com/
- Rebased on top of rust-next.
- Use the `kunit_get_current_test()` C function, which wasn't previously
available, instead of rolling our own.
- (Thanks also to Boqun for suggesting a nicer way of implementing this,
which I tried, but the `kunit_get_current_test()` version obsoleted.)
---
rust/kernel/kunit.rs | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
index 97e99b52e4a9..7388fa831b8e 100644
--- a/rust/kernel/kunit.rs
+++ b/rust/kernel/kunit.rs
@@ -288,11 +288,47 @@ macro_rules! kunit_unsafe_test_suite {
};
}
+/// Returns whether we are currently running a KUnit test.
+///
+/// In some cases, you need to call test-only code from outside the test case, for example, to
+/// create a function mock. This function allows to change behavior depending on whether we are
+/// currently running a KUnit test or not.
+///
+/// # Examples
+///
+/// This example shows how a function can be mocked to return a well-known value while testing:
+///
+/// ```
+/// # use kernel::kunit::in_kunit_test;
+/// fn fn_mock_example(n: i32) -> i32 {
+/// if in_kunit_test() {
+/// return 100;
+/// }
+///
+/// n + 1
+/// }
+///
+/// let mock_res = fn_mock_example(5);
+/// assert_eq!(mock_res, 100);
+/// ```
+pub fn in_kunit_test() -> bool {
+ // SAFETY: `kunit_get_current_test()` is always safe to call (it has fallbacks for
+ // when KUnit is not enabled).
+ !unsafe { bindings::kunit_get_current_test() }.is_null()
+}
+
#[kunit_tests(rust_kernel_kunit)]
mod tests {
+ use super::*;
+
#[test]
fn rust_test_kunit_example_test() {
#![expect(clippy::eq_op)]
assert_eq!(1 + 1, 2);
}
+
+ #[test]
+ fn rust_test_kunit_in_kunit_test() {
+ assert!(in_kunit_test());
+ }
}
--
2.49.0.rc0.332.g42c0ae87b1-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax
2025-03-07 9:00 [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
` (2 preceding siblings ...)
2025-03-07 9:00 ` [PATCH v8 3/3] rust: kunit: allow to know if we are in a test David Gow
@ 2025-03-18 8:14 ` David Gow
2025-03-20 23:13 ` Miguel Ojeda
2025-03-20 11:24 ` Miguel Ojeda
4 siblings, 1 reply; 8+ messages in thread
From: David Gow @ 2025-03-18 8:14 UTC (permalink / raw)
To: Miguel Ojeda, José Expósito, Rae Moar, Boqun Feng,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, Brendan Higgins, Tamir Duberstein
Cc: kunit-dev, linux-kselftest, rust-for-linux, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1334 bytes --]
On Fri, 7 Mar 2025 at 17:01, David Gow <davidgow@google.com> wrote:
>
> Hi all,
>
> This is v8 of the Rust/KUnit integration patch. I think all of the
> suggestions have at least been responded to (even if there are a few I'm
> leaving as either future projects or matters of taste). Hopefully this
> is good-to-go for 6.15, so we can start using it concurrently with
> making any additional improvements we may wish.
>
> This series was originally written by José Expósito, and has been
> modified and updated by Matt Gilbride, Miguel Ojeda, and myself. The
> original version can be found here:
> https://github.com/Rust-for-Linux/linux/pull/950
Has anyone got any objections to this for 6.15?
I'm happy for it to go in via the rust tree if that's most convenient,
but if you'd rather it go in via kselftest/kunit, that's fine, too.
The only reason for delay I can think of is all of the patches which
adjust clippy warnings, etc., for things like addr_of_mut!() (and
possibly some of the casting). I'm unlikely to have time to re-send
everything out before the merge window with those fixed (I'm going to
be away for the latter half of this week), so if folks are okay with
either accepting this as-is, doing a follow-up fix, or delaying those
warnings, that's ideal on my end.
Cheers,
-- David
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5281 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax
2025-03-07 9:00 [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
` (3 preceding siblings ...)
2025-03-18 8:14 ` [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
@ 2025-03-20 11:24 ` Miguel Ojeda
2025-03-21 2:28 ` David Gow
4 siblings, 1 reply; 8+ messages in thread
From: Miguel Ojeda @ 2025-03-20 11:24 UTC (permalink / raw)
To: David Gow
Cc: Miguel Ojeda, José Expósito, Rae Moar, Boqun Feng,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, Brendan Higgins, Tamir Duberstein,
kunit-dev, linux-kselftest, rust-for-linux, linux-kernel
On Fri, Mar 7, 2025 at 10:01 AM David Gow <davidgow@google.com> wrote:
>
> This is v8 of the Rust/KUnit integration patch. I think all of the
> suggestions have at least been responded to (even if there are a few I'm
> leaving as either future projects or matters of taste). Hopefully this
> is good-to-go for 6.15, so we can start using it concurrently with
> making any additional improvements we may wish.
Applied to `rust-next` -- thanks everyone!
[ Applied Markdown in comment. - Miguel ]
[ Removed spurious (in rendered form) newline in docs. - Miguel ]
(finally! Special thanks to David here.)
Cheers,
Miguel
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax
2025-03-18 8:14 ` [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
@ 2025-03-20 23:13 ` Miguel Ojeda
0 siblings, 0 replies; 8+ messages in thread
From: Miguel Ojeda @ 2025-03-20 23:13 UTC (permalink / raw)
To: David Gow
Cc: Miguel Ojeda, José Expósito, Rae Moar, Boqun Feng,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, Brendan Higgins, Tamir Duberstein,
kunit-dev, linux-kselftest, rust-for-linux, linux-kernel
On Tue, Mar 18, 2025 at 9:14 AM David Gow <davidgow@google.com> wrote:
>
> The only reason for delay I can think of is all of the patches which
> adjust clippy warnings, etc., for things like addr_of_mut!() (and
> possibly some of the casting). I'm unlikely to have time to re-send
> everything out before the merge window with those fixed (I'm going to
> be away for the latter half of this week), so if folks are okay with
> either accepting this as-is, doing a follow-up fix, or delaying those
> warnings, that's ideal on my end.
Yeah, those series (Antonio's or Tamir's) are independent, and in my
view they shouldn't delay something (a feature) like this series.
Cheers,
Miguel
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax
2025-03-20 11:24 ` Miguel Ojeda
@ 2025-03-21 2:28 ` David Gow
0 siblings, 0 replies; 8+ messages in thread
From: David Gow @ 2025-03-21 2:28 UTC (permalink / raw)
To: Miguel Ojeda
Cc: Miguel Ojeda, José Expósito, Rae Moar, Boqun Feng,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, Brendan Higgins, Tamir Duberstein,
kunit-dev, linux-kselftest, rust-for-linux, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 909 bytes --]
On Thu, 20 Mar 2025 at 19:24, Miguel Ojeda
<miguel.ojeda.sandonis@gmail.com> wrote:
>
> On Fri, Mar 7, 2025 at 10:01 AM David Gow <davidgow@google.com> wrote:
> >
> > This is v8 of the Rust/KUnit integration patch. I think all of the
> > suggestions have at least been responded to (even if there are a few I'm
> > leaving as either future projects or matters of taste). Hopefully this
> > is good-to-go for 6.15, so we can start using it concurrently with
> > making any additional improvements we may wish.
>
> Applied to `rust-next` -- thanks everyone!
>
> [ Applied Markdown in comment. - Miguel ]
>
> [ Removed spurious (in rendered form) newline in docs. - Miguel ]
>
> (finally! Special thanks to David here.)
>
Woohoo! Glad to see this finally at the finish line.
I'll be able to finally have the chocolate I've been saving for when this lands!
Thanks,
-- David
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5281 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-03-21 2:28 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-03-07 9:00 [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
2025-03-07 9:00 ` [PATCH v8 1/3] rust: kunit: add KUnit case and suite macros David Gow
2025-03-07 9:00 ` [PATCH v8 2/3] rust: macros: add macro to easily run KUnit tests David Gow
2025-03-07 9:00 ` [PATCH v8 3/3] rust: kunit: allow to know if we are in a test David Gow
2025-03-18 8:14 ` [PATCH v8 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
2025-03-20 23:13 ` Miguel Ojeda
2025-03-20 11:24 ` Miguel Ojeda
2025-03-21 2:28 ` David Gow
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).