* [PATCH v4 0/3] rust: kunit: Support KUnit tests with a user-space like syntax
@ 2024-11-01 6:44 David Gow
2024-11-01 6:45 ` [PATCH v4 1/3] rust: kunit: add KUnit case and suite macros David Gow
` (2 more replies)
0 siblings, 3 replies; 12+ messages in thread
From: David Gow @ 2024-11-01 6:44 UTC (permalink / raw)
To: Miguel Ojeda, José Expósito, Brendan Higgins, Rae Moar,
Boqun Feng, Alex Gaynor, Gary Guo, Benno Lossin,
Björn Roy Baron, Alice Ryhl, Matt Gilbride
Cc: David Gow, kunit-dev, linux-kselftest, rust-for-linux,
linux-kernel
This series was originally written by José Expósito, and has been
modified and updated by Matt Gilbride 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 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 | 194 +++++++++++++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
rust/macros/kunit.rs | 168 +++++++++++++++++++++++++++++++++++++
rust/macros/lib.rs | 29 +++++++
5 files changed, 393 insertions(+)
create mode 100644 rust/macros/kunit.rs
--
2.47.0.199.ga7371fff76-goog
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 1/3] rust: kunit: add KUnit case and suite macros
2024-11-01 6:44 [PATCH v4 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
@ 2024-11-01 6:45 ` David Gow
2024-11-01 17:47 ` Boqun Feng
2024-11-01 6:45 ` [PATCH v4 2/3] rust: macros: add macro to easily run KUnit tests David Gow
2024-11-01 6:45 ` [PATCH v4 3/3] rust: kunit: allow to know if we are in a test David Gow
2 siblings, 1 reply; 12+ messages in thread
From: David Gow @ 2024-11-01 6:45 UTC (permalink / raw)
To: Miguel Ojeda, José Expósito, Brendan Higgins, Rae Moar,
Boqun Feng, Alex Gaynor, Gary Guo, Benno Lossin,
Björn Roy Baron, Alice Ryhl, Matt Gilbride
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: David Gow <davidgow@google.com>
Signed-off-by: David Gow <davidgow@google.com>
---
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 | 112 +++++++++++++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
2 files changed, 113 insertions(+)
diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
index 824da0e9738a..85bc1faff0d5 100644
--- a/rust/kernel/kunit.rs
+++ b/rust/kernel/kunit.rs
@@ -161,3 +161,115 @@ macro_rules! kunit_assert_eq {
$crate::kunit_assert!($name, $file, $diff, $left == $right);
}};
}
+
+/// Represents an individual test case.
+///
+/// The test case should have the signature
+/// `unsafe extern "C" fn test_case(test: *mut crate::bindings::kunit)`.
+///
+/// The `kunit_unsafe_test_suite!` macro expects a NULL-terminated list of test cases.
+/// Use `kunit_case_null` to generate such a delimeter.
+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 retuns such a delimiter.
+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 test cases.
+///
+/// # Examples
+///
+/// ```ignore
+/// unsafe extern "C" fn test_fn(_test: *mut crate::bindings::kunit) {
+/// let actual = 1 + 1;
+/// let expected = 2;
+/// assert_eq!(actual, expected);
+/// }
+///
+/// static mut KUNIT_TEST_CASE: crate::bindings::kunit_case = crate::kunit_case(name, test_fn);
+/// static mut KUNIT_NULL_CASE: crate::bindings::kunit_case = crate::kunit_case_null();
+/// static mut KUNIT_TEST_CASES: &mut[crate::bindings::kunit_case] = unsafe {
+/// &mut[KUNIT_TEST_CASE, KUNIT_NULL_CASE]
+/// };
+/// crate::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
+/// ```
+#[macro_export]
+macro_rules! kunit_unsafe_test_suite {
+ ($name:ident, $test_cases:ident) => {
+ const _: () = {
+ static KUNIT_TEST_SUITE_NAME: [i8; 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 i8;
+ i += 1;
+ }
+
+ ret
+ };
+
+ static mut KUNIT_TEST_SUITE: $crate::bindings::kunit_suite =
+ $crate::bindings::kunit_suite {
+ name: KUNIT_TEST_SUITE_NAME,
+ // SAFETY: User is expected to pass a correct `test_cases`, hence this macro
+ // named 'unsafe'.
+ test_cases: unsafe { $test_cases.as_mut_ptr() },
+ suite_init: None,
+ suite_exit: None,
+ init: None,
+ exit: None,
+ attr: $crate::bindings::kunit_attributes {
+ speed: $crate::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]
+ #[link_section = ".kunit_test_suites"]
+ static mut KUNIT_TEST_SUITE_ENTRY: *const $crate::bindings::kunit_suite =
+ // SAFETY: `KUNIT_TEST_SUITE` is static.
+ unsafe { core::ptr::addr_of_mut!(KUNIT_TEST_SUITE) };
+ };
+ };
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index b62451f64f6e..d662aa37070f 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -18,6 +18,7 @@
#![feature(inline_const)]
#![feature(lint_reasons)]
#![feature(unsize)]
+#![feature(const_mut_refs)]
// Ensure conditional compilation based on the kernel configuration works;
// otherwise we may silently break things like initcall handling.
--
2.47.0.199.ga7371fff76-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 2/3] rust: macros: add macro to easily run KUnit tests
2024-11-01 6:44 [PATCH v4 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
2024-11-01 6:45 ` [PATCH v4 1/3] rust: kunit: add KUnit case and suite macros David Gow
@ 2024-11-01 6:45 ` David Gow
2024-11-01 21:38 ` Boqun Feng
2024-11-01 6:45 ` [PATCH v4 3/3] rust: kunit: allow to know if we are in a test David Gow
2 siblings, 1 reply; 12+ messages in thread
From: David Gow @ 2024-11-01 6:45 UTC (permalink / raw)
To: Miguel Ojeda, José Expósito, Brendan Higgins, Rae Moar,
Boqun Feng, Alex Gaynor, Gary Guo, Benno Lossin,
Björn Roy Baron, Alice Ryhl, Matt Gilbride
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>
Signed-off-by: David Gow <davidgow@google.com>
---
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 | 10 +++
rust/macros/kunit.rs | 168 +++++++++++++++++++++++++++++++++++++++++++
rust/macros/lib.rs | 29 ++++++++
4 files changed, 208 insertions(+)
create mode 100644 rust/macros/kunit.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index b77f4495dcf4..b65035ede675 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12433,6 +12433,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 85bc1faff0d5..71ce1d145be8 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.
@@ -273,3 +275,11 @@ macro_rules! kunit_unsafe_test_suite {
};
};
}
+
+#[kunit_tests(rust_kernel_kunit)]
+mod tests {
+ #[test]
+ fn rust_test_kunit_example_test() {
+ assert_eq!(1 + 1, 2);
+ }
+}
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
new file mode 100644
index 000000000000..c421ff65f7f9
--- /dev/null
+++ b/rust/macros/kunit.rs
@@ -0,0 +1,168 @@
+// 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 {
+ if attr.to_string().is_empty() {
+ panic!("Missing test name in #[kunit_tests(test_name)] macro")
+ }
+
+ if attr.to_string().as_bytes().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();
+ // }
+ // static mut KUNIT_CASE_FOO: kernel::bindings::kunit_case =
+ // kernel::kunit::kunit_case(foo, kunit_rust_wrapper_foo);
+ //
+ // unsafe extern "C" fn kunit_rust_wrapper_bar(_test: * mut kernel::bindings::kunit) {
+ // bar();
+ // }
+ // static mut KUNIT_CASE_BAR: kernel::bindings::kunit_case =
+ // kernel::kunit::kunit_case(bar, kunit_rust_wrapper_bar);
+ //
+ // static mut KUNIT_CASE_NULL: kernel::bindings::kunit_case = kernel::kunit::kunit_case_null();
+ //
+ // static mut TEST_CASES : &mut[kernel::bindings::kunit_case] = unsafe {
+ // &mut [KUNIT_CASE_FOO, KUNIT_CASE_BAR, 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_case_name = format!("KUNIT_CASE_{}", test.to_uppercase());
+ let kunit_wrapper = format!(
+ "unsafe extern \"C\" fn {}(_test: *mut kernel::bindings::kunit) {{ {}(); }}",
+ kunit_wrapper_fn_name, test
+ );
+ let kunit_case = format!(
+ "static mut {}: kernel::bindings::kunit_case = kernel::kunit::kunit_case(kernel::c_str!(\"{}\"), {});",
+ kunit_case_name, test, kunit_wrapper_fn_name
+ );
+ writeln!(kunit_macros, "{kunit_wrapper}").unwrap();
+ writeln!(kunit_macros, "{kunit_case}").unwrap();
+ writeln!(test_cases, "{kunit_case_name},").unwrap();
+ }
+
+ writeln!(
+ kunit_macros,
+ "static mut KUNIT_CASE_NULL: kernel::bindings::kunit_case = kernel::kunit::kunit_case_null();"
+ )
+ .unwrap();
+
+ writeln!(
+ kunit_macros,
+ "static mut TEST_CASES : &mut[kernel::bindings::kunit_case] = unsafe {{ &mut[{test_cases} KUNIT_CASE_NULL] }};"
+ )
+ .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 939ae00b723a..098925b99982 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;
@@ -430,3 +431,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.47.0.199.ga7371fff76-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 3/3] rust: kunit: allow to know if we are in a test
2024-11-01 6:44 [PATCH v4 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
2024-11-01 6:45 ` [PATCH v4 1/3] rust: kunit: add KUnit case and suite macros David Gow
2024-11-01 6:45 ` [PATCH v4 2/3] rust: macros: add macro to easily run KUnit tests David Gow
@ 2024-11-01 6:45 ` David Gow
2024-11-01 21:29 ` Boqun Feng
2 siblings, 1 reply; 12+ messages in thread
From: David Gow @ 2024-11-01 6:45 UTC (permalink / raw)
To: Miguel Ojeda, José Expósito, Brendan Higgins, Rae Moar,
Boqun Feng, Alex Gaynor, Gary Guo, Benno Lossin,
Björn Roy Baron, Alice Ryhl, Matt Gilbride
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: David Gow <davidgow@google.com>
Signed-off-by: David Gow <davidgow@google.com>
---
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 | 72 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
index 71ce1d145be8..ad38d6d62446 100644
--- a/rust/kernel/kunit.rs
+++ b/rust/kernel/kunit.rs
@@ -276,10 +276,82 @@ macro_rules! kunit_unsafe_test_suite {
};
}
+/// In some cases, you need to call test-only code from outside the test case, for example, to
+/// create a function mock. This function can be invoked to know 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() {
+/// 100
+/// } else {
+/// n + 1
+/// }
+/// }
+///
+/// let mock_res = fn_mock_example(5);
+/// assert_eq!(mock_res, 100);
+/// ```
+///
+/// Sometimes, you don't control the code that needs to be mocked. This example shows how the
+/// `bindings` module can be mocked:
+///
+/// ```
+/// // Import our mock naming it as the real module.
+/// #[cfg(CONFIG_KUNIT)]
+/// use bindings_mock_example as bindings;
+///
+/// // This module mocks `bindings`.
+/// mod bindings_mock_example {
+/// use kernel::kunit::in_kunit_test;
+/// use kernel::bindings::u64_;
+///
+/// // Make the other binding functions available.
+/// pub(crate) use kernel::bindings::*;
+///
+/// // Mock `ktime_get_boot_fast_ns` to return a well-known value when running a KUnit test.
+/// pub(crate) unsafe fn ktime_get_boot_fast_ns() -> u64_ {
+/// if in_kunit_test() {
+/// 1234
+/// } else {
+/// unsafe { kernel::bindings::ktime_get_boot_fast_ns() }
+/// }
+/// }
+/// }
+///
+/// // This is the function we want to test. Since `bindings` has been mocked, we can use its
+/// // functions seamlessly.
+/// fn get_boot_ns() -> u64 {
+/// unsafe { bindings::ktime_get_boot_fast_ns() }
+/// }
+///
+/// let time = get_boot_ns();
+/// assert_eq!(time, 1234);
+/// ```
+pub fn in_kunit_test() -> bool {
+ // SAFETY: kunit_get_current_test() is always safe to call from C (it has fallbacks for
+ // when KUnit is not enabled), and we're only comparing the result to NULL.
+ unsafe { !bindings::kunit_get_current_test().is_null() }
+}
+
#[kunit_tests(rust_kernel_kunit)]
mod tests {
+ use super::*;
+
#[test]
fn rust_test_kunit_example_test() {
assert_eq!(1 + 1, 2);
}
+
+ #[test]
+ fn rust_test_kunit_in_kunit_test() {
+ let in_kunit = in_kunit_test();
+ assert!(in_kunit);
+ }
}
--
2.47.0.199.ga7371fff76-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v4 1/3] rust: kunit: add KUnit case and suite macros
2024-11-01 6:45 ` [PATCH v4 1/3] rust: kunit: add KUnit case and suite macros David Gow
@ 2024-11-01 17:47 ` Boqun Feng
2024-11-09 8:18 ` David Gow
0 siblings, 1 reply; 12+ messages in thread
From: Boqun Feng @ 2024-11-01 17:47 UTC (permalink / raw)
To: David Gow
Cc: Miguel Ojeda, José Expósito, Brendan Higgins, Rae Moar,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, kunit-dev, linux-kselftest,
rust-for-linux, linux-kernel
On Fri, Nov 01, 2024 at 02:45:00PM +0800, David Gow wrote:
> 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: David Gow <davidgow@google.com>
> Signed-off-by: David Gow <davidgow@google.com>
> ---
>
> 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 | 112 +++++++++++++++++++++++++++++++++++++++++++
> rust/kernel/lib.rs | 1 +
> 2 files changed, 113 insertions(+)
>
> diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
> index 824da0e9738a..85bc1faff0d5 100644
> --- a/rust/kernel/kunit.rs
> +++ b/rust/kernel/kunit.rs
> @@ -161,3 +161,115 @@ macro_rules! kunit_assert_eq {
> $crate::kunit_assert!($name, $file, $diff, $left == $right);
> }};
> }
> +
> +/// Represents an individual test case.
> +///
> +/// The test case should have the signature
> +/// `unsafe extern "C" fn test_case(test: *mut crate::bindings::kunit)`.
> +///
> +/// The `kunit_unsafe_test_suite!` macro expects a NULL-terminated list of test cases.
> +/// Use `kunit_case_null` to generate such a delimeter.
> +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 retuns such a delimiter.
> +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 test cases.
> +///
> +/// # Examples
> +///
> +/// ```ignore
> +/// unsafe extern "C" fn test_fn(_test: *mut crate::bindings::kunit) {
> +/// let actual = 1 + 1;
> +/// let expected = 2;
> +/// assert_eq!(actual, expected);
> +/// }
> +///
> +/// static mut KUNIT_TEST_CASE: crate::bindings::kunit_case = crate::kunit_case(name, test_fn);
> +/// static mut KUNIT_NULL_CASE: crate::bindings::kunit_case = crate::kunit_case_null();
> +/// static mut KUNIT_TEST_CASES: &mut[crate::bindings::kunit_case] = unsafe {
> +/// &mut[KUNIT_TEST_CASE, KUNIT_NULL_CASE]
> +/// };
> +/// crate::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
> +/// ```
> +#[macro_export]
> +macro_rules! kunit_unsafe_test_suite {
> + ($name:ident, $test_cases:ident) => {
> + const _: () = {
> + static KUNIT_TEST_SUITE_NAME: [i8; 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 i8;
> + i += 1;
> + }
> +
> + ret
> + };
> +
> + static mut KUNIT_TEST_SUITE: $crate::bindings::kunit_suite =
> + $crate::bindings::kunit_suite {
> + name: KUNIT_TEST_SUITE_NAME,
> + // SAFETY: User is expected to pass a correct `test_cases`, hence this macro
> + // named 'unsafe'.
> + test_cases: unsafe { $test_cases.as_mut_ptr() },
> + suite_init: None,
> + suite_exit: None,
> + init: None,
> + exit: None,
> + attr: $crate::bindings::kunit_attributes {
> + speed: $crate::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]
> + #[link_section = ".kunit_test_suites"]
> + static mut KUNIT_TEST_SUITE_ENTRY: *const $crate::bindings::kunit_suite =
> + // SAFETY: `KUNIT_TEST_SUITE` is static.
> + unsafe { core::ptr::addr_of_mut!(KUNIT_TEST_SUITE) };
FYI, `addr_of` and `addr_of_mut` is safe now on statics since 1.82:
https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html#safely-addressing-unsafe-statics
We need a `#[allow(unused_unsafe)]` here to avoid the unused unsafe
warning.
Regards,
Boqun
> + };
> + };
> +}
> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
> index b62451f64f6e..d662aa37070f 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -18,6 +18,7 @@
> #![feature(inline_const)]
> #![feature(lint_reasons)]
> #![feature(unsize)]
> +#![feature(const_mut_refs)]
>
> // Ensure conditional compilation based on the kernel configuration works;
> // otherwise we may silently break things like initcall handling.
> --
> 2.47.0.199.ga7371fff76-goog
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 3/3] rust: kunit: allow to know if we are in a test
2024-11-01 6:45 ` [PATCH v4 3/3] rust: kunit: allow to know if we are in a test David Gow
@ 2024-11-01 21:29 ` Boqun Feng
0 siblings, 0 replies; 12+ messages in thread
From: Boqun Feng @ 2024-11-01 21:29 UTC (permalink / raw)
To: David Gow
Cc: Miguel Ojeda, José Expósito, Brendan Higgins, Rae Moar,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, kunit-dev, linux-kselftest,
rust-for-linux, linux-kernel
On Fri, Nov 01, 2024 at 02:45:02PM +0800, David Gow wrote:
[...]
> +/// ```
> +/// // Import our mock naming it as the real module.
> +/// #[cfg(CONFIG_KUNIT)]
> +/// use bindings_mock_example as bindings;
> +///
> +/// // This module mocks `bindings`.
> +/// mod bindings_mock_example {
> +/// use kernel::kunit::in_kunit_test;
> +/// use kernel::bindings::u64_;
> +///
> +/// // Make the other binding functions available.
> +/// pub(crate) use kernel::bindings::*;
> +///
> +/// // Mock `ktime_get_boot_fast_ns` to return a well-known value when running a KUnit test.
> +/// pub(crate) unsafe fn ktime_get_boot_fast_ns() -> u64_ {
Clippy complains this `unsafe` pub function doesn't have a "# Safety"
section. Actually this function is not necessarily to be `unsafe`.
> +/// if in_kunit_test() {
> +/// 1234
> +/// } else {
> +/// unsafe { kernel::bindings::ktime_get_boot_fast_ns() }
Need safety comments here,
> +/// }
> +/// }
> +/// }
> +///
> +/// // This is the function we want to test. Since `bindings` has been mocked, we can use its
> +/// // functions seamlessly.
> +/// fn get_boot_ns() -> u64 {
> +/// unsafe { bindings::ktime_get_boot_fast_ns() }
and here. If you make ktime_get_boot_fast_ns() safe, then no unsafe
block is needed here.
Regards,
Boqun
> +/// }
> +///
> +/// let time = get_boot_ns();
> +/// assert_eq!(time, 1234);
> +/// ```
> +pub fn in_kunit_test() -> bool {
> + // SAFETY: kunit_get_current_test() is always safe to call from C (it has fallbacks for
> + // when KUnit is not enabled), and we're only comparing the result to NULL.
> + unsafe { !bindings::kunit_get_current_test().is_null() }
> +}
> +
> #[kunit_tests(rust_kernel_kunit)]
> mod tests {
> + use super::*;
> +
> #[test]
> fn rust_test_kunit_example_test() {
> assert_eq!(1 + 1, 2);
> }
> +
> + #[test]
> + fn rust_test_kunit_in_kunit_test() {
> + let in_kunit = in_kunit_test();
> + assert!(in_kunit);
> + }
> }
> --
> 2.47.0.199.ga7371fff76-goog
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 2/3] rust: macros: add macro to easily run KUnit tests
2024-11-01 6:45 ` [PATCH v4 2/3] rust: macros: add macro to easily run KUnit tests David Gow
@ 2024-11-01 21:38 ` Boqun Feng
2024-11-01 22:12 ` Miguel Ojeda
0 siblings, 1 reply; 12+ messages in thread
From: Boqun Feng @ 2024-11-01 21:38 UTC (permalink / raw)
To: David Gow
Cc: Miguel Ojeda, José Expósito, Brendan Higgins, Rae Moar,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, kunit-dev, linux-kselftest,
rust-for-linux, linux-kernel
On Fri, Nov 01, 2024 at 02:45:01PM +0800, David Gow wrote:
[...]
> @@ -273,3 +275,11 @@ macro_rules! kunit_unsafe_test_suite {
> };
> };
> }
> +
> +#[kunit_tests(rust_kernel_kunit)]
> +mod tests {
> + #[test]
> + fn rust_test_kunit_example_test() {
> + assert_eq!(1 + 1, 2);
Clippy reported:
ERROR:root:error: identical args used in this `assert_eq!` macro call
--> ../rust/kernel/kunit.rs:348:20
|
348 | assert_eq!(1 + 1, 2);
| ^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#eq_op
= note: `-D clippy::eq-op` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::eq_op)]`
but this lint doesn't make sense to me, I would say we just drop this
lint?
Regards,
Boqun
> + }
> +}
[...]
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 2/3] rust: macros: add macro to easily run KUnit tests
2024-11-01 21:38 ` Boqun Feng
@ 2024-11-01 22:12 ` Miguel Ojeda
2024-11-02 0:08 ` Boqun Feng
0 siblings, 1 reply; 12+ messages in thread
From: Miguel Ojeda @ 2024-11-01 22:12 UTC (permalink / raw)
To: Boqun Feng
Cc: David Gow, Miguel Ojeda, José Expósito, Brendan Higgins,
Rae Moar, Alex Gaynor, Gary Guo, Benno Lossin,
Björn Roy Baron, Alice Ryhl, Matt Gilbride, kunit-dev,
linux-kselftest, rust-for-linux, linux-kernel
On Fri, Nov 1, 2024 at 10:38 PM Boqun Feng <boqun.feng@gmail.com> wrote:
>
> but this lint doesn't make sense to me, I would say we just drop this
> lint?
I am not sure if it is intended to fire there (i.e. to resolve
constants), but seems OK since it is not something one would want
normally to write except in exceptional cases.
So we could drop it globally, but if it is just this case that we
expect, then I think it is better to just `#[expect(...)]` it (or
perhaps use a different example) and then, if we see in the future
that we have quite a few "trivial" comparisons like this, then we can
drop it globally.
Cheers,
Miguel
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 2/3] rust: macros: add macro to easily run KUnit tests
2024-11-01 22:12 ` Miguel Ojeda
@ 2024-11-02 0:08 ` Boqun Feng
2024-11-03 15:41 ` Miguel Ojeda
0 siblings, 1 reply; 12+ messages in thread
From: Boqun Feng @ 2024-11-02 0:08 UTC (permalink / raw)
To: Miguel Ojeda
Cc: David Gow, Miguel Ojeda, José Expósito, Brendan Higgins,
Rae Moar, Alex Gaynor, Gary Guo, Benno Lossin,
Björn Roy Baron, Alice Ryhl, Matt Gilbride, kunit-dev,
linux-kselftest, rust-for-linux, linux-kernel
On Fri, Nov 01, 2024 at 11:12:06PM +0100, Miguel Ojeda wrote:
> On Fri, Nov 1, 2024 at 10:38 PM Boqun Feng <boqun.feng@gmail.com> wrote:
> >
> > but this lint doesn't make sense to me, I would say we just drop this
> > lint?
>
> I am not sure if it is intended to fire there (i.e. to resolve
> constants), but seems OK since it is not something one would want
> normally to write except in exceptional cases.
>
Hmm.. so I think clippy won't warn for a normal Rust #[test] function:
https://github.com/rust-lang/rust-clippy/pull/7811
but since in this patch #[test] is handled by us instead of by "rustc
--test" (I assume this is how Rust std handle #[cfg(test)] and
#[test]?), clippy doesn't know.
> So we could drop it globally, but if it is just this case that we
> expect, then I think it is better to just `#[expect(...)]` it (or
> perhaps use a different example) and then, if we see in the future
> that we have quite a few "trivial" comparisons like this, then we can
> drop it globally.
>
It seems to me that clippy agrees not triggering this in test code at
least.
Regards,
Boqun
> Cheers,
> Miguel
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 2/3] rust: macros: add macro to easily run KUnit tests
2024-11-02 0:08 ` Boqun Feng
@ 2024-11-03 15:41 ` Miguel Ojeda
0 siblings, 0 replies; 12+ messages in thread
From: Miguel Ojeda @ 2024-11-03 15:41 UTC (permalink / raw)
To: Boqun Feng
Cc: David Gow, Miguel Ojeda, José Expósito, Brendan Higgins,
Rae Moar, Alex Gaynor, Gary Guo, Benno Lossin,
Björn Roy Baron, Alice Ryhl, Matt Gilbride, kunit-dev,
linux-kselftest, rust-for-linux, linux-kernel
On Sat, Nov 2, 2024 at 1:08 AM Boqun Feng <boqun.feng@gmail.com> wrote:
>
> Hmm.. so I think clippy won't warn for a normal Rust #[test] function:
>
> https://github.com/rust-lang/rust-clippy/pull/7811
That is a very good point. It is a bit surprising that those details
are not documented, but we could mimic that behavior.
(Personally, I don't particularly enjoy exceptional/context-dependent
cases, unless it is something used everywhere, like `use`ing the
prelude that we have).
Cheers,
Miguel
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 1/3] rust: kunit: add KUnit case and suite macros
2024-11-01 17:47 ` Boqun Feng
@ 2024-11-09 8:18 ` David Gow
2024-11-09 20:32 ` Miguel Ojeda
0 siblings, 1 reply; 12+ messages in thread
From: David Gow @ 2024-11-09 8:18 UTC (permalink / raw)
To: Boqun Feng
Cc: Miguel Ojeda, José Expósito, Brendan Higgins, Rae Moar,
Alex Gaynor, Gary Guo, Benno Lossin, Björn Roy Baron,
Alice Ryhl, Matt Gilbride, kunit-dev, linux-kselftest,
rust-for-linux, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 8196 bytes --]
On Sat, 2 Nov 2024 at 01:47, Boqun Feng <boqun.feng@gmail.com> wrote:
>
> On Fri, Nov 01, 2024 at 02:45:00PM +0800, David Gow wrote:
> > 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: David Gow <davidgow@google.com>
> > Signed-off-by: David Gow <davidgow@google.com>
> > ---
> >
> > 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 | 112 +++++++++++++++++++++++++++++++++++++++++++
> > rust/kernel/lib.rs | 1 +
> > 2 files changed, 113 insertions(+)
> >
> > diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
> > index 824da0e9738a..85bc1faff0d5 100644
> > --- a/rust/kernel/kunit.rs
> > +++ b/rust/kernel/kunit.rs
> > @@ -161,3 +161,115 @@ macro_rules! kunit_assert_eq {
> > $crate::kunit_assert!($name, $file, $diff, $left == $right);
> > }};
> > }
> > +
> > +/// Represents an individual test case.
> > +///
> > +/// The test case should have the signature
> > +/// `unsafe extern "C" fn test_case(test: *mut crate::bindings::kunit)`.
> > +///
> > +/// The `kunit_unsafe_test_suite!` macro expects a NULL-terminated list of test cases.
> > +/// Use `kunit_case_null` to generate such a delimeter.
> > +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 retuns such a delimiter.
> > +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 test cases.
> > +///
> > +/// # Examples
> > +///
> > +/// ```ignore
> > +/// unsafe extern "C" fn test_fn(_test: *mut crate::bindings::kunit) {
> > +/// let actual = 1 + 1;
> > +/// let expected = 2;
> > +/// assert_eq!(actual, expected);
> > +/// }
> > +///
> > +/// static mut KUNIT_TEST_CASE: crate::bindings::kunit_case = crate::kunit_case(name, test_fn);
> > +/// static mut KUNIT_NULL_CASE: crate::bindings::kunit_case = crate::kunit_case_null();
> > +/// static mut KUNIT_TEST_CASES: &mut[crate::bindings::kunit_case] = unsafe {
> > +/// &mut[KUNIT_TEST_CASE, KUNIT_NULL_CASE]
> > +/// };
> > +/// crate::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
> > +/// ```
> > +#[macro_export]
> > +macro_rules! kunit_unsafe_test_suite {
> > + ($name:ident, $test_cases:ident) => {
> > + const _: () = {
> > + static KUNIT_TEST_SUITE_NAME: [i8; 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 i8;
> > + i += 1;
> > + }
> > +
> > + ret
> > + };
> > +
> > + static mut KUNIT_TEST_SUITE: $crate::bindings::kunit_suite =
> > + $crate::bindings::kunit_suite {
> > + name: KUNIT_TEST_SUITE_NAME,
> > + // SAFETY: User is expected to pass a correct `test_cases`, hence this macro
> > + // named 'unsafe'.
> > + test_cases: unsafe { $test_cases.as_mut_ptr() },
> > + suite_init: None,
> > + suite_exit: None,
> > + init: None,
> > + exit: None,
> > + attr: $crate::bindings::kunit_attributes {
> > + speed: $crate::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]
> > + #[link_section = ".kunit_test_suites"]
> > + static mut KUNIT_TEST_SUITE_ENTRY: *const $crate::bindings::kunit_suite =
> > + // SAFETY: `KUNIT_TEST_SUITE` is static.
> > + unsafe { core::ptr::addr_of_mut!(KUNIT_TEST_SUITE) };
>
> FYI, `addr_of` and `addr_of_mut` is safe now on statics since 1.82:
>
> https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html#safely-addressing-unsafe-statics
>
> We need a `#[allow(unused_unsafe)]` here to avoid the unused unsafe
> warning.
>
I've not been able to work out how to fix this: adding
"#![allow(unused_unsafe)]" to the unsafe block here triggers another
error:
ERROR:root:error[E0658]: attributes on expressions are experimental
Is there somewhere else we can put the attribute to support this
without having to either disable the warning, or use the experimental
feature globally?
-- David
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5294 bytes --]
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 1/3] rust: kunit: add KUnit case and suite macros
2024-11-09 8:18 ` David Gow
@ 2024-11-09 20:32 ` Miguel Ojeda
0 siblings, 0 replies; 12+ messages in thread
From: Miguel Ojeda @ 2024-11-09 20:32 UTC (permalink / raw)
To: David Gow
Cc: Boqun Feng, Miguel Ojeda, José Expósito,
Brendan Higgins, Rae Moar, Alex Gaynor, Gary Guo, Benno Lossin,
Björn Roy Baron, Alice Ryhl, Matt Gilbride, kunit-dev,
linux-kselftest, rust-for-linux, linux-kernel
On Sat, Nov 9, 2024 at 9:18 AM David Gow <davidgow@google.com> wrote:
>
> Is there somewhere else we can put the attribute to support this
> without having to either disable the warning, or use the experimental
> feature globally?
Yeah, on the item, e.g. https://godbolt.org/z/oo8osadn4:
const _: () = {
static mut X: i32 = 42;
#[allow(unused_unsafe)]
static mut S: *const i32 = unsafe { core::ptr::addr_of_mut!(X) };
};
Cheers,
Miguel
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2024-11-09 20:32 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-11-01 6:44 [PATCH v4 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
2024-11-01 6:45 ` [PATCH v4 1/3] rust: kunit: add KUnit case and suite macros David Gow
2024-11-01 17:47 ` Boqun Feng
2024-11-09 8:18 ` David Gow
2024-11-09 20:32 ` Miguel Ojeda
2024-11-01 6:45 ` [PATCH v4 2/3] rust: macros: add macro to easily run KUnit tests David Gow
2024-11-01 21:38 ` Boqun Feng
2024-11-01 22:12 ` Miguel Ojeda
2024-11-02 0:08 ` Boqun Feng
2024-11-03 15:41 ` Miguel Ojeda
2024-11-01 6:45 ` [PATCH v4 3/3] rust: kunit: allow to know if we are in a test David Gow
2024-11-01 21:29 ` Boqun Feng
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).