s_test_fixture 0.1.8

s_test_fixture or simple test fixture is a macro library to implement test fixture with no hassle
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{ExprCall, Item, ItemFn, ItemMod};

/// helper function to edit the source code with the argument function before every test
pub fn add_fn_before_each(test_mod: &mut ItemMod, arg_fn: &ExprCall) {
    generic_each_adder(test_mod, arg_fn, &add_fn_before);
}

/// helper function to edit the source code with the argument function after every test
pub fn add_fn_after_each(test_mod: &mut ItemMod, arg_fn: &ExprCall) {
    add_struct_dropper_to_after_each(test_mod);
    add_impl_dropper_to_after_each(test_mod);
    generic_each_adder(test_mod, arg_fn, &add_after_fn_dropper_already_defined);
}
/// helper function to add the argument function
fn generic_each_adder(
    test_mod: &mut ItemMod,
    arg_fn: &ExprCall,
    adder: &dyn Fn(&ItemFn, &ExprCall) -> TokenStream,
) {
    let mut new_item: Vec<Item>;
    match &test_mod.content {
        None => panic!("no test inside the module"),
        Some((_, original_items)) => {
            new_item = original_items.clone();
            for (item, i) in original_items.iter().zip(0..original_items.len()) {
                if let Item::Fn(current_item_fn) = item {
                    for attr in current_item_fn.attrs.clone() {
                        let attribut_string = format!("{:?}", attr.clone().tokens.to_string());
                        // #[test] return "\"\"" might cause problem if a function
                        // that is not a test use an atribute macro
                        // but there is no eq trait for TokenStream
                        if attribut_string == String::from("\"\"") {
                            let token_fn_modified = adder(&current_item_fn, arg_fn);
                            let syn_item: ItemFn = syn::parse(token_fn_modified).unwrap();
                            new_item[i] = Item::Fn(syn_item);
                            break;
                        }
                    }
                }
            }
        }
    }
    let new_content = (test_mod.content.clone().unwrap().0, new_item);
    test_mod.content = Some(new_content);
}

/// helper function to edit the source code with the argument function before the test
pub fn add_fn_before(function: &ItemFn, args: &ExprCall) -> TokenStream {
    let ItemFn {
        attrs,
        vis,
        sig,
        block,
    } = function;
    let stmts = &block.stmts;

    TokenStream::from(quote! {
        #(#attrs)* #vis #sig {
            #args;
            #(#stmts)*
        }
    })
}
/// helper function to edit the source code with the argument function after the test
// reference https://stackoverflow.com/questions/66850967/procedural-attribute-macro-to-inject-code-at-the-beginning-of-a-function
pub fn add_fn_after(function: &ItemFn, args: &ExprCall) -> TokenStream {
    let ItemFn {
        attrs,
        vis,
        sig,
        block,
    } = function;
    let stmts = &block.stmts;

    // reference https://stackoverflow.com/questions/29963449/golang-like-defer-in-rust
    TokenStream::from(quote! {
        #(#attrs)* #vis #sig {
            struct ScopeCall<F: FnMut()> {
                c: F
            }
            impl<F: FnMut()> Drop for ScopeCall<F> {
                fn drop(&mut self) {
                    (self.c)();
                }
            }
            let _scope_call = ScopeCall { c: || -> () { #args; } };

            #(#stmts)*
        }
    })
}
/// helper function to add to the source code a struct that will call the argument function of the macro
fn add_struct_dropper_to_after_each(test_mod: &mut ItemMod) {
    let token_struct_dropper = quote! {
        struct ScopeCall<F: FnMut()> {
            c: F
        }
    };
    generic_adder_element_to_after_each(test_mod, token_struct_dropper);
}

/// helper function to add to the source code a trait that will execute the argument function of the macro when the object is destroyed
fn add_impl_dropper_to_after_each(test_mod: &mut ItemMod) {
    let token_struct_dropper = quote! {
        impl<F: FnMut()> Drop for ScopeCall<F> {
            fn drop(&mut self) {
                (self.c)();
            }
        }
    };
    generic_adder_element_to_after_each(test_mod, token_struct_dropper);
}
/// helper function to add elements to the source code
fn generic_adder_element_to_after_each(
    test_mod: &mut ItemMod,
    token: quote::__private::TokenStream,
) {
    let mut items_with_with_struct: Vec<Item>;
    match &test_mod.content {
        None => panic!("no test inside the module"),
        Some((_, original_items)) => {
            items_with_with_struct = original_items.clone();
            let syn_item: Item = syn::parse2(token).unwrap();
            items_with_with_struct.insert(0, syn_item);
        }
    }
    let new_content = (test_mod.content.clone().unwrap().0, items_with_with_struct);
    test_mod.content = Some(new_content);
}

fn add_after_fn_dropper_already_defined(function: &ItemFn, args: &ExprCall) -> TokenStream {
    let ItemFn {
        attrs,
        vis,
        sig,
        block,
    } = function;
    let stmts = &block.stmts;

    // reference https://stackoverflow.com/questions/29963449/golang-like-defer-in-rust
    TokenStream::from(quote! {
        #(#attrs)* #vis #sig {
            let _scope_call = ScopeCall { c: || -> () { #args; } };

            #(#stmts)*
        }
    })
}