| 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<Expr>, |
| expected: Option<Expected>, |
| case_desc: Option<LitStr>, |
| } |
| |
| 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 expected: Expected = input.parse()?; |
| |
| test_case_name += &format!(" {}", expected.to_string()); |
| |
| Some(expected) |
| } 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(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 |
| ); |
| } |
| } |
| } |
| } |