[PATCH RFC 2/3] rust: kunit: allow same test name with different #[cfg(...)]

From: Nicolás Antinori

Date: Tue Sep 15 2026 - 15:38:14 EST


Sometimes it is necessary to test the same code paths under different
configurations. The `#[cfg(...)]` macro can be used to check if specific
configurations are enabled and run tests accordingly.

Currently, defining multiple tests with the same name under different
`#[cfg(...)]` attributes results in a compilation error. This patch removes
that restriction, allowing identical test names across different
configurations. Additionally, it appends the active configuration to the
test name, ensuring the runner clearly indicates which test executed and
which was skipped.

Signed-off-by: Nicolás Antinori <nico.antinori.7@xxxxxxxxx>
---
rust/kernel/kunit.rs | 15 ++++++++++++
rust/macros/kunit.rs | 57 +++++++++++++++++++++++++++++++++++++++++---
2 files changed, 69 insertions(+), 3 deletions(-)

diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
index 65a1040ee2b0..613c8d2aea78 100644
--- a/rust/kernel/kunit.rs
+++ b/rust/kernel/kunit.rs
@@ -348,6 +348,21 @@ fn rust_test_kunit_in_kunit_test() {
assert!(in_kunit_test());
}

+ // Both tests with cfg have the same name on purpose because we are implicitly testing that
+ // tests with the same name but different configs do not throw a compilation error
+ #[test]
+ #[cfg(CONFIG_RUST_KUNIT_SELFTEST = "y")]
+ fn rust_test_kunit_parse_cfg_in_kunit_test() {
+ assert!(in_kunit_test());
+ }
+
+ #[test]
+ #[cfg(CONFIG_RUST_KUNIT_SELFTEST = "n")]
+ fn rust_test_kunit_parse_cfg_in_kunit_test() {
+ // This test should never run because of the `cfg`.
+ assert!(false)
+ }
+
#[test]
#[should_panic]
fn rust_test_kunit_panic_in_kunit_test() {
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
index 2c6405cebc0a..605d925cd8f0 100644
--- a/rust/macros/kunit.rs
+++ b/rust/macros/kunit.rs
@@ -6,14 +6,19 @@

use std::ffi::CString;

-use proc_macro2::TokenStream;
+use proc_macro2::{
+ TokenStream,
+ TokenTree, //
+};
use quote::{
format_ident,
quote,
ToTokens, //
};
use syn::{
+ parse::ParseStream,
parse_quote,
+ Attribute,
Error,
Ident,
Item,
@@ -22,6 +27,46 @@
Result, //
};

+fn get_cfg_string(attr: &Attribute) -> Result<String> {
+ let mut result = String::from("_cfg");
+ attr.parse_args_with(|input: ParseStream<'_>| {
+ while !input.is_empty() {
+ build_cfg_string(input.parse()?, &mut result)?;
+ }
+ Ok(result)
+ })
+}
+
+fn build_cfg_string(tt: TokenTree, result: &mut String) -> Result<()> {
+ match tt {
+ TokenTree::Ident(ident) => {
+ result.push('_');
+ result.push_str(&ident.to_string().to_lowercase());
+ }
+ TokenTree::Punct(ref punct) => match punct.as_char() {
+ '=' => {
+ result.push_str("_equals");
+ }
+ _ => {
+ return Err(Error::new_spanned(
+ punct,
+ "only \"=\" is allowed to check configurations",
+ ))
+ }
+ },
+ TokenTree::Literal(lit) => {
+ result.push('_');
+ result.push_str(&lit.to_string().trim_matches('"').to_string());
+ }
+ TokenTree::Group(group) => {
+ for group_tt in group.stream() {
+ build_cfg_string(group_tt, result)?;
+ }
+ }
+ }
+ Ok(())
+}
+
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(
@@ -106,6 +151,12 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<Toke
.cloned()
.collect();

+ let cfg_attrs_str = cfg_attrs
+ .iter()
+ .map(get_cfg_string)
+ .collect::<Result<Vec<String>>>()?
+ .join("__");
+
let should_panic = f
.attrs
.iter()
@@ -113,7 +164,7 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<Toke

// Before the test, override usual `assert!` and `assert_eq!` macros with ones that call
// KUnit instead.
- let test_str = test.to_string();
+ let test_str = format!("{test}{cfg_attrs_str}");
let path = CString::new(crate::helpers::file()).expect("file path cannot contain NUL");
processed_items.push(parse_quote! {
#[allow(unused)]
@@ -135,7 +186,7 @@ macro_rules! assert_eq {
// Add back the test item.
processed_items.push(Item::Fn(f));

- let kunit_wrapper_fn_name = format_ident!("kunit_rust_wrapper_{test}");
+ let kunit_wrapper_fn_name = format_ident!("kunit_rust_wrapper_{test}{cfg_attrs_str}");
let test_cstr = LitCStr::new(
&CString::new(test_str.as_str()).expect("identifier cannot contain NUL"),
test.span(),
--
2.47.3