1 //! Attribute proc macro for rdroidtest instances.
2 use proc_macro::TokenStream;
3 use proc_macro2::TokenStream as TokenStream2;
4 use quote::{quote, ToTokens};
5 use syn::{parse_macro_input, ItemFn, Meta};
6 
7 /// Macro to mark an `rdroidtest` test function.  Can take one optional argument, an expression that
8 /// evaluates to a `Vec` of parameter (name, value) pairs.
9 ///
10 /// Also detects `#[ignore]` and `#[ignore_if(<expr>)]` attributes on the test function.
11 #[proc_macro_attribute]
rdroidtest(args: TokenStream, item: TokenStream) -> TokenStream12 pub fn rdroidtest(args: TokenStream, item: TokenStream) -> TokenStream {
13     // Only accept code that parses as a function definition.
14     let item = parse_macro_input!(item as ItemFn);
15     let fn_name = &item.sig.ident;
16 
17     // If the attribute has any arguments, they are expected to be a parameter generator expression.
18     let param_gen: Option<TokenStream2> = if args.is_empty() { None } else { Some(args.into()) };
19 
20     // Look for `#[ignore]` and `#[ignore_if(<expr>)]` attributes on the wrapped item.
21     let mut ignore_if: Option<TokenStream2> = None;
22     let mut ignored = false;
23     for attr in &item.attrs {
24         match &attr.meta {
25             Meta::Path(path) if path.to_token_stream().to_string().as_str() == "ignore" => {
26                 // `#[ignore]` attribute.
27                 ignored = true;
28             }
29             Meta::List(list) if list.path.to_token_stream().to_string().as_str() == "ignore_if" => {
30                 // `#[ignore_if(<expr>)]` attribute.
31                 ignore_if = Some(list.tokens.clone());
32             }
33             _ => {}
34         }
35     }
36     if ignored {
37         // `#[ignore]` trumps any specified `#[ignore_if]`.
38         ignore_if = Some(if param_gen.is_some() {
39             // `ignore_if` needs to be something invoked with a single parameter.
40             quote! { |_p| true }.into_iter().collect()
41         } else {
42             quote! { true }.into_iter().collect()
43         });
44     }
45 
46     // Build up an invocation of the appropriate `rdroidtest` declarative macro.
47     let invocation = match (param_gen, ignore_if) {
48         (Some(pg), Some(ii)) => quote! { ::rdroidtest::ptest!( #fn_name, #pg, ignore_if: #ii ); },
49         (Some(pg), None) => quote! { ::rdroidtest::ptest!( #fn_name, #pg ); },
50         (None, Some(ii)) => quote! { ::rdroidtest::test!( #fn_name, ignore_if: #ii ); },
51         (None, None) => quote! { ::rdroidtest::test!( #fn_name ); },
52     };
53 
54     let mut stream = TokenStream2::new();
55     stream.extend([invocation]);
56     stream.extend(item.into_token_stream());
57     stream.into_token_stream().into()
58 }
59 
60 /// Macro to mark conditions for ignoring an `rdroidtest` test function.  Expands to nothing here,
61 /// scanned for by the [`rdroidtest`] attribute macro.
62 #[proc_macro_attribute]
ignore_if(_args: TokenStream, item: TokenStream) -> TokenStream63 pub fn ignore_if(_args: TokenStream, item: TokenStream) -> TokenStream {
64     item
65 }
66