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