On Wed, Oct 30, 2024 at 12:57:14PM +0800, David Gow wrote:
> From: José Expósito
> 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
> Signed-off-by: José Expósito
> [Updated to use new const fn.]
> Signed-off-by: David Gow
> ---
>
> Changes since v2:
> https://lore.kernel.org/linux-kselftest/[email protected]/
> - 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/[email protected]/
> - Rebased on top of rust-next
> - Make use of the new const functions, rather than the kunit_case!()
> macro.
>
> ---
> MAINTAINERS | 1 +
> rust/kernel/kunit.rs | 11
> rust/macros/kunit.rs | 153 +++
> rust/macros/lib.rs | 29
> 4 files changed, 194 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 27bc4139d352..ac296467a552 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.
> @@ -269,3 +271,12 @@ macro_rules! kunit_unsafe_test_suite {
> };
> };
> }
> +
> +#[kunit_tests(rust_kernel_kunit)]
> +mod tests {
> +#[test]
> +fn rust_test_kunit_kunit_tests() {
> +let running = true;
> +assert_eq!(running, true);
> +}
> +}
> diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs
> new file mode 100644
> index ..850d268cc96a
> --- /dev/null
> +++ b/rust/macros/kunit.rs
> @@ -0,0 +1,153 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! Procedural macro to run KUnit tests using a user-space like syntax.
> +//!
> +//! Copyright (c) 2023 José Expósito
> +
> +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.
> +