Re: [PATCH v3 2/3] rust: macros: add macro to easily run KUnit tests

2024-10-31 Thread David Gow
On Wed, 30 Oct 2024 at 13:11, Boqun Feng  wrote:
>
> 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
> >

(...snip...)

> > +let new_body: TokenStream = vec![body.stream(), 
> > kunit_macros.parse().unwrap()]
> > +.into_iter()
> > +.collect();
> > +
> > +// Remove the `#[test]` macros.
> > +let new_body = new_body.to_string().replace("#[test]", "");
>
> Yeah, I want to see how you do it this time ;-) So if you do a
> `.to_string()` on a `TokenStream`, you lose all the span [1] information
> ("span information" is a term invited by me, hope I get it right ;-))
> e.g. if there is a compile error in the test code, the compiler cannot
> report the exact line of the error, it can only report there is an
> error.
>
> Last time I find how to preserve the Span:
>
> 
> https://lore.kernel.org/rust-for-linux/ZMba0_XXZuTgWyWY@boqun-archlinux/
>
> Hope it helps!
>
> [1]: https://doc.rust-lang.org/proc_macro/struct.Span.html
>
> Regards,
> Boqun

Thanks. I managed to get this working, but just ended up with an
uglier version of your change, so I've copied it in and added you as a
co-developer for this patch in v4.

It made clippy catch a couple of warnings in the example tests, too,
so it's clearly working well.

Cheers,
-- David


smime.p7s
Description: S/MIME Cryptographic Signature


Re: [PATCH v3 2/3] rust: macros: add macro to easily run KUnit tests

2024-10-29 Thread Boqun Feng
On Tue, Oct 29, 2024 at 10:11:38PM -0700, Boqun Feng wrote:
[...]
> > +
> > +let new_body: TokenStream = vec![body.stream(), 
> > kunit_macros.parse().unwrap()]
> > +.into_iter()
> > +.collect();
> > +
> > +// Remove the `#[test]` macros.
> > +let new_body = new_body.to_string().replace("#[test]", "");
> 
> Yeah, I want to see how you do it this time ;-) So if you do a
> `.to_string()` on a `TokenStream`, you lose all the span [1] information
> ("span information" is a term invited by me, hope I get it right ;-))

Not important: I meant I am not a procdure macro expert, hope "span
information" is what is used when discussing span preservation ;-)

Regards,
Boqun

> e.g. if there is a compile error in the test code, the compiler cannot
> report the exact line of the error, it can only report there is an
> error.
> 
[...]



Re: [PATCH v3 2/3] rust: macros: add macro to easily run KUnit tests

2024-10-29 Thread Boqun Feng
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.
> +