use crate::expected::Expected; use crate::utils::fmt_syn; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::parse::{Parse, ParseStream}; use syn::{parse_quote, Error, Expr, Ident, ItemFn, LitStr, Token}; #[cfg_attr(test, derive(Debug, PartialEq))] pub struct TestCase { test_case_name: String, args: Vec, expected: Option, case_desc: Option, } impl Parse for TestCase { fn parse(input: ParseStream) -> Result { 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]> = input.parse()?; let expected = if arrow.is_some() { let expected: Expected = input.parse()?; test_case_name += &format!(" {}", expected.to_string()); Some(expected) } else { None }; let semicolon: Option = 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(LitStr::value) .unwrap_or_else(|| self.test_case_name.clone()); crate::utils::escape_test_name(case_desc) } pub fn render(&self, mut item: ItemFn) -> TokenStream2 { let item_name = item.sig.ident.clone(); 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().to_lowercase().contains("inconclusive")) .unwrap_or_default(); let mut attrs = vec![]; let expected = if let Some(expected) = &self.expected { let case = expected.case(); if let Some(attr) = case.attr() { attrs.push(attr); } if let Some(body) = case.body() { body } else { parse_quote! { () } } } else { parse_quote! { () } }; if inconclusive { attrs.push(parse_quote! { #[ignore] }) } attrs.append(&mut item.attrs); if let Some(_asyncness) = item.sig.asyncness { quote! { #(#attrs)* async fn #test_case_name() { let _result = #item_name(#(#arg_values),*).await; #expected } } } else { quote! { #[test] #(#attrs)* fn #test_case_name() { let _result = #item_name(#(#arg_values),*); #expected } } } } } #[cfg(test)] mod tests { use super::*; mod expected { use super::*; mod parse { use super::*; use crate::expected::expr_case::ExprCase; use crate::expected::ignore_case::IgnoreCase; use crate::expected::panic_case::PanicCase; use crate::expected::pattern_case::PatternCase; use syn::parse_quote; #[test] fn parses_expression() { let actual: Expected = parse_quote! { 2 + 3 }; assert_eq!(Expected::Expr(ExprCase::new(parse_quote!(2 + 3))), actual); } #[test] fn parses_panic() { let actual: Expected = parse_quote! { panics "Error msg" }; assert_eq!( Expected::Panic(PanicCase::new(parse_quote!("Error msg"))), actual ); } #[test] fn parses_panic_without_msg() { let actual: Expected = parse_quote! { panics }; assert_eq!(Expected::Panic(PanicCase::new(None)), actual); } #[test] fn parses_pattern() { let actual: Expected = parse_quote! { matches Some(_) }; assert_eq!( Expected::Pattern(PatternCase::new(parse_quote!(Some(_)))), actual ); } #[test] fn parses_inconclusive() { let actual: Expected = parse_quote! { inconclusive "Ignore this" }; assert_eq!( Expected::Ignore(IgnoreCase::new(parse_quote!("Ignore this"))), actual ); } } } mod test_case { use super::*; mod parse { use super::*; use syn::parse_quote; #[test] fn parses_basic_input() { let actual: TestCase = parse_quote! { 2, 10 }; assert_eq!( TestCase { test_case_name: " 2 10".to_string(), args: vec![parse_quote!(2), parse_quote!(10),], expected: None, case_desc: None, }, actual ); } #[test] fn parses_input_with_expectation() { let actual: TestCase = parse_quote! { 2, 10 => 12 }; assert_eq!( TestCase { test_case_name: " 2 10 expects 12".to_string(), args: vec![parse_quote!(2), parse_quote!(10),], expected: Some(parse_quote!(12)), case_desc: None, }, actual ); } #[test] fn parses_input_with_description() { let actual: TestCase = parse_quote! { 2, 10; "basic addition" }; assert_eq!( TestCase { test_case_name: " 2 10".to_string(), args: vec![parse_quote!(2), parse_quote!(10),], expected: None, case_desc: parse_quote!("basic addition"), }, actual ); } } } }