* [PATCH 02/11] rust: macros: use `quote!` from vendored crate
[not found] <20251211185805.2835633-1-gary@kernel.org>
@ 2025-12-11 18:56 ` Gary Guo
2025-12-16 9:47 ` Benno Lossin
2025-12-11 18:56 ` [PATCH 08/11] rust: macros: convert `#[kunit_tests]` macro to use `syn` Gary Guo
2025-12-11 18:56 ` [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
2 siblings, 1 reply; 9+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Brendan Higgins, David Gow, Rae Moar,
Greg Kroah-Hartman, Tamir Duberstein, Fiona Behrens, Igor Korotin,
José Expósito, Guilherme Giacomo Simoes
Cc: rust-for-linux, linux-kernel, linux-kselftest, kunit-dev
From: Gary Guo <gary@garyguo.net>
With `quote` crate now vendored in the kernel, we can remove our custom
`quote!` macro implementation and just rely on that crate instead.
The `quote` crate uses types from the `proc-macro2` library so we also
update to use that, and perform conversion in the top-level lib.rs.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/macros/concat_idents.rs | 2 +-
rust/macros/export.rs | 4 +-
rust/macros/fmt.rs | 4 +-
rust/macros/helpers.rs | 2 +-
rust/macros/kunit.rs | 3 +-
rust/macros/lib.rs | 21 ++--
rust/macros/module.rs | 6 +-
rust/macros/paste.rs | 2 +-
rust/macros/quote.rs | 182 -----------------------------------
rust/macros/vtable.rs | 3 +-
10 files changed, 28 insertions(+), 201 deletions(-)
delete mode 100644 rust/macros/quote.rs
diff --git a/rust/macros/concat_idents.rs b/rust/macros/concat_idents.rs
index 7e4b450f3a507..12cb231c3d715 100644
--- a/rust/macros/concat_idents.rs
+++ b/rust/macros/concat_idents.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{token_stream, Ident, TokenStream, TokenTree};
+use proc_macro2::{token_stream, Ident, TokenStream, TokenTree};
use crate::helpers::expect_punct;
diff --git a/rust/macros/export.rs b/rust/macros/export.rs
index a08f6337d5c8d..92d9b30971929 100644
--- a/rust/macros/export.rs
+++ b/rust/macros/export.rs
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
+use proc_macro2::TokenStream;
+use quote::quote;
+
use crate::helpers::function_name;
-use proc_macro::TokenStream;
/// Please see [`crate::export`] for documentation.
pub(crate) fn export(_attr: TokenStream, ts: TokenStream) -> TokenStream {
diff --git a/rust/macros/fmt.rs b/rust/macros/fmt.rs
index 2f4b9f6e22110..19f709262552b 100644
--- a/rust/macros/fmt.rs
+++ b/rust/macros/fmt.rs
@@ -1,8 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{Ident, TokenStream, TokenTree};
use std::collections::BTreeSet;
+use proc_macro2::{Ident, TokenStream, TokenTree};
+use quote::quote_spanned;
+
/// Please see [`crate::fmt`] for documentation.
pub(crate) fn fmt(input: TokenStream) -> TokenStream {
let mut input = input.into_iter();
diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
index e2602be402c10..853527b5d9567 100644
--- a/rust/macros/helpers.rs
+++ b/rust/macros/helpers.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{token_stream, Group, Ident, TokenStream, TokenTree};
+use proc_macro2::{token_stream, Group, Ident, TokenStream, TokenTree};
pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
if let Some(TokenTree::Ident(ident)) = it.next() {
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
index b395bb0536959..7427c17ee5f5c 100644
--- a/rust/macros/kunit.rs
+++ b/rust/macros/kunit.rs
@@ -4,10 +4,11 @@
//!
//! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
-use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
use std::collections::HashMap;
use std::fmt::Write;
+use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
+
pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
let attr = attr.to_string();
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 793f712dbf7c0..8e0f49ac783b2 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -11,8 +11,6 @@
// to avoid depending on the full `proc_macro_span` on Rust >= 1.88.0.
#![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))]
-#[macro_use]
-mod quote;
mod concat_idents;
mod export;
mod fmt;
@@ -101,7 +99,7 @@
/// the kernel module.
#[proc_macro]
pub fn module(ts: TokenStream) -> TokenStream {
- module::module(ts)
+ module::module(ts.into()).into()
}
/// Declares or implements a vtable trait.
@@ -176,7 +174,7 @@ pub fn module(ts: TokenStream) -> TokenStream {
/// [`kernel::error::VTABLE_DEFAULT_ERROR`]: ../kernel/error/constant.VTABLE_DEFAULT_ERROR.html
#[proc_macro_attribute]
pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
- vtable::vtable(attr, ts)
+ vtable::vtable(attr.into(), ts.into()).into()
}
/// Export a function so that C code can call it via a header file.
@@ -199,7 +197,7 @@ pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
/// automatically exported with `EXPORT_SYMBOL_GPL`.
#[proc_macro_attribute]
pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
- export::export(attr, ts)
+ export::export(attr.into(), ts.into()).into()
}
/// Like [`core::format_args!`], but automatically wraps arguments in [`kernel::fmt::Adapter`].
@@ -217,7 +215,7 @@ pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
/// [`pr_info!`]: ../kernel/macro.pr_info.html
#[proc_macro]
pub fn fmt(input: TokenStream) -> TokenStream {
- fmt::fmt(input)
+ fmt::fmt(input.into()).into()
}
/// Concatenate two identifiers.
@@ -275,7 +273,7 @@ pub fn fmt(input: TokenStream) -> TokenStream {
/// ```
#[proc_macro]
pub fn concat_idents(ts: TokenStream) -> TokenStream {
- concat_idents::concat_idents(ts)
+ concat_idents::concat_idents(ts.into()).into()
}
/// Paste identifiers together.
@@ -413,9 +411,12 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {
/// [`paste`]: https://docs.rs/paste/
#[proc_macro]
pub fn paste(input: TokenStream) -> TokenStream {
- let mut tokens = input.into_iter().collect();
+ let mut tokens = proc_macro2::TokenStream::from(input).into_iter().collect();
paste::expand(&mut tokens);
- tokens.into_iter().collect()
+ tokens
+ .into_iter()
+ .collect::<proc_macro2::TokenStream>()
+ .into()
}
/// Registers a KUnit test suite and its test cases using a user-space like syntax.
@@ -442,5 +443,5 @@ pub fn paste(input: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
- kunit::kunit_tests(attr, ts)
+ kunit::kunit_tests(attr.into(), ts.into()).into()
}
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 8cef6cc958b54..6974fb04f58fa 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
-use crate::helpers::*;
-use proc_macro::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
use std::fmt::Write;
+use proc_macro2::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
+
+use crate::helpers::*;
+
fn expect_string_array(it: &mut token_stream::IntoIter) -> Vec<String> {
let group = expect_group(it);
assert_eq!(group.delimiter(), Delimiter::Bracket);
diff --git a/rust/macros/paste.rs b/rust/macros/paste.rs
index cce712d19855b..2181e312a7d32 100644
--- a/rust/macros/paste.rs
+++ b/rust/macros/paste.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
+use proc_macro2::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
fn concat_helper(tokens: &[TokenTree]) -> Vec<(String, Span)> {
let mut tokens = tokens.iter();
diff --git a/rust/macros/quote.rs b/rust/macros/quote.rs
deleted file mode 100644
index ddfc21577539c..0000000000000
--- a/rust/macros/quote.rs
+++ /dev/null
@@ -1,182 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0 OR MIT
-
-use proc_macro::{TokenStream, TokenTree};
-
-pub(crate) trait ToTokens {
- fn to_tokens(&self, tokens: &mut TokenStream);
-}
-
-impl<T: ToTokens> ToTokens for Option<T> {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- if let Some(v) = self {
- v.to_tokens(tokens);
- }
- }
-}
-
-impl ToTokens for proc_macro::Group {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- tokens.extend([TokenTree::from(self.clone())]);
- }
-}
-
-impl ToTokens for proc_macro::Ident {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- tokens.extend([TokenTree::from(self.clone())]);
- }
-}
-
-impl ToTokens for TokenTree {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- tokens.extend([self.clone()]);
- }
-}
-
-impl ToTokens for TokenStream {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- tokens.extend(self.clone());
- }
-}
-
-/// Converts tokens into [`proc_macro::TokenStream`] and performs variable interpolations with
-/// the given span.
-///
-/// This is a similar to the
-/// [`quote_spanned!`](https://docs.rs/quote/latest/quote/macro.quote_spanned.html) macro from the
-/// `quote` crate but provides only just enough functionality needed by the current `macros` crate.
-macro_rules! quote_spanned {
- ($span:expr => $($tt:tt)*) => {{
- let mut tokens = ::proc_macro::TokenStream::new();
- {
- #[allow(unused_variables)]
- let span = $span;
- quote_spanned!(@proc tokens span $($tt)*);
- }
- tokens
- }};
- (@proc $v:ident $span:ident) => {};
- (@proc $v:ident $span:ident #$id:ident $($tt:tt)*) => {
- $crate::quote::ToTokens::to_tokens(&$id, &mut $v);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident #(#$id:ident)* $($tt:tt)*) => {
- for token in $id {
- $crate::quote::ToTokens::to_tokens(&token, &mut $v);
- }
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident ( $($inner:tt)* ) $($tt:tt)*) => {
- #[allow(unused_mut)]
- let mut tokens = ::proc_macro::TokenStream::new();
- quote_spanned!(@proc tokens $span $($inner)*);
- $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
- ::proc_macro::Delimiter::Parenthesis,
- tokens,
- ))]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident [ $($inner:tt)* ] $($tt:tt)*) => {
- let mut tokens = ::proc_macro::TokenStream::new();
- quote_spanned!(@proc tokens $span $($inner)*);
- $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
- ::proc_macro::Delimiter::Bracket,
- tokens,
- ))]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident { $($inner:tt)* } $($tt:tt)*) => {
- let mut tokens = ::proc_macro::TokenStream::new();
- quote_spanned!(@proc tokens $span $($inner)*);
- $v.extend([::proc_macro::TokenTree::Group(::proc_macro::Group::new(
- ::proc_macro::Delimiter::Brace,
- tokens,
- ))]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident :: $($tt:tt)*) => {
- $v.extend([::proc_macro::Spacing::Joint, ::proc_macro::Spacing::Alone].map(|spacing| {
- ::proc_macro::TokenTree::Punct(::proc_macro::Punct::new(':', spacing))
- }));
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident : $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new(':', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident , $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new(',', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident @ $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('@', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident ! $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('!', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident ; $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new(';', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident + $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('+', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident = $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('=', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident # $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('#', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident & $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Punct(
- ::proc_macro::Punct::new('&', ::proc_macro::Spacing::Alone),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident _ $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Ident(
- ::proc_macro::Ident::new("_", $span),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
- (@proc $v:ident $span:ident $id:ident $($tt:tt)*) => {
- $v.extend([::proc_macro::TokenTree::Ident(
- ::proc_macro::Ident::new(stringify!($id), $span),
- )]);
- quote_spanned!(@proc $v $span $($tt)*);
- };
-}
-
-/// Converts tokens into [`proc_macro::TokenStream`] and performs variable interpolations with
-/// mixed site span ([`Span::mixed_site()`]).
-///
-/// This is a similar to the [`quote!`](https://docs.rs/quote/latest/quote/macro.quote.html) macro
-/// from the `quote` crate but provides only just enough functionality needed by the current
-/// `macros` crate.
-///
-/// [`Span::mixed_site()`]: https://doc.rust-lang.org/proc_macro/struct.Span.html#method.mixed_site
-macro_rules! quote {
- ($($tt:tt)*) => {
- quote_spanned!(::proc_macro::Span::mixed_site() => $($tt)*)
- }
-}
diff --git a/rust/macros/vtable.rs b/rust/macros/vtable.rs
index ee06044fcd4f3..f209216785745 100644
--- a/rust/macros/vtable.rs
+++ b/rust/macros/vtable.rs
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
-use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
use std::collections::HashSet;
use std::fmt::Write;
+use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
+
pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
let mut tokens: Vec<_> = ts.into_iter().collect();
--
2.51.2
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 08/11] rust: macros: convert `#[kunit_tests]` macro to use `syn`
[not found] <20251211185805.2835633-1-gary@kernel.org>
2025-12-11 18:56 ` [PATCH 02/11] rust: macros: use `quote!` from vendored crate Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2026-01-04 23:38 ` Tamir Duberstein
2025-12-11 18:56 ` [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
2 siblings, 1 reply; 9+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Brendan Higgins, David Gow, Rae Moar,
Guilherme Giacomo Simoes, Patrick Miller, José Expósito,
Tamir Duberstein
Cc: rust-for-linux, linux-kselftest, kunit-dev, linux-kernel
From: Gary Guo <gary@garyguo.net>
This allows significant cleanups.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/macros/kunit.rs | 274 +++++++++++++++++++------------------------
rust/macros/lib.rs | 6 +-
2 files changed, 123 insertions(+), 157 deletions(-)
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
index 7427c17ee5f5c..516219f5b1356 100644
--- a/rust/macros/kunit.rs
+++ b/rust/macros/kunit.rs
@@ -4,81 +4,50 @@
//!
//! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
-use std::collections::HashMap;
-use std::fmt::Write;
-
-use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
-
-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 `{attr}` exceeds the maximum length of 255 bytes")
+use std::ffi::CString;
+
+use proc_macro2::TokenStream;
+use quote::{
+ format_ident,
+ quote,
+ ToTokens, //
+};
+use syn::{
+ parse_quote,
+ Error,
+ Ident,
+ Item,
+ ItemMod,
+ LitCStr,
+ Result, //
+};
+
+pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<TokenStream> {
+ if test_suite.to_string().len() > 255 {
+ return Err(Error::new_spanned(
+ test_suite,
+ "test suite names cannot exceed the maximum length of 255 bytes",
+ ));
}
- 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"),
+ // We cannot handle modules that defer to another file (e.g. `mod foo;`).
+ let Some((module_brace, module_items)) = module.content.take() else {
+ Err(Error::new_spanned(
+ module,
+ "`#[kunit_tests(test_name)]` attribute should only be applied to inline modules",
+ ))?
};
- // Get the functions set as tests. Search for `[test]` -> `fn`.
- let mut body_it = body.stream().into_iter();
- let mut tests = Vec::new();
- let mut attributes: HashMap<String, TokenStream> = HashMap::new();
- while let Some(token) = body_it.next() {
- match token {
- TokenTree::Punct(ref p) if p.as_char() == '#' => match body_it.next() {
- Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket => {
- if let Some(TokenTree::Ident(name)) = g.stream().into_iter().next() {
- // Collect attributes because we need to find which are tests. We also
- // need to copy `cfg` attributes so tests can be conditionally enabled.
- attributes
- .entry(name.to_string())
- .or_default()
- .extend([token, TokenTree::Group(g)]);
- }
- continue;
- }
- _ => (),
- },
- TokenTree::Ident(i) if i.to_string() == "fn" && attributes.contains_key("test") => {
- if let Some(TokenTree::Ident(test_name)) = body_it.next() {
- tests.push((test_name, attributes.remove("cfg").unwrap_or_default()))
- }
- }
-
- _ => (),
- }
- attributes.clear();
- }
+ // Make the entire module gated behind `CONFIG_KUNIT`.
+ module
+ .attrs
+ .insert(0, parse_quote!(#[cfg(CONFIG_KUNIT="y")]));
- // Add `#[cfg(CONFIG_KUNIT="y")]` before the module declaration.
- let config_kunit = "#[cfg(CONFIG_KUNIT=\"y\")]".to_owned().parse().unwrap();
- tokens.insert(
- 0,
- TokenTree::Group(Group::new(Delimiter::None, config_kunit)),
- );
+ let mut processed_items = Vec::new();
+ let mut test_cases = Vec::new();
// Generate the test KUnit test suite and a test case for each `#[test]`.
+ //
// The code generated for the following test module:
//
// ```
@@ -110,98 +79,93 @@ pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
//
// ::kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
// ```
- let mut kunit_macros = "".to_owned();
- let mut test_cases = "".to_owned();
- let mut assert_macros = "".to_owned();
- let path = crate::helpers::file();
- let num_tests = tests.len();
- for (test, cfg_attr) in tests {
- let kunit_wrapper_fn_name = format!("kunit_rust_wrapper_{test}");
- // Append any `cfg` attributes the user might have written on their tests so we don't
- // attempt to call them when they are `cfg`'d out. An extra `use` is used here to reduce
- // the length of the assert message.
- let kunit_wrapper = format!(
- r#"unsafe extern "C" fn {kunit_wrapper_fn_name}(_test: *mut ::kernel::bindings::kunit)
- {{
- (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
- {cfg_attr} {{
- (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
- use ::kernel::kunit::is_test_result_ok;
- assert!(is_test_result_ok({test}()));
+ //
+ // Non-function items (e.g. imports) are preserved.
+ for item in module_items {
+ let Item::Fn(mut f) = item else {
+ processed_items.push(item);
+ continue;
+ };
+
+ // TODO: Replace below with `extract_if` when MSRV is bumped above 1.85.
+ // Remove `#[test]` attributes applied on the function and count if any.
+ if !f.attrs.iter().any(|attr| attr.path().is_ident("test")) {
+ processed_items.push(Item::Fn(f));
+ continue;
+ }
+ f.attrs.retain(|attr| !attr.path().is_ident("test"));
+
+ let test = f.sig.ident.clone();
+
+ // Retrieve `#[cfg]` applied on the function which needs to be present on derived items too.
+ let cfg_attrs: Vec<_> = f
+ .attrs
+ .iter()
+ .filter(|attr| attr.path().is_ident("cfg"))
+ .cloned()
+ .collect();
+
+ // Before the test, override usual `assert!` and `assert_eq!` macros with ones that call
+ // KUnit instead.
+ let test_str = test.to_string();
+ let path = crate::helpers::file();
+ processed_items.push(parse_quote! {
+ #[allow(unused)]
+ macro_rules! assert {
+ ($cond:expr $(,)?) => {{
+ kernel::kunit_assert!(#test_str, #path, 0, $cond);
+ }}
+ }
+ });
+ processed_items.push(parse_quote! {
+ #[allow(unused)]
+ macro_rules! assert_eq {
+ ($left:expr, $right:expr $(,)?) => {{
+ kernel::kunit_assert_eq!(#test_str, #path, 0, $left, $right);
}}
- }}"#,
+ }
+ });
+
+ // Add back the test item.
+ processed_items.push(Item::Fn(f));
+
+ let kunit_wrapper_fn_name = format_ident!("kunit_rust_wrapper_{test}");
+ let test_cstr = LitCStr::new(
+ &CString::new(test_str.as_str()).expect("identifier cannot contain NUL"),
+ test.span(),
);
- writeln!(kunit_macros, "{kunit_wrapper}").unwrap();
- writeln!(
- test_cases,
- " ::kernel::kunit::kunit_case(::kernel::c_str!(\"{test}\"), {kunit_wrapper_fn_name}),"
- )
- .unwrap();
- writeln!(
- assert_macros,
- r#"
-/// Overrides the usual [`assert!`] macro with one that calls KUnit instead.
-#[allow(unused)]
-macro_rules! assert {{
- ($cond:expr $(,)?) => {{{{
- kernel::kunit_assert!("{test}", "{path}", 0, $cond);
- }}}}
-}}
-
-/// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead.
-#[allow(unused)]
-macro_rules! assert_eq {{
- ($left:expr, $right:expr $(,)?) => {{{{
- kernel::kunit_assert_eq!("{test}", "{path}", 0, $left, $right);
- }}}}
-}}
- "#
- )
- .unwrap();
- }
+ processed_items.push(parse_quote! {
+ unsafe extern "C" fn #kunit_wrapper_fn_name(_test: *mut ::kernel::bindings::kunit) {
+ (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
- writeln!(kunit_macros).unwrap();
- writeln!(
- kunit_macros,
- "static mut TEST_CASES: [::kernel::bindings::kunit_case; {}] = [\n{test_cases} ::kernel::kunit::kunit_case_null(),\n];",
- num_tests + 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);
+ // Append any `cfg` attributes the user might have written on their tests so we
+ // don't attempt to call them when they are `cfg`'d out. An extra `use` is used
+ // here to reduce the length of the assert message.
+ #(#cfg_attrs)*
+ {
+ (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
+ use ::kernel::kunit::is_test_result_ok;
+ assert!(is_test_result_ok(#test()));
}
- },
- _ => {
- new_body.push(token);
}
- }
- }
-
- let mut final_body = TokenStream::new();
- final_body.extend::<TokenStream>(assert_macros.parse().unwrap());
- final_body.extend(new_body);
- final_body.extend::<TokenStream>(kunit_macros.parse().unwrap());
+ });
- tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, final_body)));
+ test_cases.push(quote!(
+ ::kernel::kunit::kunit_case(#test_cstr, #kunit_wrapper_fn_name)
+ ));
+ }
- tokens.into_iter().collect()
+ let num_tests_plus_1 = test_cases.len() + 1;
+ processed_items.push(parse_quote! {
+ static mut TEST_CASES: [::kernel::bindings::kunit_case; #num_tests_plus_1] = [
+ #(#test_cases,)*
+ ::kernel::kunit::kunit_case_null(),
+ ];
+ });
+ processed_items.push(parse_quote! {
+ ::kernel::kunit_unsafe_test_suite!(#test_suite, TEST_CASES);
+ });
+
+ module.content = Some((module_brace, processed_items));
+ Ok(module.to_token_stream())
}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index bb2dfd4a4dafc..9cfac9fce0d36 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -453,6 +453,8 @@ pub fn paste(input: TokenStream) -> TokenStream {
/// }
/// ```
#[proc_macro_attribute]
-pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
- kunit::kunit_tests(attr.into(), ts.into()).into()
+pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
+ kunit::kunit_tests(parse_macro_input!(attr), parse_macro_input!(input))
+ .unwrap_or_else(|e| e.into_compile_error())
+ .into()
}
--
2.51.2
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value
[not found] <20251211185805.2835633-1-gary@kernel.org>
2025-12-11 18:56 ` [PATCH 02/11] rust: macros: use `quote!` from vendored crate Gary Guo
2025-12-11 18:56 ` [PATCH 08/11] rust: macros: convert `#[kunit_tests]` macro to use `syn` Gary Guo
@ 2025-12-11 18:56 ` Gary Guo
2025-12-16 12:48 ` Benno Lossin
2026-01-04 23:43 ` Tamir Duberstein
2 siblings, 2 replies; 9+ messages in thread
From: Gary Guo @ 2025-12-11 18:56 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Brendan Higgins, David Gow, Rae Moar
Cc: rust-for-linux, linux-kselftest, kunit-dev, linux-kernel
From: Gary Guo <gary@garyguo.net>
The last null element can be created (constly) using `pin_init::zeroed`,
so prefer to use it instead of adding a custom way of building it.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/kunit.rs | 26 +-------------------------
rust/macros/kunit.rs | 4 ++--
2 files changed, 3 insertions(+), 27 deletions(-)
diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
index 79436509dd73d..034158cdaf06b 100644
--- a/rust/kernel/kunit.rs
+++ b/rust/kernel/kunit.rs
@@ -192,9 +192,6 @@ pub fn is_test_result_ok(t: impl TestResult) -> bool {
}
/// 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,
@@ -215,27 +212,6 @@ pub const fn kunit_case(
}
}
-/// 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(),
- param_init: None,
- param_exit: None,
- }
-}
-
/// Registers a KUnit test suite.
///
/// # Safety
@@ -254,7 +230,7 @@ pub const fn kunit_case_null() -> kernel::bindings::kunit_case {
///
/// 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(),
+/// pin_init::zeroed(),
/// ];
/// kernel::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
/// ```
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
index 516219f5b1356..fd2cfabfaef76 100644
--- a/rust/macros/kunit.rs
+++ b/rust/macros/kunit.rs
@@ -74,7 +74,7 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<Toke
// 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(),
+ // ::pin_init::zeroed(),
// ];
//
// ::kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
@@ -159,7 +159,7 @@ macro_rules! assert_eq {
processed_items.push(parse_quote! {
static mut TEST_CASES: [::kernel::bindings::kunit_case; #num_tests_plus_1] = [
#(#test_cases,)*
- ::kernel::kunit::kunit_case_null(),
+ ::pin_init::zeroed(),
];
});
processed_items.push(parse_quote! {
--
2.51.2
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH 02/11] rust: macros: use `quote!` from vendored crate
2025-12-11 18:56 ` [PATCH 02/11] rust: macros: use `quote!` from vendored crate Gary Guo
@ 2025-12-16 9:47 ` Benno Lossin
2026-01-04 13:39 ` Tamir Duberstein
0 siblings, 1 reply; 9+ messages in thread
From: Benno Lossin @ 2025-12-16 9:47 UTC (permalink / raw)
To: Gary Guo, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Brendan Higgins, David Gow, Rae Moar, Greg Kroah-Hartman,
Tamir Duberstein, Fiona Behrens, Igor Korotin,
José Expósito, Guilherme Giacomo Simoes
Cc: rust-for-linux, linux-kernel, linux-kselftest, kunit-dev
On Thu Dec 11, 2025 at 7:56 PM CET, Gary Guo wrote:
> From: Gary Guo <gary@garyguo.net>
>
> With `quote` crate now vendored in the kernel, we can remove our custom
> `quote!` macro implementation and just rely on that crate instead.
>
> The `quote` crate uses types from the `proc-macro2` library so we also
> update to use that, and perform conversion in the top-level lib.rs.
>
> Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Benno Lossin <lossin@kernel.org>
> ---
> rust/macros/concat_idents.rs | 2 +-
> rust/macros/export.rs | 4 +-
> rust/macros/fmt.rs | 4 +-
> rust/macros/helpers.rs | 2 +-
> rust/macros/kunit.rs | 3 +-
> rust/macros/lib.rs | 21 ++--
> rust/macros/module.rs | 6 +-
> rust/macros/paste.rs | 2 +-
> rust/macros/quote.rs | 182 -----------------------------------
> rust/macros/vtable.rs | 3 +-
> 10 files changed, 28 insertions(+), 201 deletions(-)
> delete mode 100644 rust/macros/quote.rs
> @@ -275,7 +273,7 @@ pub fn fmt(input: TokenStream) -> TokenStream {
> /// ```
> #[proc_macro]
> pub fn concat_idents(ts: TokenStream) -> TokenStream {
> - concat_idents::concat_idents(ts)
> + concat_idents::concat_idents(ts.into()).into()
> }
>
> /// Paste identifiers together.
> @@ -413,9 +411,12 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {
> /// [`paste`]: https://docs.rs/paste/
> #[proc_macro]
> pub fn paste(input: TokenStream) -> TokenStream {
> - let mut tokens = input.into_iter().collect();
> + let mut tokens = proc_macro2::TokenStream::from(input).into_iter().collect();
> paste::expand(&mut tokens);
> - tokens.into_iter().collect()
> + tokens
> + .into_iter()
> + .collect::<proc_macro2::TokenStream>()
> + .into()
There is no `FromIterator` impl on `proc_macro::TokenStream`? Sad :(
Cheers,
Benno
> }
>
> /// Registers a KUnit test suite and its test cases using a user-space like syntax.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value
2025-12-11 18:56 ` [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
@ 2025-12-16 12:48 ` Benno Lossin
2026-01-04 23:43 ` Tamir Duberstein
1 sibling, 0 replies; 9+ messages in thread
From: Benno Lossin @ 2025-12-16 12:48 UTC (permalink / raw)
To: Gary Guo, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Brendan Higgins, David Gow, Rae Moar
Cc: rust-for-linux, linux-kselftest, kunit-dev, linux-kernel
On Thu Dec 11, 2025 at 7:56 PM CET, Gary Guo wrote:
> From: Gary Guo <gary@garyguo.net>
>
> The last null element can be created (constly) using `pin_init::zeroed`,
> so prefer to use it instead of adding a custom way of building it.
>
> Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Cheers,
Benno
> ---
> rust/kernel/kunit.rs | 26 +-------------------------
> rust/macros/kunit.rs | 4 ++--
> 2 files changed, 3 insertions(+), 27 deletions(-)
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 02/11] rust: macros: use `quote!` from vendored crate
2025-12-16 9:47 ` Benno Lossin
@ 2026-01-04 13:39 ` Tamir Duberstein
0 siblings, 0 replies; 9+ messages in thread
From: Tamir Duberstein @ 2026-01-04 13:39 UTC (permalink / raw)
To: Benno Lossin
Cc: Gary Guo, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Brendan Higgins, David Gow, Rae Moar, Greg Kroah-Hartman,
Fiona Behrens, Igor Korotin, José Expósito,
Guilherme Giacomo Simoes, rust-for-linux, linux-kernel,
linux-kselftest, kunit-dev
On Tue, Dec 16, 2025 at 4:47 AM Benno Lossin <lossin@kernel.org> wrote:
>
> On Thu Dec 11, 2025 at 7:56 PM CET, Gary Guo wrote:
> > From: Gary Guo <gary@garyguo.net>
> >
> > With `quote` crate now vendored in the kernel, we can remove our custom
> > `quote!` macro implementation and just rely on that crate instead.
> >
> > The `quote` crate uses types from the `proc-macro2` library so we also
> > update to use that, and perform conversion in the top-level lib.rs.
> >
> > Signed-off-by: Gary Guo <gary@garyguo.net>
>
> Reviewed-by: Benno Lossin <lossin@kernel.org>
Reviewed-by: Tamir Duberstein <tamird@gmail.com>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 08/11] rust: macros: convert `#[kunit_tests]` macro to use `syn`
2025-12-11 18:56 ` [PATCH 08/11] rust: macros: convert `#[kunit_tests]` macro to use `syn` Gary Guo
@ 2026-01-04 23:38 ` Tamir Duberstein
2026-01-05 2:28 ` Gary Guo
0 siblings, 1 reply; 9+ messages in thread
From: Tamir Duberstein @ 2026-01-04 23:38 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Brendan Higgins, David Gow, Rae Moar, Guilherme Giacomo Simoes,
Patrick Miller, José Expósito, rust-for-linux,
linux-kselftest, kunit-dev, linux-kernel
On Thu, Dec 11, 2025 at 2:30 PM Gary Guo <gary@kernel.org> wrote:
>
> From: Gary Guo <gary@garyguo.net>
>
> This allows significant cleanups.
>
> Signed-off-by: Gary Guo <gary@garyguo.net>
> ---
> rust/macros/kunit.rs | 274 +++++++++++++++++++------------------------
> rust/macros/lib.rs | 6 +-
> 2 files changed, 123 insertions(+), 157 deletions(-)
>
> diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
> index 7427c17ee5f5c..516219f5b1356 100644
> --- a/rust/macros/kunit.rs
> +++ b/rust/macros/kunit.rs
> @@ -4,81 +4,50 @@
> //!
> //! Copyright (c) 2023 José Expósito <jose.exposito89@gmail.com>
>
> -use std::collections::HashMap;
> -use std::fmt::Write;
> -
> -use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
> -
> -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 `{attr}` exceeds the maximum length of 255 bytes")
> +use std::ffi::CString;
> +
> +use proc_macro2::TokenStream;
> +use quote::{
> + format_ident,
> + quote,
> + ToTokens, //
> +};
> +use syn::{
> + parse_quote,
> + Error,
> + Ident,
> + Item,
> + ItemMod,
> + LitCStr,
> + Result, //
> +};
> +
> +pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<TokenStream> {
> + if test_suite.to_string().len() > 255 {
> + return Err(Error::new_spanned(
> + test_suite,
> + "test suite names cannot exceed the maximum length of 255 bytes",
> + ));
> }
>
> - 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"),
> + // We cannot handle modules that defer to another file (e.g. `mod foo;`).
> + let Some((module_brace, module_items)) = module.content.take() else {
> + Err(Error::new_spanned(
> + module,
> + "`#[kunit_tests(test_name)]` attribute should only be applied to inline modules",
> + ))?
> };
>
> - // Get the functions set as tests. Search for `[test]` -> `fn`.
> - let mut body_it = body.stream().into_iter();
> - let mut tests = Vec::new();
> - let mut attributes: HashMap<String, TokenStream> = HashMap::new();
> - while let Some(token) = body_it.next() {
> - match token {
> - TokenTree::Punct(ref p) if p.as_char() == '#' => match body_it.next() {
> - Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket => {
> - if let Some(TokenTree::Ident(name)) = g.stream().into_iter().next() {
> - // Collect attributes because we need to find which are tests. We also
> - // need to copy `cfg` attributes so tests can be conditionally enabled.
> - attributes
> - .entry(name.to_string())
> - .or_default()
> - .extend([token, TokenTree::Group(g)]);
> - }
> - continue;
> - }
> - _ => (),
> - },
> - TokenTree::Ident(i) if i.to_string() == "fn" && attributes.contains_key("test") => {
> - if let Some(TokenTree::Ident(test_name)) = body_it.next() {
> - tests.push((test_name, attributes.remove("cfg").unwrap_or_default()))
> - }
> - }
> -
> - _ => (),
> - }
> - attributes.clear();
> - }
> + // Make the entire module gated behind `CONFIG_KUNIT`.
> + module
> + .attrs
> + .insert(0, parse_quote!(#[cfg(CONFIG_KUNIT="y")]));
Does this need to be the first attribute? I think it can just be
pushed to the end.
>
> - // Add `#[cfg(CONFIG_KUNIT="y")]` before the module declaration.
> - let config_kunit = "#[cfg(CONFIG_KUNIT=\"y\")]".to_owned().parse().unwrap();
> - tokens.insert(
> - 0,
> - TokenTree::Group(Group::new(Delimiter::None, config_kunit)),
> - );
> + let mut processed_items = Vec::new();
> + let mut test_cases = Vec::new();
>
> // Generate the test KUnit test suite and a test case for each `#[test]`.
> + //
> // The code generated for the following test module:
> //
> // ```
> @@ -110,98 +79,93 @@ pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> //
> // ::kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
> // ```
> - let mut kunit_macros = "".to_owned();
> - let mut test_cases = "".to_owned();
> - let mut assert_macros = "".to_owned();
> - let path = crate::helpers::file();
> - let num_tests = tests.len();
> - for (test, cfg_attr) in tests {
> - let kunit_wrapper_fn_name = format!("kunit_rust_wrapper_{test}");
> - // Append any `cfg` attributes the user might have written on their tests so we don't
> - // attempt to call them when they are `cfg`'d out. An extra `use` is used here to reduce
> - // the length of the assert message.
> - let kunit_wrapper = format!(
> - r#"unsafe extern "C" fn {kunit_wrapper_fn_name}(_test: *mut ::kernel::bindings::kunit)
> - {{
> - (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
> - {cfg_attr} {{
> - (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
> - use ::kernel::kunit::is_test_result_ok;
> - assert!(is_test_result_ok({test}()));
> + //
> + // Non-function items (e.g. imports) are preserved.
> + for item in module_items {
> + let Item::Fn(mut f) = item else {
> + processed_items.push(item);
> + continue;
> + };
> +
> + // TODO: Replace below with `extract_if` when MSRV is bumped above 1.85.
> + // Remove `#[test]` attributes applied on the function and count if any.
What does "count if any" mean here?
> + if !f.attrs.iter().any(|attr| attr.path().is_ident("test")) {
> + processed_items.push(Item::Fn(f));
> + continue;
> + }
> + f.attrs.retain(|attr| !attr.path().is_ident("test"));
Can this code be something like this:
let before = f.attrs.len();
f.attrs.retain(|attr| !attr.path().is_ident("test"));
let after = f.attrs.len();
if after == before {
processed_items.push(Item::Fn(f));
continue;
}
> +
> + let test = f.sig.ident.clone();
> +
> + // Retrieve `#[cfg]` applied on the function which needs to be present on derived items too.
> + let cfg_attrs: Vec<_> = f
> + .attrs
> + .iter()
> + .filter(|attr| attr.path().is_ident("cfg"))
> + .cloned()
> + .collect();
> +
> + // Before the test, override usual `assert!` and `assert_eq!` macros with ones that call
> + // KUnit instead.
> + let test_str = test.to_string();
> + let path = crate::helpers::file();
> + processed_items.push(parse_quote! {
> + #[allow(unused)]
> + macro_rules! assert {
> + ($cond:expr $(,)?) => {{
> + kernel::kunit_assert!(#test_str, #path, 0, $cond);
> + }}
> + }
> + });
> + processed_items.push(parse_quote! {
> + #[allow(unused)]
> + macro_rules! assert_eq {
> + ($left:expr, $right:expr $(,)?) => {{
> + kernel::kunit_assert_eq!(#test_str, #path, 0, $left, $right);
> }}
> - }}"#,
> + }
> + });
Am I reading this right that the macros will be repeatedly redefined
before each test? Could we put them inside each test body instead?
> +
> + // Add back the test item.
> + processed_items.push(Item::Fn(f));
> +
> + let kunit_wrapper_fn_name = format_ident!("kunit_rust_wrapper_{test}");
> + let test_cstr = LitCStr::new(
> + &CString::new(test_str.as_str()).expect("identifier cannot contain NUL"),
> + test.span(),
> );
> - writeln!(kunit_macros, "{kunit_wrapper}").unwrap();
> - writeln!(
> - test_cases,
> - " ::kernel::kunit::kunit_case(::kernel::c_str!(\"{test}\"), {kunit_wrapper_fn_name}),"
> - )
> - .unwrap();
> - writeln!(
> - assert_macros,
> - r#"
> -/// Overrides the usual [`assert!`] macro with one that calls KUnit instead.
> -#[allow(unused)]
> -macro_rules! assert {{
> - ($cond:expr $(,)?) => {{{{
> - kernel::kunit_assert!("{test}", "{path}", 0, $cond);
> - }}}}
> -}}
> -
> -/// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead.
> -#[allow(unused)]
> -macro_rules! assert_eq {{
> - ($left:expr, $right:expr $(,)?) => {{{{
> - kernel::kunit_assert_eq!("{test}", "{path}", 0, $left, $right);
> - }}}}
> -}}
> - "#
> - )
> - .unwrap();
> - }
> + processed_items.push(parse_quote! {
> + unsafe extern "C" fn #kunit_wrapper_fn_name(_test: *mut ::kernel::bindings::kunit) {
> + (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
>
> - writeln!(kunit_macros).unwrap();
> - writeln!(
> - kunit_macros,
> - "static mut TEST_CASES: [::kernel::bindings::kunit_case; {}] = [\n{test_cases} ::kernel::kunit::kunit_case_null(),\n];",
> - num_tests + 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);
> + // Append any `cfg` attributes the user might have written on their tests so we
> + // don't attempt to call them when they are `cfg`'d out. An extra `use` is used
> + // here to reduce the length of the assert message.
> + #(#cfg_attrs)*
> + {
> + (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
> + use ::kernel::kunit::is_test_result_ok;
> + assert!(is_test_result_ok(#test()));
> }
> - },
> - _ => {
> - new_body.push(token);
> }
> - }
> - }
> -
> - let mut final_body = TokenStream::new();
> - final_body.extend::<TokenStream>(assert_macros.parse().unwrap());
> - final_body.extend(new_body);
> - final_body.extend::<TokenStream>(kunit_macros.parse().unwrap());
> + });
>
> - tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, final_body)));
> + test_cases.push(quote!(
> + ::kernel::kunit::kunit_case(#test_cstr, #kunit_wrapper_fn_name)
> + ));
> + }
>
> - tokens.into_iter().collect()
> + let num_tests_plus_1 = test_cases.len() + 1;
> + processed_items.push(parse_quote! {
> + static mut TEST_CASES: [::kernel::bindings::kunit_case; #num_tests_plus_1] = [
> + #(#test_cases,)*
> + ::kernel::kunit::kunit_case_null(),
> + ];
> + });
> + processed_items.push(parse_quote! {
> + ::kernel::kunit_unsafe_test_suite!(#test_suite, TEST_CASES);
> + });
> +
> + module.content = Some((module_brace, processed_items));
> + Ok(module.to_token_stream())
> }
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index bb2dfd4a4dafc..9cfac9fce0d36 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
> @@ -453,6 +453,8 @@ pub fn paste(input: TokenStream) -> TokenStream {
> /// }
> /// ```
> #[proc_macro_attribute]
> -pub fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> - kunit::kunit_tests(attr.into(), ts.into()).into()
> +pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream {
> + kunit::kunit_tests(parse_macro_input!(attr), parse_macro_input!(input))
> + .unwrap_or_else(|e| e.into_compile_error())
> + .into()
> }
> --
> 2.51.2
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value
2025-12-11 18:56 ` [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
2025-12-16 12:48 ` Benno Lossin
@ 2026-01-04 23:43 ` Tamir Duberstein
1 sibling, 0 replies; 9+ messages in thread
From: Tamir Duberstein @ 2026-01-04 23:43 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Brendan Higgins, David Gow, Rae Moar, rust-for-linux,
linux-kselftest, kunit-dev, linux-kernel
On Thu, Dec 11, 2025 at 2:36 PM Gary Guo <gary@kernel.org> wrote:
>
> From: Gary Guo <gary@garyguo.net>
>
> The last null element can be created (constly) using `pin_init::zeroed`,
> so prefer to use it instead of adding a custom way of building it.
>
> Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Tamir Duberstein <tamird@gmail.com>
> ---
> rust/kernel/kunit.rs | 26 +-------------------------
> rust/macros/kunit.rs | 4 ++--
> 2 files changed, 3 insertions(+), 27 deletions(-)
>
> diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
> index 79436509dd73d..034158cdaf06b 100644
> --- a/rust/kernel/kunit.rs
> +++ b/rust/kernel/kunit.rs
> @@ -192,9 +192,6 @@ pub fn is_test_result_ok(t: impl TestResult) -> bool {
> }
>
> /// 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,
> @@ -215,27 +212,6 @@ pub const fn kunit_case(
> }
> }
>
> -/// 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(),
> - param_init: None,
> - param_exit: None,
> - }
> -}
> -
> /// Registers a KUnit test suite.
> ///
> /// # Safety
> @@ -254,7 +230,7 @@ pub const fn kunit_case_null() -> kernel::bindings::kunit_case {
> ///
> /// 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(),
> +/// pin_init::zeroed(),
> /// ];
> /// kernel::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
> /// ```
> diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
> index 516219f5b1356..fd2cfabfaef76 100644
> --- a/rust/macros/kunit.rs
> +++ b/rust/macros/kunit.rs
> @@ -74,7 +74,7 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<Toke
> // 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(),
> + // ::pin_init::zeroed(),
> // ];
> //
> // ::kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
> @@ -159,7 +159,7 @@ macro_rules! assert_eq {
> processed_items.push(parse_quote! {
> static mut TEST_CASES: [::kernel::bindings::kunit_case; #num_tests_plus_1] = [
> #(#test_cases,)*
> - ::kernel::kunit::kunit_case_null(),
> + ::pin_init::zeroed(),
> ];
> });
> processed_items.push(parse_quote! {
> --
> 2.51.2
>
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 08/11] rust: macros: convert `#[kunit_tests]` macro to use `syn`
2026-01-04 23:38 ` Tamir Duberstein
@ 2026-01-05 2:28 ` Gary Guo
0 siblings, 0 replies; 9+ messages in thread
From: Gary Guo @ 2026-01-05 2:28 UTC (permalink / raw)
To: Tamir Duberstein
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Brendan Higgins, David Gow, Rae Moar, Guilherme Giacomo Simoes,
Patrick Miller, José Expósito, rust-for-linux,
linux-kselftest, kunit-dev, linux-kernel
On Sun, 4 Jan 2026 18:38:53 -0500
Tamir Duberstein <tamird@gmail.com> wrote:
> On Thu, Dec 11, 2025 at 2:30 PM Gary Guo <gary@kernel.org> wrote:
> >
> > From: Gary Guo <gary@garyguo.net>
> >
> > This allows significant cleanups.
> >
> > Signed-off-by: Gary Guo <gary@garyguo.net>
> > ---
> > rust/macros/kunit.rs | 274 +++++++++++++++++++------------------------
> > rust/macros/lib.rs | 6 +-
> > 2 files changed, 123 insertions(+), 157 deletions(-)
> >
> > diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
> > index 7427c17ee5f5c..516219f5b1356 100644
> > --- a/rust/macros/kunit.rs
> > +++ b/rust/macros/kunit.rs
> > ...
> > + // Make the entire module gated behind `CONFIG_KUNIT`.
> > + module
> > + .attrs
> > + .insert(0, parse_quote!(#[cfg(CONFIG_KUNIT="y")]));
>
> Does this need to be the first attribute? I think it can just be
> pushed to the end.
This is the current behaviour, and I think it should be the first.
Other attributes shouldn't need to be processed if kunit is not enabled.
>
>
> >
> > - // Add `#[cfg(CONFIG_KUNIT="y")]` before the module declaration.
> > - let config_kunit = "#[cfg(CONFIG_KUNIT=\"y\")]".to_owned().parse().unwrap();
> > - tokens.insert(
> > - 0,
> > - TokenTree::Group(Group::new(Delimiter::None, config_kunit)),
> > - );
> > + let mut processed_items = Vec::new();
> > + let mut test_cases = Vec::new();
> >
> > // Generate the test KUnit test suite and a test case for each `#[test]`.
> > + //
> > // The code generated for the following test module:
> > //
> > // ```
> > @@ -110,98 +79,93 @@ pub(crate) fn kunit_tests(attr: TokenStream, ts: TokenStream) -> TokenStream {
> > //
> > // ::kernel::kunit_unsafe_test_suite!(kunit_test_suit_name, TEST_CASES);
> > // ```
> > - let mut kunit_macros = "".to_owned();
> > - let mut test_cases = "".to_owned();
> > - let mut assert_macros = "".to_owned();
> > - let path = crate::helpers::file();
> > - let num_tests = tests.len();
> > - for (test, cfg_attr) in tests {
> > - let kunit_wrapper_fn_name = format!("kunit_rust_wrapper_{test}");
> > - // Append any `cfg` attributes the user might have written on their tests so we don't
> > - // attempt to call them when they are `cfg`'d out. An extra `use` is used here to reduce
> > - // the length of the assert message.
> > - let kunit_wrapper = format!(
> > - r#"unsafe extern "C" fn {kunit_wrapper_fn_name}(_test: *mut ::kernel::bindings::kunit)
> > - {{
> > - (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SKIPPED;
> > - {cfg_attr} {{
> > - (*_test).status = ::kernel::bindings::kunit_status_KUNIT_SUCCESS;
> > - use ::kernel::kunit::is_test_result_ok;
> > - assert!(is_test_result_ok({test}()));
> > + //
> > + // Non-function items (e.g. imports) are preserved.
> > + for item in module_items {
> > + let Item::Fn(mut f) = item else {
> > + processed_items.push(item);
> > + continue;
> > + };
> > +
> > + // TODO: Replace below with `extract_if` when MSRV is bumped above 1.85.
> > + // Remove `#[test]` attributes applied on the function and count if any.
>
> What does "count if any" mean here?
Sorry, I think the last half of the sentence was missing. I meant
"count if any is removed".
>
> > + if !f.attrs.iter().any(|attr| attr.path().is_ident("test")) {
> > + processed_items.push(Item::Fn(f));
> > + continue;
> > + }
> > + f.attrs.retain(|attr| !attr.path().is_ident("test"));
>
> Can this code be something like this:
>
> let before = f.attrs.len();
> f.attrs.retain(|attr| !attr.path().is_ident("test"));
> let after = f.attrs.len();
>
> if after == before {
> processed_items.push(Item::Fn(f));
> continue;
> }
Indeed looks better.
>
> > +
> > + let test = f.sig.ident.clone();
> > +
> > + // Retrieve `#[cfg]` applied on the function which needs to be present on derived items too.
> > + let cfg_attrs: Vec<_> = f
> > + .attrs
> > + .iter()
> > + .filter(|attr| attr.path().is_ident("cfg"))
> > + .cloned()
> > + .collect();
> > +
> > + // Before the test, override usual `assert!` and `assert_eq!` macros with ones that call
> > + // KUnit instead.
> > + let test_str = test.to_string();
> > + let path = crate::helpers::file();
> > + processed_items.push(parse_quote! {
> > + #[allow(unused)]
> > + macro_rules! assert {
> > + ($cond:expr $(,)?) => {{
> > + kernel::kunit_assert!(#test_str, #path, 0, $cond);
> > + }}
> > + }
> > + });
> > + processed_items.push(parse_quote! {
> > + #[allow(unused)]
> > + macro_rules! assert_eq {
> > + ($left:expr, $right:expr $(,)?) => {{
> > + kernel::kunit_assert_eq!(#test_str, #path, 0, $left, $right);
> > }}
> > - }}"#,
> > + }
> > + });
>
> Am I reading this right that the macros will be repeatedly redefined
> before each test? Could we put them inside each test body instead?
Indeed. The `test_str` is different for each test so this needs to be
redefined for each test. I experimented about better approaches but
none of them are too satisfactory. I think the existing approach is not
unreasonable.
Putting them into test body sounds more reasonable (I suppose the
existing impl doesn't want to parse functions), but I'll leave it to
the future as this commit is focusing on converting with minimal
behavioural change.
Best,
Gary
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-01-05 2:28 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20251211185805.2835633-1-gary@kernel.org>
2025-12-11 18:56 ` [PATCH 02/11] rust: macros: use `quote!` from vendored crate Gary Guo
2025-12-16 9:47 ` Benno Lossin
2026-01-04 13:39 ` Tamir Duberstein
2025-12-11 18:56 ` [PATCH 08/11] rust: macros: convert `#[kunit_tests]` macro to use `syn` Gary Guo
2026-01-04 23:38 ` Tamir Duberstein
2026-01-05 2:28 ` Gary Guo
2025-12-11 18:56 ` [PATCH 11/11] rust: kunit: use `pin_init::zeroed` instead of custom null value Gary Guo
2025-12-16 12:48 ` Benno Lossin
2026-01-04 23:43 ` Tamir Duberstein
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox