test_case_core/test_matrix/
mod.rs
1use std::{iter, mem};
2
3use proc_macro2::{Literal, Span};
4use syn::{
5 parse::{Parse, ParseStream},
6 punctuated::Punctuated,
7 spanned::Spanned,
8 Expr, ExprLit, ExprRange, Lit, RangeLimits, Token,
9};
10
11use crate::{comment::TestCaseComment, expr::TestCaseExpression, TestCase};
12
13mod matrix_product;
14
15#[derive(Debug, Default)]
16pub struct TestMatrix {
17 variables: Vec<Vec<Expr>>,
18 expression: Option<TestCaseExpression>,
19 comment: Option<TestCaseComment>,
20}
21
22impl TestMatrix {
23 pub fn push_argument(&mut self, values: Vec<Expr>) {
24 self.variables.push(values);
25 }
26
27 pub fn cases(&self) -> impl Iterator<Item = TestCase> {
28 let expression = self.expression.clone();
29 let comment = self.comment.clone();
30
31 matrix_product::multi_cartesian_product(self.variables.iter().cloned()).map(move |v| {
32 if let Some(comment) = comment.clone() {
33 TestCase::new_with_prefixed_name(
34 v,
35 expression.clone(),
36 comment.comment.value().as_ref(),
37 )
38 } else {
39 TestCase::new(v, expression.clone(), None)
40 }
41 })
42 }
43}
44
45impl Parse for TestMatrix {
46 fn parse(input: ParseStream) -> syn::Result<Self> {
47 let args: Punctuated<Expr, Token![,]> = Punctuated::parse_separated_nonempty(input)?;
48
49 let expression = (!input.is_empty()).then(|| input.parse()).transpose();
50 let comment = (!input.is_empty()).then(|| input.parse()).transpose();
51 let (expression, comment) = match (expression, comment) {
55 (Err(expression), Err(_comment)) => return Err(expression),
56 (expression, comment) => (expression.ok().flatten(), comment.ok().flatten()),
57 };
58
59 let mut matrix = TestMatrix {
60 expression,
61 comment,
62 ..Default::default()
63 };
64
65 for arg in args {
66 let values: Vec<Expr> = match &arg {
67 Expr::Array(v) => v.elems.iter().cloned().collect(),
68 Expr::Tuple(v) => v.elems.iter().cloned().collect(),
69 Expr::Range(ExprRange {
70 start, limits, end, ..
71 }) => {
72 let start = isize_from_range_expr(limits.span(), start.as_deref())?;
73 let end = isize_from_range_expr(limits.span(), end.as_deref())?;
74 let range: Box<dyn Iterator<Item = isize>> = match limits {
75 RangeLimits::HalfOpen(_) => Box::from(start..end),
76 RangeLimits::Closed(_) => Box::from(start..=end),
77 };
78 range
79 .map(|n| {
80 let mut lit = Lit::new(Literal::isize_unsuffixed(n));
81 lit.set_span(arg.span());
82 Expr::from(ExprLit { lit, attrs: vec![] })
83 })
84 .collect()
85 }
86 v => iter::once(v.clone()).collect(),
87 };
88
89 let mut value_literal_type = None;
90 for expr in &values {
91 if let Expr::Lit(ExprLit { lit, .. }) = expr {
92 let first_literal_type =
93 *value_literal_type.get_or_insert_with(|| mem::discriminant(lit));
94 if first_literal_type != mem::discriminant(lit) {
95 return Err(syn::Error::new(
96 lit.span(),
97 "All literal values must be of the same type",
98 ));
99 }
100 }
101 }
102 matrix.push_argument(values);
103 }
104
105 Ok(matrix)
106 }
107}
108
109fn isize_from_range_expr(limits_span: Span, expr: Option<&Expr>) -> syn::Result<isize> {
110 match expr {
111 Some(Expr::Lit(ExprLit {
112 lit: Lit::Int(n), ..
113 })) => n.base10_parse(),
114 Some(e) => Err(syn::Error::new(
115 e.span(),
116 "Range bounds can only be an integer literal",
117 )),
118 None => Err(syn::Error::new(
119 limits_span,
120 "Unbounded ranges are not supported",
121 )),
122 }
123}