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