Skip to content

Commit

Permalink
feat: Add the ability to have comments in matrix tests (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
ollien authored Oct 22, 2023
1 parent 97bcfc0 commit 2fc16bf
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 65 deletions.
123 changes: 88 additions & 35 deletions crates/test-case-core/src/test_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,53 +10,62 @@ use syn::{parse_quote, Error, Expr, Ident, ItemFn, ReturnType, Token};
#[derive(Debug)]
pub struct TestCase {
args: Punctuated<Expr, Token![,]>,
pub(crate) expression: Option<TestCaseExpression>,
pub(crate) comment: Option<TestCaseComment>,
expression: Option<TestCaseExpression>,
name: Ident,
}

impl Parse for TestCase {
fn parse(input: ParseStream) -> Result<Self, Error> {
Ok(Self {
args: Punctuated::parse_separated_nonempty_with(input, Expr::parse)?,
expression: input.parse().ok(),
comment: input.parse().ok(),
})
let args = Punctuated::parse_separated_nonempty_with(input, Expr::parse)?;
let expression = input.parse::<TestCaseExpression>().ok();
let comment = input.parse::<TestCaseComment>().ok();

Ok(Self::new_from_parsed(args, expression, comment))
}
}
impl TestCase {
pub(crate) fn new<I: IntoIterator<Item = Expr>>(
args: I,
expression: Option<TestCaseExpression>,
comment: Option<TestCaseComment>,
) -> Self {
Self::new_from_parsed(args.into_iter().collect(), expression, comment)
}

pub(crate) fn new_from_parsed(
args: Punctuated<Expr, Token![,]>,
expression: Option<TestCaseExpression>,
comment: Option<TestCaseComment>,
) -> Self {
let name = Self::test_case_name_ident(args.iter(), expression.as_ref(), comment.as_ref());

impl<I> From<I> for TestCase
where
I: IntoIterator<Item = Expr>,
{
fn from(into_iter: I) -> Self {
Self {
args: into_iter.into_iter().collect(),
expression: None,
comment: None,
args,
expression,
name,
}
}

pub(crate) fn new_with_prefixed_name<I: IntoIterator<Item = Expr>>(
args: I,
expression: Option<TestCaseExpression>,
prefix: &str,
) -> Self {
let parsed_args = args.into_iter().collect::<Punctuated<Expr, Token![,]>>();
let name = Self::prefixed_test_case_name(parsed_args.iter(), expression.as_ref(), prefix);

Self {
args: parsed_args,
expression,
name,
}
}
}

impl TestCase {
pub fn test_case_name(&self) -> Ident {
let case_desc = self
.comment
.as_ref()
.map(|item| item.comment.value())
.unwrap_or_else(|| {
let mut acc = String::new();
for arg in &self.args {
acc.push_str(&fmt_syn(&arg));
acc.push('_');
}
acc.push_str("expects");
if let Some(expression) = &self.expression {
acc.push(' ');
acc.push_str(&expression.to_string())
}
acc
});
crate::utils::escape_test_name(case_desc)
// The clone is kind of annoying here, but because this is behind a reference, we must clone
// to preserve the signature without a breaking change
// TODO: return a reference?
self.name.clone()
}

pub fn render(&self, mut item: ItemFn, origin_span: Span2) -> TokenStream2 {
Expand Down Expand Up @@ -118,4 +127,48 @@ impl TestCase {
}
}
}

fn test_case_name_ident<'a, I: Iterator<Item = &'a Expr>>(
args: I,
expression: Option<&TestCaseExpression>,
comment: Option<&TestCaseComment>,
) -> Ident {
let desc = Self::test_case_name_string(args, expression, comment);

crate::utils::escape_test_name(desc)
}

fn prefixed_test_case_name<'a, I: Iterator<Item = &'a Expr>>(
args: I,
expression: Option<&TestCaseExpression>,
prefix: &str,
) -> Ident {
let generated_name = Self::test_case_name_string(args, expression, None);
let full_desc = format!("{prefix}_{generated_name}");

crate::utils::escape_test_name(full_desc)
}

fn test_case_name_string<'a, I: Iterator<Item = &'a Expr>>(
args: I,
expression: Option<&TestCaseExpression>,
comment: Option<&TestCaseComment>,
) -> String {
comment
.as_ref()
.map(|item| item.comment.value())
.unwrap_or_else(|| {
let mut acc = String::new();
for arg in args {
acc.push_str(&fmt_syn(&arg));
acc.push('_');
}
acc.push_str("expects");
if let Some(expression) = expression {
acc.push(' ');
acc.push_str(&expression.to_string())
}
acc
})
}
}
22 changes: 12 additions & 10 deletions crates/test-case-core/src/test_matrix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod matrix_product;
pub struct TestMatrix {
variables: Vec<Vec<Expr>>,
expression: Option<TestCaseExpression>,
comment: Option<TestCaseComment>,
}

impl TestMatrix {
Expand All @@ -25,11 +26,18 @@ impl TestMatrix {

pub fn cases(&self) -> impl Iterator<Item = TestCase> {
let expression = self.expression.clone();
let comment = self.comment.clone();

matrix_product::multi_cartesian_product(self.variables.iter().cloned()).map(move |v| {
let mut case = TestCase::from(v);
case.expression = expression.clone();
case
if let Some(comment) = comment.clone() {
TestCase::new_with_prefixed_name(
v,
expression.clone(),
comment.comment.value().as_ref(),
)
} else {
TestCase::new(v, expression.clone(), None)
}
})
}
}
Expand All @@ -40,16 +48,10 @@ impl Parse for TestMatrix {

let mut matrix = TestMatrix {
expression: input.parse().ok(),
comment: input.parse().ok(),
..Default::default()
};

if let Ok(c) = input.parse::<TestCaseComment>() {
return Err(syn::Error::new(
c.span(),
"Comments are not allowed in #[test_matrix]",
));
}

for arg in args {
let values: Vec<Expr> = match &arg {
Expr::Array(v) => v.elems.iter().cloned().collect(),
Expand Down
11 changes: 5 additions & 6 deletions crates/test-case-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,16 @@ pub fn test_case(args: TokenStream, input: TokenStream) -> TokenStream {

/// Generates tests for the cartesian product of a given set of data
///
/// A test matrix consists of three elements:
/// A test matrix consists of four elements:
///
/// 1. _(Required)_ Sets of values to combine; the nubmer of sets must be the same as the number of
/// 1. _(Required)_ Sets of values to combine; the number of sets must be the same as the number of
/// arguments to the test body function
/// 2. _(Optional)_ Expected result (for all combinations of values)
/// 3. _(Required)_ Test body
/// 3. _(Optional)_ Test case description (applied as a prefix the generated name of the test)
/// 4. _(Required)_ Test body
///
/// _Expected result_ and _Test body_ are the same as they are for the singular `#[test_case(...)]`
/// macro but are applied to every case generated by `#[test_matrix(...)]`. `Test case description`
/// is not allowed for `test_matrix`, because test case names are auto-generated from the test body
/// function name and matrix values.
/// macro but are applied to every case generated by `#[test_matrix(...)]`.
#[proc_macro_attribute]
#[proc_macro_error::proc_macro_error]
pub fn test_matrix(args: TokenStream, input: TokenStream) -> TokenStream {
Expand Down
8 changes: 0 additions & 8 deletions tests/acceptance_cases/matrices_compilation_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,6 @@ fn unbounded_range(x: u32) {
unreachable!("Should never compile")
}

#[test_matrix(
[1, 2, 3]
; "Illegal comment"
)]
fn illegal_comment(x: u32) {
unreachable!("Should never compile")
}

const USIZE_CONST: usize = 0;

#[test_matrix(USIZE_CONST)]
Expand Down
28 changes: 28 additions & 0 deletions tests/acceptance_cases/matrices_support_basic_features/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,34 @@ mod test_cases {
assert!(x < 10);
}

#[test_matrix(
[1, 2],
[11, 12]
)]
#[test_matrix(
[3, 4],
[13, 14]
)]
fn two_matrices(x: u32, y: u32) {
assert!(x < 10);
assert!(y > 10);
}

#[test_matrix(
[1, 2],
[11, 12]
; "one, two"
)]
#[test_matrix(
[3, 4],
[13, 14]
; "three, four"
)]
fn two_matrices_with_comments(x: u32, y: u32) {
assert!(x < 10);
assert!(y > 10);
}

