[go: up one dir, main page]

test-case 0.3.0

Provides #[test_case(...)] procedural macro attribute for generating parametrized test cases easily
Documentation
use quote::ToTokens;
use syn::{Expr, Error, ItemFn, Token, LitStr};
use syn::parse::{Parse, ParseStream};
use proc_macro2::Ident;
use syn::export::TokenStream2;
use syn::parse_quote;
use quote::quote;

pub struct TestCase {
test_case_name: String,
args: Vec<Expr>,
expected: Option<Expr>,
case_desc: Option<LitStr>
}

fn fmt_syn(syn: &(impl ToTokens + Clone)) -> String {
    syn.clone().into_token_stream().to_string()
}

impl Parse for TestCase {
    fn parse(input: ParseStream) -> Result<Self, Error> {
        let mut test_case_name = String::new();

        let mut args = vec![];
        loop {
            let exp: Expr = input.parse()?;
            test_case_name += &format!(" {}", fmt_syn(&exp));
            args.push(exp);
            if !input.peek(Token![,]) {
                break
            }
            let _comma : Token![,] = input.parse()?;
        }

        let arrow: Option<Token![=>]> = input.parse()?;

        let expected = if arrow.is_some() {
            let expr: Expr = input.parse()?;
            test_case_name += &format!(" expects {}", fmt_syn(&expr));
            Some(expr)
        }
        else { None };

        let semicolon: Option<Token![;]> = input.parse()?;
        let case_desc = if semicolon.is_some() {
            let desc: LitStr = input.parse()?;
            Some(desc)
        }
        else { None };

        Ok(Self {
            test_case_name,
            args,
            expected,
            case_desc
        })
    }
}

impl TestCase {
    pub fn test_case_name(&self) -> Ident {
        let case_desc = self.case_desc.as_ref().map(|cd| {
            cd.value()
        }).unwrap_or_else(|| self.test_case_name.clone());
        crate::utils::escape_test_name(case_desc)
    }

    pub fn render(&self, item: ItemFn) -> TokenStream2 {
        let arg_keys = item.decl.inputs;
        let item_body = item.block;
        let arg_values = self.args.iter();
        let test_case_name = self.test_case_name();
        let inconclusive = self.case_desc.as_ref()
            .map(|cd| cd.value().contains("inconclusive"))
            .unwrap_or_default();

        let expected: Expr = match &self.expected {
            Some(e) => parse_quote!{
                assert_eq!(#e, _result)
            },
            None => parse_quote!{()}
        };


        let mut attrs = vec![];
        if inconclusive {
            attrs.push(parse_quote! { #[ignore] });
        }
        attrs.append(&mut item.attrs.clone());

        quote! {
            #[test]
            #(#attrs)*
            fn #test_case_name() {
                #(
                    let #arg_keys = #arg_values;
                )*

                let _result = { #item_body };
                #expected
            }
        }
    }
}