Skip to content

Commit ffa1f6f

Browse files
committed
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.
1 parent 39526d4 commit ffa1f6f

File tree

6 files changed

+43
-7
lines changed

6 files changed

+43
-7
lines changed

crates/test-case-core/src/test_case.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,38 @@ use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
55
use quote::quote;
66
use syn::parse::{Parse, ParseStream};
77
use syn::punctuated::Punctuated;
8-
use syn::{parse_quote, Error, Expr, Ident, ItemFn, ReturnType, Token};
8+
use syn::{parse_quote, Attribute, Error, Expr, Ident, ItemFn, ReturnType, Token};
9+
10+
// Check whether given attribute is a test attribute of forms:
11+
// * `#[test]`
12+
// * `#[core::prelude::*::test]` or `#[::core::prelude::*::test]`
13+
// * `#[std::prelude::*::test]` or `#[::std::prelude::*::test]`
14+
fn is_test_attribute(attr: &Attribute) -> bool {
15+
let path = match &attr.meta {
16+
syn::Meta::Path(path) => path,
17+
_ => return false,
18+
};
19+
const CANDIDATES_LEN: usize = 4;
20+
21+
let candidates: [[&str; CANDIDATES_LEN]; 2] = [
22+
["core", "prelude", "*", "test"],
23+
["std", "prelude", "*", "test"],
24+
];
25+
if path.leading_colon.is_none()
26+
&& path.segments.len() == 1
27+
&& path.segments[0].arguments.is_none()
28+
&& path.segments[0].ident == "test"
29+
{
30+
return true;
31+
} else if path.segments.len() != candidates[0].len() {
32+
return false;
33+
}
34+
candidates.into_iter().any(|segments| {
35+
path.segments.iter().zip(segments).all(|(segment, path)| {
36+
segment.arguments.is_none() && (path == "*" || segment.ident == path)
37+
})
38+
})
39+
}
940

1041
#[derive(Debug)]
1142
pub struct TestCase {
@@ -99,7 +130,12 @@ impl TestCase {
99130
quote! { let _result = super::#item_name(#(#arg_values),*).await; },
100131
)
101132
} else {
102-
attrs.insert(0, parse_quote! { #[::core::prelude::v1::test] });
133+
let qualified_test_attr = parse_quote! { #[::core::prelude::v1::test] };
134+
if let Some(attr) = attrs.iter().find(|attr| is_test_attribute(attr)) {
135+
let msg = "second test attribute is supplied, consider removing or changing the order of your test attributes";
136+
return Error::new_spanned(attr, msg).into_compile_error();
137+
}
138+
attrs.push(qualified_test_attr);
103139
(
104140
TokenStream2::new(),
105141
quote! { let _result = super::#item_name(#(#arg_values),*); },

crates/test-case-macros/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ fn expand_additional_test_case_macros(item: &mut ItemFn) -> syn::Result<Vec<(Tes
110110
}
111111

112112
for i in attrs_to_remove.into_iter().rev() {
113-
item.attrs.swap_remove(i);
113+
item.attrs.remove(i);
114114
}
115115

116116
Ok(additional_cases)

tests/acceptance_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ macro_rules! run_acceptance_test {
2222

2323
let output = sanitize_lines(output);
2424

25-
insta::assert_display_snapshot!(output);
25+
insta::assert_snapshot!(output);
2626
})
2727
};
2828
($case_name:expr) => {

tests/snapshots/rust-nightly/acceptance__matrices_compilation_errors.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ expression: output
55
error: All literal values must be of the same type
66
error: Range bounds can only be an integer literal
77
error: Unbounded ranges are not supported
8-
error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors
8+
error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors; 1 warning emitted
99
error: number too large to fit in target type
1010
error[E0308]: mismatched types

tests/snapshots/rust-stable/acceptance__features_produce_human_readable_errors.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ source: tests/acceptance_tests.rs
33
expression: output
44
---
55
error: 'with-regex' feature is required to use 'matches-regex' keyword
6-
error: could not compile `features_produce_human_readable_errors` (lib test) due to previous error
6+
error: could not compile `features_produce_human_readable_errors` (lib test) due to 1 previous error

tests/snapshots/rust-stable/acceptance__matrices_compilation_errors.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ expression: output
55
error: All literal values must be of the same type
66
error: Range bounds can only be an integer literal
77
error: Unbounded ranges are not supported
8-
error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors
8+
error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors; 1 warning emitted
99
error: number too large to fit in target type
1010
error[E0308]: mismatched types

0 commit comments

Comments
 (0)