#[test_case(5)]
#[test_matrix(
[6, 7, 8]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ source: tests/acceptance_tests.rs
expression: output
---
error: All literal values must be of the same type
error: Comments are not allowed in #[test_matrix]
error: Range bounds can only be an integer literal
error: Unbounded ranges are not supported
error: could not compile `matrices_compilation_errors` (lib test) due to 6 previous errors
error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors
error: number too large to fit in target type
error[E0308]: mismatched types
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
source: tests/acceptance_tests.rs
expression: output
---
test result: ok. 54 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 70 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test test_cases::case_after_matrix::_1_expects ... ok
test test_cases::case_after_matrix::_2_expects ... ok
test test_cases::case_after_matrix::_3_expects ... ok
Expand Down Expand Up @@ -57,3 +57,19 @@ test test_cases::str_values_tuple::_one_blue_expects ... ok
test test_cases::str_values_tuple::_one_yellow_expects ... ok
test test_cases::str_values_tuple::_two_blue_expects ... ok
test test_cases::str_values_tuple::_two_yellow_expects ... ok
test test_cases::two_matrices::_1_11_expects ... ok
test test_cases::two_matrices::_1_12_expects ... ok
test test_cases::two_matrices::_2_11_expects ... ok
test test_cases::two_matrices::_2_12_expects ... ok
test test_cases::two_matrices::_3_13_expects ... ok
test test_cases::two_matrices::_3_14_expects ... ok
test test_cases::two_matrices::_4_13_expects ... ok
test test_cases::two_matrices::_4_14_expects ... ok
test test_cases::two_matrices_with_comments::one_two_1_11_expects ... ok
test test_cases::two_matrices_with_comments::one_two_1_12_expects ... ok
test test_cases::two_matrices_with_comments::one_two_2_11_expects ... ok
test test_cases::two_matrices_with_comments::one_two_2_12_expects ... ok
test test_cases::two_matrices_with_comments::three_four_3_13_expects ... ok
test test_cases::two_matrices_with_comments::three_four_3_14_expects ... ok
test test_cases::two_matrices_with_comments::three_four_4_13_expects ... ok
test test_cases::two_matrices_with_comments::three_four_4_14_expects ... ok
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ source: tests/acceptance_tests.rs
expression: output
---
error: All literal values must be of the same type
error: Comments are not allowed in #[test_matrix]
error: Range bounds can only be an integer literal
error: Unbounded ranges are not supported
error: could not compile `matrices_compilation_errors` (lib test) due to 6 previous errors
error: could not compile `matrices_compilation_errors` (lib test) due to 5 previous errors
error: number too large to fit in target type
error[E0308]: mismatched types
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
source: tests/acceptance_tests.rs
expression: output
---
test result: ok. 54 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 70 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test test_cases::case_after_matrix::_1_expects ... ok
test test_cases::case_after_matrix::_2_expects ... ok
test test_cases::case_after_matrix::_3_expects ... ok
Expand Down Expand Up @@ -57,3 +57,19 @@ test test_cases::str_values_tuple::_one_blue_expects ... ok
test test_cases::str_values_tuple::_one_yellow_expects ... ok
test test_cases::str_values_tuple::_two_blue_expects ... ok
test test_cases::str_values_tuple::_two_yellow_expects ... ok
test test_cases::two_matrices::_1_11_expects ... ok
test test_cases::two_matrices::_1_12_expects ... ok
test test_cases::two_matrices::_2_11_expects ... ok
test test_cases::two_matrices::_2_12_expects ... ok
test test_cases::two_matrices::_3_13_expects ... ok
test test_cases::two_matrices::_3_14_expects ... ok
test test_cases::two_matrices::_4_13_expects ... ok
test test_cases::two_matrices::_4_14_expects ... ok
test test_cases::two_matrices_with_comments::one_two_1_11_expects ... ok
test test_cases::two_matrices_with_comments::one_two_1_12_expects ... ok
test test_cases::two_matrices_with_comments::one_two_2_11_expects ... ok
test test_cases::two_matrices_with_comments::one_two_2_12_expects ... ok
test test_cases::two_matrices_with_comments::three_four_3_13_expects ... ok
test test_cases::two_matrices_with_comments::three_four_3_14_expects ... ok
test test_cases::two_matrices_with_comments::three_four_4_13_expects ... ok
test test_cases::two_matrices_with_comments::three_four_4_14_expects ... ok

0 comments on commit 2fc16bf

Please sign in to comment.