@@ -9,6 +9,17 @@ use crate::{
99 wrap_test_body,
1010} ;
1111
12+ /// A parsed `it` case ready for code generation:
13+ /// `(description, fn name, body, is_async, params, forwarded attributes)`.
14+ type SpecTest = (
15+ String ,
16+ Ident ,
17+ proc_macro2:: TokenStream ,
18+ bool ,
19+ Vec < PipeParam > ,
20+ Vec < syn:: Attribute > ,
21+ ) ;
22+
1223/// A parsed parameter from pipe syntax: `|name: &Type, name2: Type|`
1324pub ( crate ) struct PipeParam {
1425 pat : syn:: Pat ,
@@ -30,8 +41,14 @@ pub(crate) enum SpecItem {
3041 // body async return_type input_params
3142 AfterEach ( proc_macro2:: TokenStream , bool , Vec < PipeParam > ) ,
3243 // body async params
33- It ( String , proc_macro2:: TokenStream , bool , Vec < PipeParam > ) ,
34- // body async params
44+ It (
45+ String ,
46+ proc_macro2:: TokenStream ,
47+ bool ,
48+ Vec < PipeParam > ,
49+ Vec < syn:: Attribute > ,
50+ ) ,
51+ // body async params attrs
3552 Other ( proc_macro2:: TokenStream ) ,
3653}
3754
@@ -63,6 +80,19 @@ fn parse_pipe_params(input: ParseStream) -> syn::Result<Vec<PipeParam>> {
6380 Ok ( params)
6481}
6582
83+ /// Reject attributes on a construct that can't carry them. Attributes are only
84+ /// forwarded on `it` test cases; hooks and the `suite;`/runtime markers are not
85+ /// standalone functions, so an attribute there would be silently dropped.
86+ fn reject_attrs ( attrs : & [ syn:: Attribute ] , keyword : & str ) -> syn:: Result < ( ) > {
87+ if let Some ( attr) = attrs. first ( ) {
88+ return Err ( syn:: Error :: new_spanned (
89+ attr,
90+ format ! ( "attributes are not supported on `{keyword}` (only on `it` test cases)" ) ,
91+ ) ) ;
92+ }
93+ Ok ( ( ) )
94+ }
95+
6696/// Parse optional `-> Type` return type.
6797fn parse_return_type ( input : ParseStream ) -> syn:: Result < Option < syn:: Type > > {
6898 if input. peek ( Token ! [ ->] ) {
@@ -97,6 +127,10 @@ impl Parse for SpecModule {
97127 let mut items = Vec :: new ( ) ;
98128
99129 while !content. is_empty ( ) {
130+ // Leading `#[...]` attributes are forwarded to `it` test cases and
131+ // re-attached to plain items; they're rejected on hooks/markers.
132+ let attrs = content. call ( syn:: Attribute :: parse_outer) ?;
133+
100134 // Check for `async` keyword first
101135 if content. peek ( Token ! [ async ] ) {
102136 let fork = content. fork ( ) ;
@@ -112,10 +146,17 @@ impl Parse for SpecModule {
112146 let params = parse_pipe_params ( & content) ?;
113147 let body;
114148 braced ! ( body in content) ;
115- items. push ( SpecItem :: It ( desc. value ( ) , body. parse ( ) ?, true , params) ) ;
149+ items. push ( SpecItem :: It (
150+ desc. value ( ) ,
151+ body. parse ( ) ?,
152+ true ,
153+ params,
154+ attrs,
155+ ) ) ;
116156 continue ;
117157 }
118158 "before_each" => {
159+ reject_attrs ( & attrs, "before_each" ) ?;
119160 let _: Token ! [ async ] = content. parse ( ) ?;
120161 let _: Ident = content. parse ( ) ?;
121162 let params = parse_pipe_params ( & content) ?;
@@ -126,6 +167,7 @@ impl Parse for SpecModule {
126167 continue ;
127168 }
128169 "after_each" => {
170+ reject_attrs ( & attrs, "after_each" ) ?;
129171 let _: Token ! [ async ] = content. parse ( ) ?;
130172 let _: Ident = content. parse ( ) ?;
131173 let params = parse_pipe_params ( & content) ?;
@@ -141,7 +183,7 @@ impl Parse for SpecModule {
141183 }
142184 // Fall through: parse as regular item (e.g. `async fn helper()`)
143185 let item: syn:: Item = content. parse ( ) ?;
144- items. push ( SpecItem :: Other ( quote ! { #item } ) ) ;
186+ items. push ( SpecItem :: Other ( quote ! { #( #attrs ) * # item } ) ) ;
145187 continue ;
146188 }
147189
@@ -150,18 +192,21 @@ impl Parse for SpecModule {
150192 let kw: Ident = fork. parse ( ) ?;
151193 match kw. to_string ( ) . as_str ( ) {
152194 "suite" => {
195+ reject_attrs ( & attrs, "suite" ) ?;
153196 let _: Ident = content. parse ( ) ?;
154197 content. parse :: < Token ! [ ; ] > ( ) ?;
155198 items. push ( SpecItem :: Suite ) ;
156199 continue ;
157200 }
158201 "tokio" => {
202+ reject_attrs ( & attrs, "tokio" ) ?;
159203 let _: Ident = content. parse ( ) ?;
160204 content. parse :: < Token ! [ ; ] > ( ) ?;
161205 items. push ( SpecItem :: Runtime ( Runtime :: Tokio ) ) ;
162206 continue ;
163207 }
164208 "async_std" => {
209+ reject_attrs ( & attrs, "async_std" ) ?;
165210 let _: Ident = content. parse ( ) ?;
166211 content. parse :: < Token ! [ ; ] > ( ) ?;
167212 items. push ( SpecItem :: Runtime ( Runtime :: AsyncStd ) ) ;
@@ -173,10 +218,17 @@ impl Parse for SpecModule {
173218 let params = parse_pipe_params ( & content) ?;
174219 let body;
175220 braced ! ( body in content) ;
176- items. push ( SpecItem :: It ( desc. value ( ) , body. parse ( ) ?, false , params) ) ;
221+ items. push ( SpecItem :: It (
222+ desc. value ( ) ,
223+ body. parse ( ) ?,
224+ false ,
225+ params,
226+ attrs,
227+ ) ) ;
177228 continue ;
178229 }
179230 "before_each" => {
231+ reject_attrs ( & attrs, "before_each" ) ?;
180232 let _: Ident = content. parse ( ) ?;
181233 let params = parse_pipe_params ( & content) ?;
182234 let ret_ty = parse_return_type ( & content) ?;
@@ -186,6 +238,7 @@ impl Parse for SpecModule {
186238 continue ;
187239 }
188240 "after_each" => {
241+ reject_attrs ( & attrs, "after_each" ) ?;
189242 let _: Ident = content. parse ( ) ?;
190243 let params = parse_pipe_params ( & content) ?;
191244 let body;
@@ -194,6 +247,7 @@ impl Parse for SpecModule {
194247 continue ;
195248 }
196249 "before" => {
250+ reject_attrs ( & attrs, "before" ) ?;
197251 let _: Ident = content. parse ( ) ?;
198252 let ret_ty = parse_return_type ( & content) ?;
199253 if content. peek ( syn:: token:: Brace ) {
@@ -206,6 +260,7 @@ impl Parse for SpecModule {
206260 }
207261 }
208262 "after" => {
263+ reject_attrs ( & attrs, "after" ) ?;
209264 let _: Ident = content. parse ( ) ?;
210265 let params = parse_pipe_params ( & content) ?;
211266 if content. peek ( syn:: token:: Brace ) {
@@ -219,13 +274,13 @@ impl Parse for SpecModule {
219274 }
220275 _ => {
221276 let item: syn:: Item = content. parse ( ) ?;
222- items. push ( SpecItem :: Other ( quote ! { #item } ) ) ;
277+ items. push ( SpecItem :: Other ( quote ! { #( #attrs ) * # item } ) ) ;
223278 continue ;
224279 }
225280 }
226281 }
227282 let item: syn:: Item = content. parse ( ) ?;
228- items. push ( SpecItem :: Other ( quote ! { #item } ) ) ;
283+ items. push ( SpecItem :: Other ( quote ! { #( #attrs ) * # item } ) ) ;
229284 }
230285
231286 Ok ( SpecModule { vis, ident, items } )
@@ -254,13 +309,7 @@ pub(crate) fn expand(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2
254309 let mut after_each_body: Option < proc_macro2:: TokenStream > = None ;
255310 let mut after_each_is_async = false ;
256311 let mut after_each_params: Vec < PipeParam > = Vec :: new ( ) ;
257- let mut tests: Vec < (
258- String ,
259- Ident ,
260- proc_macro2:: TokenStream ,
261- bool ,
262- Vec < PipeParam > ,
263- ) > = Vec :: new ( ) ;
312+ let mut tests: Vec < SpecTest > = Vec :: new ( ) ;
264313 let mut other_items: Vec < proc_macro2:: TokenStream > = Vec :: new ( ) ;
265314
266315 for item in parsed. items {
@@ -318,9 +367,9 @@ pub(crate) fn expand(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2
318367 after_each_is_async = is_async;
319368 after_each_params = params;
320369 }
321- SpecItem :: It ( desc, body, is_async, params) => {
370+ SpecItem :: It ( desc, body, is_async, params, attrs ) => {
322371 let fn_name = format_ident ! ( "{}" , slugify( & desc) ) ;
323- tests. push ( ( desc, fn_name, body, is_async, params) ) ;
372+ tests. push ( ( desc, fn_name, body, is_async, params, attrs ) ) ;
324373 }
325374 SpecItem :: Other ( tokens) => {
326375 other_items. push ( tokens) ;
@@ -329,7 +378,7 @@ pub(crate) fn expand(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2
329378 }
330379
331380 // Validate: async items require a runtime
332- let any_async = tests. iter ( ) . any ( |( _, _, _, is_async, _) | * is_async)
381+ let any_async = tests. iter ( ) . any ( |( _, _, _, is_async, _, _ ) | * is_async)
333382 || before_each_is_async
334383 || after_each_is_async;
335384
@@ -361,7 +410,7 @@ pub(crate) fn expand(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2
361410 . iter ( )
362411 . chain ( before_each_params. iter ( ) )
363412 . chain ( after_each_params. iter ( ) )
364- . chain ( tests. iter ( ) . flat_map ( |( _, _, _, _, p) | p. iter ( ) ) ) ;
413+ . chain ( tests. iter ( ) . flat_map ( |( _, _, _, _, p, _ ) | p. iter ( ) ) ) ;
365414 for p in ref_sources {
366415 if p. is_ref
367416 && let Some ( inner) = ref_inner_type ( & p. ty )
@@ -382,7 +431,7 @@ pub(crate) fn expand(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2
382431 // Detect inline mode from consumers: tests or after_each have `_`-typed params
383432 let has_infer_consumers = tests
384433 . iter ( )
385- . any ( |( _, _, _, _, params) | params. iter ( ) . any ( |p| is_type_infer ( & p. ty ) ) )
434+ . any ( |( _, _, _, _, params, _ ) | params. iter ( ) . any ( |p| is_type_infer ( & p. ty ) ) )
386435 || after_each_params. iter ( ) . any ( |p| is_type_infer ( & p. ty ) ) ;
387436 let before_each_needs_inline = !has_before_each_ctx && has_infer_consumers;
388437 let after_each_needs_inline = after_each_params. iter ( ) . any ( |p| is_type_infer ( & p. ty ) ) ;
@@ -514,7 +563,7 @@ pub(crate) fn expand(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2
514563
515564 let test_fn_defs: Vec < proc_macro2:: TokenStream > = tests
516565 . iter ( )
517- . map ( |( _desc, fn_name, body, is_async, test_params) | {
566+ . map ( |( _desc, fn_name, body, is_async, test_params, attrs ) | {
518567 // A test needs async if it's declared async or any hook it uses is async
519568 let test_needs_async = * is_async || before_each_is_async || after_each_is_async;
520569
@@ -755,6 +804,7 @@ pub(crate) fn expand(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2
755804 let inner = wrap_async_test_body ( pre, body_with_bindings, post, needs_catch) ;
756805
757806 quote ! {
807+ #( #attrs) *
758808 #test_attr
759809 async fn #fn_name( ) {
760810 #inner
@@ -764,6 +814,7 @@ pub(crate) fn expand(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2
764814 let inner = wrap_test_body ( pre, body_with_bindings, post, needs_catch) ;
765815
766816 quote ! {
817+ #( #attrs) *
767818 #[ test]
768819 fn #fn_name( ) {
769820 #inner
0 commit comments