diff --git a/crates/test-case-core/src/test_case.rs b/crates/test-case-core/src/test_case.rs index 105d421..bb37311 100644 --- a/crates/test-case-core/src/test_case.rs +++ b/crates/test-case-core/src/test_case.rs @@ -10,53 +10,62 @@ use syn::{parse_quote, Error, Expr, Ident, ItemFn, ReturnType, Token}; #[derive(Debug)] pub struct TestCase { args: Punctuated, - pub(crate) expression: Option, - pub(crate) comment: Option, + expression: Option, + name: Ident, } impl Parse for TestCase { fn parse(input: ParseStream) -> Result { - 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::().ok(); + let comment = input.parse::().ok(); + + Ok(Self::new_from_parsed(args, expression, comment)) } } +impl TestCase { + pub(crate) fn new>( + args: I, + expression: Option, + comment: Option, + ) -> Self { + Self::new_from_parsed(args.into_iter().collect(), expression, comment) + } + + pub(crate) fn new_from_parsed( + args: Punctuated, + expression: Option, + comment: Option, + ) -> Self { + let name = Self::test_case_name_ident(args.iter(), expression.as_ref(), comment.as_ref()); -impl From for TestCase -where - I: IntoIterator, -{ - 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>( + args: I, + expression: Option, + prefix: &str, + ) -> Self { + let parsed_args = args.into_iter().collect::>(); + 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 { @@ -118,4 +127,48 @@ impl TestCase { } } } + + fn test_case_name_ident<'a, I: Iterator>( + 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>( + 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>( + 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 + }) + } } diff --git a/crates/test-case-core/src/test_matrix/mod.rs b/crates/test-case-core/src/test_matrix/mod.rs index 41051a5..889c3c3 100644 --- a/crates/test-case-core/src/test_matrix/mod.rs +++ b/crates/test-case-core/src/test_matrix/mod.rs @@ -16,6 +16,7 @@ mod matrix_product; pub struct TestMatrix { variables: Vec>, expression: Option, + comment: Option, } impl TestMatrix { @@ -25,11 +26,18 @@ impl TestMatrix { pub fn cases(&self) -> impl Iterator { 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) + } }) } } @@ -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::() { - return Err(syn::Error::new( - c.span(), - "Comments are not allowed in #[test_matrix]", - )); - } - for arg in args { let values: Vec = match &arg { Expr::Array(v) => v.elems.iter().cloned().collect(), diff --git a/crates/test-case-macros/src/lib.rs b/crates/test-case-macros/src/lib.rs index b598095..82561da 100644 --- a/crates/test-case-macros/src/lib.rs +++ b/crates/test-case-macros/src/lib.rs @@ -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 { diff --git a/tests/acceptance_cases/matrices_compilation_errors/src/lib.rs b/tests/acceptance_cases/matrices_compilation_errors/src/lib.rs index 93337a0..805438a 100644 --- a/tests/acceptance_cases/matrices_compilation_errors/src/lib.rs +++ b/tests/acceptance_cases/matrices_compilation_errors/src/lib.rs @@ -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)] diff --git a/tests/acceptance_cases/matrices_support_basic_features/src/lib.rs b/tests/acceptance_cases/matrices_support_basic_features/src/lib.rs index 13b91c6..4724910 100644 --- a/tests/acceptance_cases/matrices_support_basic_features/src/lib.rs +++ b/tests/acceptance_cases/matrices_support_basic_features/src/lib.rs @@ -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] diff --git a/tests/snapshots/rust-nightly/acceptance__matrices_compilation_errors.snap b/tests/snapshots/rust-nightly/acceptance__matrices_compilation_errors.snap index 1d7a241..2617d4f 100644 --- a/tests/snapshots/rust-nightly/acceptance__matrices_compilation_errors.snap +++ b/tests/snapshots/rust-nightly/acceptance__matrices_compilation_errors.snap @@ -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 diff --git a/tests/snapshots/rust-nightly/acceptance__matrices_support_basic_features.snap b/tests/snapshots/rust-nightly/acceptance__matrices_support_basic_features.snap index be719b9..fa88fed 100644 --- a/tests/snapshots/rust-nightly/acceptance__matrices_support_basic_features.snap +++ b/tests/snapshots/rust-nightly/acceptance__matrices_support_basic_features.snap @@ -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 @@ -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 diff --git a/tests/snapshots/rust-stable/acceptance__matrices_compilation_errors.snap b/tests/snapshots/rust-stable/acceptance__matrices_compilation_errors.snap index 1d7a241..2617d4f 100644 --- a/tests/snapshots/rust-stable/acceptance__matrices_compilation_errors.snap +++ b/tests/snapshots/rust-stable/acceptance__matrices_compilation_errors.snap @@ -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 diff --git a/tests/snapshots/rust-stable/acceptance__matrices_support_basic_features.snap b/tests/snapshots/rust-stable/acceptance__matrices_support_basic_features.snap index be719b9..fa88fed 100644 --- a/tests/snapshots/rust-stable/acceptance__matrices_support_basic_features.snap +++ b/tests/snapshots/rust-stable/acceptance__matrices_support_basic_features.snap @@ -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 @@ -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