use greenhouse::TestFn;
use quote::format_ident;
use std::fmt::Formatter;
use syn::braced;
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
#[derive(Clone)]
pub struct TestCases {
cases: Punctuated<TestCase, Token![,]>,
}
impl TestCases {
pub fn to_token_stream(&self, test_fn: &TestFn) -> Result<proc_macro2::TokenStream> {
let visibility = test_fn.visibility();
let mod_ident = format_ident!("{}", test_fn.identifier());
let generated_cases = self
.cases
.iter()
.map(|case| case.to_token_stream(test_fn))
.collect::<Result<Vec<_>>>()?;
Ok(::quote::quote! {
#[cfg(test)]
#visibility mod #mod_ident {
use super::*;
#(#generated_cases)*
}
})
}
}
impl std::fmt::Debug for TestCases {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("TestCases(")?;
for case in self.cases.iter() {
case.fmt(f)?;
}
f.write_str(")")
}
}
impl Parse for TestCases {
fn parse(input: ParseStream) -> Result<Self> {
Ok(TestCases {
cases: Punctuated::parse_terminated(input)?,
})
}
}
#[derive(Clone)]
#[allow(dead_code)]
pub struct TestCase {
id: syn::Ident,
assignment: Token![=],
braces: syn::token::Brace,
arguments: Punctuated<syn::Expr, Token![,]>,
}
impl TestCase {
pub fn to_token_stream(&self, test_fn: &TestFn) -> Result<::proc_macro2::TokenStream> {
test_fn.assert_at_most_one_test_macro()?;
let test_meta = test_fn.test_macro_attribute();
let attributes = test_fn.attributes();
let visibility = test_fn.visibility();
let constness = test_fn.constness();
let asyncness = test_fn.asyncness();
let unsafety = test_fn.unsafety();
let abi = test_fn.abi();
let identifier = &self.id;
let bindings = self.generate_bindings(test_fn)?;
let return_type = test_fn.return_type();
let body = test_fn.body();
Ok(::quote::quote! {
#[#test_meta]
#(#attributes)*
#visibility #constness #asyncness #unsafety #abi fn #identifier() #return_type {
#bindings
#body
}
})
}
fn generate_bindings(&self, test_fn: &TestFn) -> Result<::proc_macro2::TokenStream> {
let identifier = &self.id;
let parameters = test_fn.parameters()?;
if self.arguments.len() != parameters.len() {
return Err(::syn::Error::new(
identifier.span(), format_args!(
"{}: Expected {} arguments, but {} were given",
identifier,
parameters.len(),
self.arguments.len(),
),
));
}
let bindings = parameters
.iter()
.zip(&self.arguments)
.map(|((ident, typ), expr)| {
::quote::quote! {
let #ident: #typ = #expr;
}
});
Ok(::quote::quote! {
#(#bindings)*
})
}
}
impl std::fmt::Debug for TestCase {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("TestCase(id = {:?})", self.id))
}
}
impl Parse for TestCase {
fn parse(input: ParseStream) -> Result<Self> {
let content;
Ok(TestCase {
id: input.parse()?,
assignment: input.parse()?,
braces: braced!(content in input),
arguments: Punctuated::parse_terminated(&content)?,
})
}
}