use proc_macro2::{Span, TokenStream};
use crate::attribute::{Fn, ParameterizedList};
use crate::tests::TestCases;
pub fn generate(argument_lists: ParameterizedList, func: Fn) -> proc_macro::TokenStream {
let values = into_argument_map(&argument_lists);
let args = function_arguments(&func);
let amount_of_test_cases = values.amount_of_test_cases().unwrap_or_default();
let generated_test_cases =
(0..amount_of_test_cases).map(|i| generate_test_case(args.as_slice(), &values, i, &func));
generate_module(generated_test_cases, &func).into()
}
fn into_argument_map(arguments: &ParameterizedList) -> TestCases<'_> {
arguments
.args
.iter()
.fold(TestCases::empty(), |mut acc, args| {
let identifier = &args.id;
let exprs = args.param_args.iter().collect::<Vec<&syn::Expr>>();
acc.insert(identifier, exprs);
acc
})
}
type FnArgPair<'ctx> = (&'ctx syn::Ident, &'ctx Box<syn::Type>);
fn function_arguments(f: &Fn) -> Vec<FnArgPair<'_>> {
f.item_fn.sig.inputs.iter().map(|fn_arg| {
match fn_arg {
syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => match pat.as_ref() {
syn::Pat::Ident(syn::PatIdent { ident, .. }) => (ident, ty) ,
_ => panic!("parameterized-macro: error: No identifier found for test case")
}
_ => panic!("parameterized-macro: error: Unexpected receiver found in test case function arguments")
}
}).collect::<Vec<_>>()
}
fn generate_module<I: Iterator<Item = TokenStream>>(test_cases: I, f: &Fn) -> TokenStream {
let name = &f.item_fn.sig.ident;
let vis = &f.item_fn.vis;
let mod_ident = syn::Ident::new(&format!("{}", name), name.span());
quote::quote! {
#[cfg(test)]
#vis mod #mod_ident {
use super::*;
#(#test_cases)*
}
}
}
fn generate_test_case(
parameters: &[FnArgPair],
test_cases: &TestCases,
i: usize,
f: &Fn,
) -> TokenStream {
let constness = f.constness();
let asyncness = f.asyncness();
let unsafety = f.unsafety();
let visibility = f.visibility();
let identifier = syn::Ident::new(&format!("case_{}", i), Span::call_site());
let return_type = f.return_type();
let body = f.body();
let bindings = parameters.iter().map(|(identifier, ty)| {
let expr = test_cases.get(identifier, i);
generate_binding(identifier, ty, expr)
});
let (use_test_macro, unrelated_attributes): (Vec<_>, Vec<_>) =
f.attrs.iter().partition(|&m| m.is_use_test_macro());
if use_test_macro.len() > 1 {
panic!("parameterized-macro: the #[parameterized_macro(..)] attribute should not be present more than once!");
}
let unrelated_attributes = unrelated_attributes.iter().map(|attr| attr.quoted());
let test_macro = if use_test_macro.is_empty() {
quote::quote!(#[test])
} else {
let meta = use_test_macro[0];
let meta = meta.quoted();
quote::quote!(#[#meta])
};
quote::quote! {
#test_macro
#(#unrelated_attributes)*
#constness #asyncness #unsafety #visibility fn #identifier() #return_type {
#(#bindings)*
#body
}
}
}
fn generate_binding(identifier: &syn::Ident, ty: &syn::Type, expr: &syn::Expr) -> TokenStream {
quote::quote! {
let #identifier: #ty = #expr;
}
}