mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	The KUnit `#[test]` support that landed recently is very basic and does
not map the `assert*!` macros into KUnit like the doctests do, so they
panic at the moment.
Thus implement the custom mapping in a similar way to doctests, reusing
the infrastructure there.
In Rust 1.88.0, the `file()` method in `Span` may be stable [1]. However,
it was changed recently (from `SourceFile`), so we need to do something
different in previous versions. Thus create a helper for it and use it
to get the path.
With this, a failing test suite like:
    #[kunit_tests(my_test_suite)]
    mod tests {
        use super::*;
        #[test]
        fn my_first_test() {
            assert_eq!(42, 43);
        }
        #[test]
        fn my_second_test() {
            assert!(42 >= 43);
        }
    }
will properly map back to KUnit, printing something like:
    [    1.924325]     KTAP version 1
    [    1.924421]     # Subtest: my_test_suite
    [    1.924506]     # speed: normal
    [    1.924525]     1..2
    [    1.926385]     # my_first_test: ASSERTION FAILED at rust/kernel/lib.rs:251
    [    1.926385]     Expected 42 == 43 to be true, but is false
    [    1.928026]     # my_first_test.speed: normal
    [    1.928075]     not ok 1 my_first_test
    [    1.928723]     # my_second_test: ASSERTION FAILED at rust/kernel/lib.rs:256
    [    1.928723]     Expected 42 >= 43 to be true, but is false
    [    1.929834]     # my_second_test.speed: normal
    [    1.929868]     not ok 2 my_second_test
    [    1.930032] # my_test_suite: pass:0 fail:2 skip:0 total:2
    [    1.930153] # Totals: pass:0 fail:2 skip:0 total
Link: https://github.com/rust-lang/rust/pull/140514 [1]
Reviewed-by: David Gow <davidgow@google.com>
Acked-by: Danilo Krummrich <dakr@kernel.org>
Link: https://lore.kernel.org/r/20250502215133.1923676-2-ojeda@kernel.org
[ Required `KUNIT=y` like for doctests. Used the `cfg_attr` from the
  TODO comment and clarified its comment now that the stabilization is
  in beta and thus quite likely stable in Rust 1.88.0. Simplified the
  `new_body` code by introducing a new variable. Added
  `#[allow(clippy::incompatible_msrv)]`. - Miguel ]
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
		
	
			
		
			
				
	
	
		
			105 lines
		
	
	
	
		
			3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			105 lines
		
	
	
	
		
			3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0
 | 
						|
 | 
						|
use proc_macro::{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() {
 | 
						|
        Some(ident.to_string())
 | 
						|
    } else {
 | 
						|
        None
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option<String> {
 | 
						|
    if let Some(TokenTree::Literal(literal)) = it.next() {
 | 
						|
        Some(literal.to_string())
 | 
						|
    } else {
 | 
						|
        None
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
pub(crate) fn try_string(it: &mut token_stream::IntoIter) -> Option<String> {
 | 
						|
    try_literal(it).and_then(|string| {
 | 
						|
        if string.starts_with('\"') && string.ends_with('\"') {
 | 
						|
            let content = &string[1..string.len() - 1];
 | 
						|
            if content.contains('\\') {
 | 
						|
                panic!("Escape sequences in string literals not yet handled");
 | 
						|
            }
 | 
						|
            Some(content.to_string())
 | 
						|
        } else if string.starts_with("r\"") {
 | 
						|
            panic!("Raw string literals are not yet handled");
 | 
						|
        } else {
 | 
						|
            None
 | 
						|
        }
 | 
						|
    })
 | 
						|
}
 | 
						|
 | 
						|
pub(crate) fn expect_ident(it: &mut token_stream::IntoIter) -> String {
 | 
						|
    try_ident(it).expect("Expected Ident")
 | 
						|
}
 | 
						|
 | 
						|
pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
 | 
						|
    if let TokenTree::Punct(punct) = it.next().expect("Reached end of token stream for Punct") {
 | 
						|
        punct.as_char()
 | 
						|
    } else {
 | 
						|
        panic!("Expected Punct");
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
pub(crate) fn expect_string(it: &mut token_stream::IntoIter) -> String {
 | 
						|
    try_string(it).expect("Expected string")
 | 
						|
}
 | 
						|
 | 
						|
pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> String {
 | 
						|
    let string = try_string(it).expect("Expected string");
 | 
						|
    assert!(string.is_ascii(), "Expected ASCII string");
 | 
						|
    string
 | 
						|
}
 | 
						|
 | 
						|
pub(crate) fn expect_group(it: &mut token_stream::IntoIter) -> Group {
 | 
						|
    if let TokenTree::Group(group) = it.next().expect("Reached end of token stream for Group") {
 | 
						|
        group
 | 
						|
    } else {
 | 
						|
        panic!("Expected Group");
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
pub(crate) fn expect_end(it: &mut token_stream::IntoIter) {
 | 
						|
    if it.next().is_some() {
 | 
						|
        panic!("Expected end");
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/// Given a function declaration, finds the name of the function.
 | 
						|
pub(crate) fn function_name(input: TokenStream) -> Option<Ident> {
 | 
						|
    let mut input = input.into_iter();
 | 
						|
    while let Some(token) = input.next() {
 | 
						|
        match token {
 | 
						|
            TokenTree::Ident(i) if i.to_string() == "fn" => {
 | 
						|
                if let Some(TokenTree::Ident(i)) = input.next() {
 | 
						|
                    return Some(i);
 | 
						|
                }
 | 
						|
                return None;
 | 
						|
            }
 | 
						|
            _ => continue,
 | 
						|
        }
 | 
						|
    }
 | 
						|
    None
 | 
						|
}
 | 
						|
 | 
						|
pub(crate) fn file() -> String {
 | 
						|
    #[cfg(not(CONFIG_RUSTC_HAS_SPAN_FILE))]
 | 
						|
    {
 | 
						|
        proc_macro::Span::call_site()
 | 
						|
            .source_file()
 | 
						|
            .path()
 | 
						|
            .to_string_lossy()
 | 
						|
            .into_owned()
 | 
						|
    }
 | 
						|
 | 
						|
    #[cfg(CONFIG_RUSTC_HAS_SPAN_FILE)]
 | 
						|
    #[allow(clippy::incompatible_msrv)]
 | 
						|
    {
 | 
						|
        proc_macro::Span::call_site().file()
 | 
						|
    }
 | 
						|
}
 |