blob: 4dd13859cedde2bba4296a840c7971820d2a54b5 [file] [log] [blame]
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
);
}
}
}
}