//! Attribute proc macro for rdroidtest instances. use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; use syn::{parse_macro_input, ItemFn, Meta}; /// Macro to mark an `rdroidtest` test function. Can take one optional argument, an expression that /// evaluates to a `Vec` of parameter (name, value) pairs. /// /// Also detects `#[ignore]` and `#[ignore_if()]` attributes on the test function. #[proc_macro_attribute] pub fn rdroidtest(args: TokenStream, item: TokenStream) -> TokenStream { // Only accept code that parses as a function definition. let item = parse_macro_input!(item as ItemFn); let fn_name = &item.sig.ident; // If the attribute has any arguments, they are expected to be a parameter generator expression. let param_gen: Option = if args.is_empty() { None } else { Some(args.into()) }; // Look for `#[ignore]` and `#[ignore_if()]` attributes on the wrapped item. let mut ignore_if: Option = None; let mut ignored = false; for attr in &item.attrs { match &attr.meta { Meta::Path(path) if path.to_token_stream().to_string().as_str() == "ignore" => { // `#[ignore]` attribute. ignored = true; } Meta::List(list) if list.path.to_token_stream().to_string().as_str() == "ignore_if" => { // `#[ignore_if()]` attribute. ignore_if = Some(list.tokens.clone()); } _ => {} } } if ignored { // `#[ignore]` trumps any specified `#[ignore_if]`. ignore_if = Some(if param_gen.is_some() { // `ignore_if` needs to be something invoked with a single parameter. quote! { |_p| true }.into_iter().collect() } else { quote! { true }.into_iter().collect() }); } // Build up an invocation of the appropriate `rdroidtest` declarative macro. let invocation = match (param_gen, ignore_if) { (Some(pg), Some(ii)) => quote! { ::rdroidtest::ptest!( #fn_name, #pg, ignore_if: #ii ); }, (Some(pg), None) => quote! { ::rdroidtest::ptest!( #fn_name, #pg ); }, (None, Some(ii)) => quote! { ::rdroidtest::test!( #fn_name, ignore_if: #ii ); }, (None, None) => quote! { ::rdroidtest::test!( #fn_name ); }, }; let mut stream = TokenStream2::new(); stream.extend([invocation]); stream.extend(item.into_token_stream()); stream.into_token_stream().into() } /// Macro to mark conditions for ignoring an `rdroidtest` test function. Expands to nothing here, /// scanned for by the [`rdroidtest`] attribute macro. #[proc_macro_attribute] pub fn ignore_if(_args: TokenStream, item: TokenStream) -> TokenStream { item }