blob: feeacecd1fc9c9ef159d6514e5269f82e69e2821 [file]
//! 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(<expr>)]` 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<TokenStream2> = if args.is_empty() { None } else { Some(args.into()) };
// Look for `#[ignore]` and `#[ignore_if(<expr>)]` attributes on the wrapped item.
let mut ignore_if: Option<TokenStream2> = 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(<expr>)]` 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
}