[PATCH v2 0/3] rust: doctest: unify with other kunit tests

0 views
Skip to first unread message

Gary Guo

unread,
Sep 2, 2026, 6:00:15 PM (9 days ago) Sep 2
to Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein, Alexandre Courbot, Onur Özkan, Brendan Higgins, David Gow, Rae Moar, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, linux-k...@vger.kernel.org, kuni...@googlegroups.com, Gary Guo
Currently in Rust, kunit tests are handled with `kunit_tests`, while the
doctests generate a lot `extern "C"` functions and invoke them through a
generated C wrapper.

This is currently needed because of the special treatment of file name and
line numbers from doctests. Convert them to override `file!()` and
`line!()` macro instead, and use them inside `kunit_assert!`.

With this it is possible to just generate `#[kunit_tests]` and `#[test]`
for doctests.

David mentioned in [1] that this should route via the Rust tree.

Link: https://lore.kernel.org/rust-for-linux/e30d16ec-f5a3-4cd8...@davidgow.net/ [1]

Signed-off-by: Gary Guo <ga...@garyguo.net>
---
Changes in v2:
- Rebased on v7.3-rc1
- Picked up tags
- Link to v1: https://patch.msgid.link/20260616-kunit-v...@garyguo.net

---
Gary Guo (3):
rust: kunit: use `file!()` inside `kunit_assert!`
rust: kunit: use `line!()` inside `kunit_assert!`
rust: doctest: generate Rust kunit test suites

Documentation/rust/general-information.rst | 4 +-
init/Kconfig | 3 --
rust/Makefile | 4 +-
rust/kernel/kunit.rs | 11 +++--
rust/kernel/str.rs | 2 +-
rust/macros/helpers.rs | 16 -------
rust/macros/kunit.rs | 5 +-
rust/macros/lib.rs | 5 --
scripts/rustdoc_test_gen.rs | 73 ++++++++----------------------
9 files changed, 30 insertions(+), 93 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260616-kunit-348f2c029b1e

Best regards,
--
Gary Guo <ga...@garyguo.net>

Gary Guo

unread,
Sep 2, 2026, 6:00:16 PM (9 days ago) Sep 2
to Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein, Alexandre Courbot, Onur Özkan, Brendan Higgins, David Gow, Rae Moar, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, linux-k...@vger.kernel.org, kuni...@googlegroups.com, Gary Guo
The `diff` parameter is needed currently because doctests want to override
the line number. Simplify it by changing it to use `line!()`. Have doctests
override `line!()` macro to achieve the current behavior.

A few current doctests (or their invoked macros) require `line!()` to
expand to literal; they're updated to use `::core::line!()` instead.

