rtest-derive 0.1.5

heper crate with derive macros for rtest
Documentation
use proc_macro::TokenStream;
use quote::quote;
use rtest_util::TestArguments;
use std::sync::{Arc, Mutex, OnceLock};

struct TestInfo {
    fn_name: String,
    #[allow(dead_code)]
    args:    TestArguments,
    input:   String,
}

static FUNCTION_NAMES: OnceLock<Arc<Mutex<Vec<TestInfo>>>> = OnceLock::new();

#[proc_macro_derive(FromContext)]
pub fn from_context(input: TokenStream) -> TokenStream {
    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
    let name = &ast.ident;
    let name_str = name.to_string();

    let code = quote!(
        impl rtest::FromContext for #name {
            type Context = rtest::Context;

            fn from_context(context: &Self::Context) -> Option<rtest::Resource<Self>> {
                context.extract::<#name>()
            }

            fn into_context(context: &Self::Context, resource: rtest::Resource<#name>) {
                context.inject(resource)
            }

            fn get_resource_id() -> rtest::ResourceId {
                #name_str.to_string()
            }
        }
    );
    code.into()
}

#[proc_macro_attribute]
pub fn rtest(attr: TokenStream, orig_input: TokenStream) -> TokenStream {
    use darling::FromMeta;

    let input = orig_input.clone();
    let fn_ast = syn::parse_macro_input!(input as syn::ItemFn);

    let attr_args = match darling::ast::NestedMeta::parse_meta_list(attr.into()) {
        Ok(v) => v,
        Err(e) => {
            return TokenStream::from(darling::Error::from(e).write_errors());
        },
    };

    let args = match TestArguments::from_list(&attr_args) {
        Ok(v) => v,
        Err(e) => {
            return TokenStream::from(e.write_errors());
        },
    };

    {
        let mutex = FUNCTION_NAMES.get_or_init(|| Arc::new(Mutex::new(Vec::new())));
        let mut list = mutex.lock().unwrap();
        list.push(TestInfo {
            fn_name: fn_ast.sig.ident.to_string(),
            args,
            input: orig_input.to_string(),
        })
    }
    orig_input
}

#[proc_macro]
/// input must be the RunConfig
pub fn run(input: TokenStream) -> TokenStream {
    use std::str::FromStr;
    let list: Vec<_> = {
        let mutex = FUNCTION_NAMES.get_or_init(|| Arc::new(Mutex::new(Vec::new())));
        mutex.lock().unwrap().drain(..).collect()
    };

    let input_struct = syn::parse_macro_input!(input as syn::Path);

    let mut commands = quote!();
    for l in list.into_iter() {
        let name = l.fn_name.to_string();
        let input = TokenStream::from_str(&l.input).unwrap();
        let ast = syn::parse_macro_input!(input as syn::ItemFn);
        let fn_ident = ast.sig.ident;
        let relative_fn_ident = l
            .args
            .module
            .as_ref()
            .and_then(|module| module.split_once("::"))
            .map(|module| format!("crate::{}::{}", module.1, &fn_ident.to_string()))
            .unwrap_or(fn_ident.to_string());
        let relative_fn_ident = TokenStream::from_str(&relative_fn_ident).unwrap();
        let relative_fn_ident = syn::parse_macro_input!(relative_fn_ident as syn::Path);
        let testargs = l.args;

        let f = quote! {
            let c = #input_struct.context.clone();
            let handler_params = rtest::HandlerParams::from(&#input_struct);
            let (reference, inputs, outputs) = rtest::describe_handler(& #relative_fn_ident);
            test_repo.add(#name, Box::new(move || {
                rtest::call_handler(c.clone(), &mut #relative_fn_ident, &handler_params)
            }), #testargs, reference, inputs, outputs);
        };
        commands = quote!(
            #commands
            #f
        );
    }

    let code = quote!(
        {
            use rtest::{Runner, Printer, TestArguments};
            use clap::Parser;
            let args = rtest::Args::parse();
            let #input_struct: rtest::RunConfig<_> = #input_struct;

            let mut test_repo = rtest::TestRepo::new(args.test_tracing_log_level);
            #commands
            let (results, info) = Runner::run::<rtest::SimpleStrategy>(test_repo);
            Printer::print_to_stdout(results, info);
        }
    );

    code.into()
}