From ffa1f6f537056b52d8eaab1213baa92ca0ab4968 Mon Sep 17 00:00:00 2001 From: Kezhu Wang Date: Wed, 17 Apr 2024 20:19:39 +0800 Subject: [PATCH] fix: improve compatibility among test proc macros This pr proposes a generic mechanism among different test proc macros to avoid to generate multiple `[::core::prelude::v1::test]` on test method. `proc_macro_attribute` function is fed with tokens after its attribute and no tokens before it. Give the above, this pr proposes test proc macros to append newly generated macros after existing ones. This way, proc macros processed later can read all macros including generated and handwritten and make further decisions. Specifically, proc macros can append `#[::core::prelude::v1::test]` only if it does not exist. Macros that transform test method signature can append `#[::core::prelude::v1::test]` directly without checking its existence once they generate valid signature for test method. Closes #101, #146. --- crates/test-case-core/src/test_case.rs | 40 ++++++++++++++++++- crates/test-case-macros/src/lib.rs | 2 +- tests/acceptance_tests.rs | 2 +- ...ceptance__matrices_compilation_errors.snap | 2 +- ...eatures_produce_human_readable_errors.snap | 2 +- ...ceptance__matrices_compilation_errors.snap | 2 +- 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/crates/test-case-core/src/test_case.rs b/crates/test-case-core/src/test_case.rs index 376b14e..b9c95ef 100644 --- a/crates/test-case-core/src/test_case.rs +++ b/crates/test-case-core/src/test_case.rs @@ -5,7 +5,38 @@ use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; use quote::quote; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::{parse_quote, Error, Expr, Ident, ItemFn, ReturnType, Token}; +use syn::{parse_quote, Attribute, Error, Expr, Ident, ItemFn, ReturnType, Token}; + +// Check whether given attribute is a test attribute of forms: +// * `#[test]` +// * `#[core::prelude::*::test]` or `#[::core::prelude::*::test]` +// * `#[std::prelude::*::test]` or `#[::std::prelude::*::test]` +fn is_test_attribute(attr: &Attribute) -> bool { + let path = match &attr.meta { + syn::Meta::Path(path) => path, + _ => return false, + }; + const CANDIDATES_LEN: usize = 4; + + let candidates: [[&str; CANDIDATES_LEN]; 2] = [ + ["core", "prelude", "*", "test"], + ["std", "prelude", "*", "test"], + ]; + if path.leading_colon.is_none() + && path.segments.len() == 1 + && path.segments[0].arguments.is_none() + && path.segments[0].ident == "test" + { + return true; + } else if path.segments.len() != candidates[0].len() { + return false; + } + candidates.into_iter().any(|segments| { + path.segments.iter().zip(segments).all(|(segment, path)| { + segment.arguments.is_none() && (path == "*" || segment.ident == path) + }) + }) +} #[derive(Debug)] pub struct TestCase { @@ -99,7 +130,12 @@ impl TestCase { quote! { let _result = super::#item_name(#(#arg_values),*).await; }, ) } else { - attrs.insert(0, parse_quote! { #[::core::prelude::v1::test] }); + let qualified_test_attr = parse_quote! { #[::core::prelude::v1::test] }; + if let Some(attr) = attrs.iter().find(|attr| is_test_attribute(attr)) { + let msg = "second test attribute is supplied, consider removing or changing the order of your test attributes"; + return Error::new_spanned(attr, msg).into_compile_error(); + } + attrs.push(qualified_test_attr); ( TokenStream2::new(), quote! { let _result = super::#item_name(#(#arg_values),*); }, diff --git a/crates/test-case-macros/src/lib.rs b/crates/test-case-macros/src/lib.rs index 90d4675..940725c 100644 --- a/crates/test-case-macros/src/lib.rs +++ b/crates/test-case-macros/src/lib.rs @@ -110,7 +110,7 @@ fn expand_additional_test_case_macros(item: &mut ItemFn) -> syn::Result { diff --git a/tests/snapshots/rust-nightly/acceptance__matrices_compilation_errors.snap b/tests/snapshots/rust-nightly/acceptance__matrices_compilation_errors.snap index 2617d4f..2a83f9c 100644 --- a/tests/snapshots/rust-nightly/acceptance__matrices_compilation_errors.snap +++ b/tests/snapshots/rust-nightly/acceptance__matrices_compilation_errors.snap @@ -5,6 +5,6 @@ expression: output error: All literal values must be of the same type error: Range bounds can only be an integer literal error: Unbounded ranges are not supported -error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors +error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors; 1 warning emitted error: number too large to fit in target type error[E0308]: mismatched types diff --git a/tests/snapshots/rust-stable/acceptance__features_produce_human_readable_errors.snap b/tests/snapshots/rust-stable/acceptance__features_produce_human_readable_errors.snap index 2646115..2b5a76f 100644 --- a/tests/snapshots/rust-stable/acceptance__features_produce_human_readable_errors.snap +++ b/tests/snapshots/rust-stable/acceptance__features_produce_human_readable_errors.snap @@ -3,4 +3,4 @@ source: tests/acceptance_tests.rs expression: output --- error: 'with-regex' feature is required to use 'matches-regex' keyword -error: could not compile `features_produce_human_readable_errors` (lib test) due to previous error +error: could not compile `features_produce_human_readable_errors` (lib test) due to 1 previous error diff --git a/tests/snapshots/rust-stable/acceptance__matrices_compilation_errors.snap b/tests/snapshots/rust-stable/acceptance__matrices_compilation_errors.snap index 2617d4f..2a83f9c 100644 --- a/tests/snapshots/rust-stable/acceptance__matrices_compilation_errors.snap +++ b/tests/snapshots/rust-stable/acceptance__matrices_compilation_errors.snap @@ -5,6 +5,6 @@ expression: output error: All literal values must be of the same type error: Range bounds can only be an integer literal error: Unbounded ranges are not supported -error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors +error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors; 1 warning emitted error: number too large to fit in target type error[E0308]: mismatched types