Reviewed-by: David Gow <da...@davidgow.net>
Signed-off-by: Gary Guo <ga...@garyguo.net>
---
rust/kernel/kunit.rs | 8 ++++----
rust/kernel/str.rs | 2 +-
rust/macros/kunit.rs | 4 ++--
scripts/rustdoc_test_gen.rs | 16 ++++++++++++----
4 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
index 12084873b51e..27b109923407 100644
--- a/rust/kernel/kunit.rs
+++ b/rust/kernel/kunit.rs
@@ -58,7 +58,7 @@ pub fn info(args: fmt::Arguments<'_>) {
#[doc(hidden)]
#[macro_export]
macro_rules! kunit_assert {
- ($name:literal, $diff:expr, $condition:expr $(,)?) => {
+ ($name:literal, $condition:expr $(,)?) => {
'out: {
// Do nothing if the condition is `true`.
if $condition {
@@ -67,7 +67,7 @@ macro_rules! kunit_assert {

// Use `file!()` instead of `::core::file!()` here so it can be overridden.
static FILE: &'static $crate::str::CStr = $crate::c_str!(file!());
- static LINE: i32 = ::core::line!() as i32 - $diff;
+ static LINE: i32 = line!() as i32;
static CONDITION: &'static $crate::str::CStr = $crate::c_str!(stringify!($condition));

// SAFETY: FFI call without safety requirements.
@@ -165,10 +165,10 @@ unsafe impl Sync for UnaryAssert {}
#[doc(hidden)]
#[macro_export]
macro_rules! kunit_assert_eq {
- ($name:literal, $diff:expr, $left:expr, $right:expr $(,)?) => {{
+ ($name:literal, $left:expr, $right:expr $(,)?) => {{
// For the moment, we just forward to the expression assert because, for binary asserts,
// KUnit supports only a few types (e.g. integers).
- $crate::kunit_assert!($name, $diff, $left == $right);
+ $crate::kunit_assert!($name, $left == $right);
}};
}

diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index b3caa9a1c898..644b4279a116 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -411,7 +411,7 @@ fn as_ref(&self) -> &BStr {
/// const BAD: &CStr = c_str!("literal");
///
/// // `c_str!` is still needed for static non-literal C strings.
-/// const GOOD: &CStr = c_str!(concat!(file!(), ":", line!(), ": My CStr!"));
+/// const GOOD: &CStr = c_str!(concat!(file!(), ":", ::core::line!(), ": My CStr!"));
/// ```
#[macro_export]
macro_rules! c_str {
diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
index 936eff014870..e9152b9d51f9 100644
--- a/rust/macros/kunit.rs
+++ b/rust/macros/kunit.rs
@@ -113,7 +113,7 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result<Toke
#[allow(unused)]
macro_rules! assert {
($cond:expr $(,)?) => {{
- kernel::kunit_assert!(#test_str, 0, $cond);
+ kernel::kunit_assert!(#test_str, $cond);
}}
}
});
@@ -121,7 +121,7 @@ macro_rules! assert {
#[allow(unused)]
macro_rules! assert_eq {
($left:expr, $right:expr $(,)?) => {{
- kernel::kunit_assert_eq!(#test_str, 0, $left, $right);
+ kernel::kunit_assert_eq!(#test_str, $left, $right);
}}
}
});
diff --git a/scripts/rustdoc_test_gen.rs b/scripts/rustdoc_test_gen.rs
index bb57745ec339..bb870f83dde2 100644
--- a/scripts/rustdoc_test_gen.rs
+++ b/scripts/rustdoc_test_gen.rs
@@ -176,18 +176,26 @@ fn main() {
r#"/// Generated `{name}` KUnit test case from a Rust documentation test.
#[no_mangle]
pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{
- /// Overrides the usual [`file!`] macro with one that expands to the real path.
+ // Overrides the usual [`file!`] macro with one that expands to the real path.
#[allow(unused)]
macro_rules! file {{
() => {{ "{real_path}" }}
}}

+ // Overrides the usual [`line!`] macro with one that expands to the real line number.
+ #[allow(unused)]
+ macro_rules! line {{
+ // NOTE: This does not expand to a literal, but a constant expression.
+ // Therefore code that depends on `line!()` being overrideable needs special adjustment.
+ () => {{ const {{ ::core::line!() - __DOCTEST_ANCHOR + {line} }} }}
+ }}
+
/// Overrides the usual [`assert!`] macro with one that calls KUnit instead.
#[allow(unused)]
macro_rules! assert {{
($cond:expr $(,)?) => {{{{
::kernel::kunit_assert!(
- "{kunit_name}", __DOCTEST_ANCHOR - {line}, $cond
+ "{kunit_name}", $cond
);
}}}}
}}
@@ -197,7 +205,7 @@ macro_rules! assert {{
macro_rules! assert_eq {{
($left:expr, $right:expr $(,)?) => {{{{
::kernel::kunit_assert_eq!(
- "{kunit_name}", __DOCTEST_ANCHOR - {line}, $left, $right
+ "{kunit_name}", $left, $right
);
}}}}
}}
@@ -219,7 +227,7 @@ macro_rules! assert_eq {{

/// The anchor where the test code body starts.
#[allow(unused)]
- static __DOCTEST_ANCHOR: i32 = ::core::line!() as i32 + {body_offset} + 2;
+ static __DOCTEST_ANCHOR: u32 = ::core::line!() + {body_offset} + 2;
{{
#![allow(unreachable_pub, clippy::disallowed_names)]
{body}

--
2.54.0

Gary Guo

unread,
Sep 2, 2026, 6:00:16 PM (9 days ago) Sep 2
to Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein, Alexandre Courbot, Onur Özkan, Brendan Higgins, David Gow, Rae Moar, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, linux-k...@vger.kernel.org, kuni...@googlegroups.com, Gary Guo
For doctest, instead of generating C FFI functions, generate a Rust test
suite with `#[kunit_tests]` and `#[test]` attributes. This makes the C glue
no longer needed.

Reviewed-by: David Gow <da...@davidgow.net>
Signed-off-by: Gary Guo <ga...@garyguo.net>
---
rust/Makefile | 4 +--
scripts/rustdoc_test_gen.rs | 69 ++++++---------------------------------------
2 files changed, 10 insertions(+), 63 deletions(-)

diff --git a/rust/Makefile b/rust/Makefile
index da1a7409d984..ec8c08ee3263 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -39,10 +39,8 @@ obj-$(CONFIG_RUST) += exports.o
always-$(CONFIG_RUST) += host/libproc_macro2.rlib host/libquote.rlib host/libsyn.rlib

always-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.rs
-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated_kunit.c

obj-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.o
-obj-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated_kunit.o

always-$(subst y,$(CONFIG_RUST),$(CONFIG_JUMP_LABEL)) += kernel/generated_arch_static_branch_asm.rs
ifndef CONFIG_UML
@@ -400,7 +398,7 @@ quiet_cmd_rustdoc_test_kernel = RUSTDOC TK $<
$< $(rustdoc_test_kernel_quiet); \
$(objtree)/scripts/rustdoc_test_gen

-%/doctests_kernel_generated.rs %/doctests_kernel_generated_kunit.c: \
+%/doctests_kernel_generated.rs: \
$(src)/kernel/lib.rs $(obj)/kernel.o \
$(objtree)/scripts/rustdoc_test_builder \
$(objtree)/scripts/rustdoc_test_gen FORCE
diff --git a/scripts/rustdoc_test_gen.rs b/scripts/rustdoc_test_gen.rs
index bb870f83dde2..068ecdb0845d 100644
--- a/scripts/rustdoc_test_gen.rs
+++ b/scripts/rustdoc_test_gen.rs
@@ -125,9 +125,7 @@ fn main() {
// Sort paths.
paths.sort();

- let mut rust_tests = String::new();
- let mut c_test_declarations = String::new();
- let mut c_test_cases = String::new();
+ let mut tests = String::new();
let mut body = String::new();
let mut last_file = String::new();
let mut number = 0;
@@ -172,10 +170,10 @@ fn main() {

use std::fmt::Write;
write!(
- rust_tests,
+ tests,
r#"/// Generated `{name}` KUnit test case from a Rust documentation test.
-#[no_mangle]
-pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{
+#[test]
+fn {kunit_name}() {{
// Overrides the usual [`file!`] macro with one that expands to the real path.
#[allow(unused)]
macro_rules! file {{
@@ -190,26 +188,6 @@ macro_rules! line {{
() => {{ const {{ ::core::line!() - __DOCTEST_ANCHOR + {line} }} }}
}}

- /// Overrides the usual [`assert!`] macro with one that calls KUnit instead.
- #[allow(unused)]
- macro_rules! assert {{
- ($cond:expr $(,)?) => {{{{
- ::kernel::kunit_assert!(
- "{kunit_name}", $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!(
- "{kunit_name}", $left, $right
- );
- }}}}
- }}
-
// Many tests need the prelude, so provide it by default.
#[allow(unused)]
use ::kernel::prelude::*;
@@ -238,14 +216,9 @@ macro_rules! assert_eq {{
"#
)
.unwrap();
-
- write!(c_test_declarations, "void {kunit_name}(struct kunit *);\n").unwrap();
- write!(c_test_cases, " KUNIT_CASE({kunit_name}),\n").unwrap();
}

- let rust_tests = rust_tests.trim();
- let c_test_declarations = c_test_declarations.trim();
- let c_test_cases = c_test_cases.trim();
+ let tests = tests.trim();

write!(
BufWriter::new(File::create("rust/doctests_kernel_generated.rs").unwrap()),
@@ -271,34 +244,10 @@ impl ModuleMetadata for LocalModule {{
}};
}}

-{rust_tests}
-"#
- )
- .unwrap();
-
- write!(
- BufWriter::new(File::create("rust/doctests_kernel_generated_kunit.c").unwrap()),
- r#"/*
- * `kernel` crate documentation tests.
- */
-
-#include <kunit/test.h>
-
-{c_test_declarations}
-
-static struct kunit_case test_cases[] = {{
- {c_test_cases}
- {{ }}
-}};
-
-static struct kunit_suite test_suite = {{
- .name = "rust_doctests_kernel",
- .test_cases = test_cases,
-}};
-
-kunit_test_suite(test_suite);
-
-MODULE_LICENSE("GPL");
+#[kernel::macros::kunit_tests(rust_doctests_kernel)]
+mod tests {{
+{tests}
+}}
"#
)
.unwrap();

--
2.54.0

David Gow

unread,
Sep 3, 2026, 8:50:49 AM (9 days ago) Sep 3
to Gary Guo, Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein, Alexandre Courbot, Onur Özkan, Brendan Higgins, Rae Moar, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, linux-k...@vger.kernel.org, kuni...@googlegroups.com
Le 03/09/2026 à 5:59 AM, Gary Guo a écrit :
> Currently in Rust, kunit tests are handled with `kunit_tests`, while the
> doctests generate a lot `extern "C"` functions and invoke them through a
> generated C wrapper.
>
> This is currently needed because of the special treatment of file name and
> line numbers from doctests. Convert them to override `file!()` and
> `line!()` macro instead, and use them inside `kunit_assert!`.
>
> With this it is possible to just generate `#[kunit_tests]` and `#[test]`
> for doctests.
>
> David mentioned in [1] that this should route via the Rust tree.
>
> Link: https://lore.kernel.org/rust-for-linux/e30d16ec-f5a3-4cd8...@davidgow.net/ [1]
>
> Signed-off-by: Gary Guo <ga...@garyguo.net>
> ---
> Changes in v2:
> - Rebased on v7.3-rc1
> - Picked up tags
> - Link to v1: https://patch.msgid.link/20260616-kunit-v...@garyguo.net
>
> ---

Thanks again. This still looks good to me.

Cheers,
-- David
Reply all
Reply to author
Forward
0 new messages