1use std::{fmt, iter::Fuse};
4
5#[doc(hidden)] pub fn case<I: IntoIterator>(iter: I, index: usize) -> I::Item
8where
9 I::Item: fmt::Debug,
10{
11 iter.into_iter().nth(index).unwrap_or_else(|| {
12 panic!("case #{index} not provided from the cases iterator");
13 })
14}
15
16#[doc(hidden)] pub trait ArgNames<T: fmt::Debug>: Copy + IntoIterator<Item = &'static str> {
19 fn print_with_args(self, args: &T) -> String;
20}
21
22impl<T: fmt::Debug> ArgNames<T> for [&'static str; 1] {
23 fn print_with_args(self, args: &T) -> String {
24 format!("{name} = {args:?}", name = self[0])
25 }
26}
27
28macro_rules! impl_arg_names {
29 ($n:tt => $($idx:tt: $arg_ty:ident),+) => {
30 impl<$($arg_ty : fmt::Debug,)+> ArgNames<($($arg_ty,)+)> for [&'static str; $n] {
31 fn print_with_args(self, args: &($($arg_ty,)+)) -> String {
32 use std::fmt::Write as _;
33
34 let mut buffer = String::new();
35 $(
36 write!(buffer, "{} = {:?}", self[$idx], args.$idx).unwrap();
37 if $idx + 1 < self.len() {
38 buffer.push_str(", ");
39 }
40 )+
41 buffer
42 }
43 }
44 };
45}
46
47impl_arg_names!(2 => 0: T, 1: U);
48impl_arg_names!(3 => 0: T, 1: U, 2: V);
49impl_arg_names!(4 => 0: T, 1: U, 2: V, 3: W);
50impl_arg_names!(5 => 0: T, 1: U, 2: V, 3: W, 4: X);
51impl_arg_names!(6 => 0: T, 1: U, 2: V, 3: W, 4: X, 5: Y);
52impl_arg_names!(7 => 0: T, 1: U, 2: V, 3: W, 4: X, 5: Y, 6: Z);
53
54pub struct TestCases<T> {
75 lazy: fn() -> Box<dyn Iterator<Item = T>>,
76}
77
78impl<T> fmt::Debug for TestCases<T> {
79 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
80 formatter.debug_struct("TestCases").finish_non_exhaustive()
81 }
82}
83
84impl<T> Clone for TestCases<T> {
85 fn clone(&self) -> Self {
86 *self
87 }
88}
89
90impl<T> Copy for TestCases<T> {}
91
92impl<T> TestCases<T> {
93 pub const fn new(lazy: fn() -> Box<dyn Iterator<Item = T>>) -> Self {
95 Self { lazy }
96 }
97}
98
99impl<T> IntoIterator for TestCases<T> {
100 type Item = T;
101 type IntoIter = Box<dyn Iterator<Item = T>>;
102
103 fn into_iter(self) -> Self::IntoIter {
104 (self.lazy)()
105 }
106}
107
108#[macro_export]
115macro_rules! cases {
116 ($iter:expr) => {
117 $crate::TestCases::<_>::new(|| {
118 std::boxed::Box::new(core::iter::IntoIterator::into_iter($iter))
119 })
120 };
121}
122
123#[derive(Debug, Clone, Copy)]
141pub struct Product<Ts>(pub Ts);
142
143impl<T, U> IntoIterator for Product<(T, U)>
144where
145 T: Clone + IntoIterator,
146 U: Clone + IntoIterator,
147{
148 type Item = (T::Item, U::Item);
149 type IntoIter = ProductIter<T, U>;
150
151 fn into_iter(self) -> Self::IntoIter {
152 let (_, second) = &self.0;
153 let second = second.clone();
154 ProductIter {
155 sources: self.0,
156 first_idx: 0,
157 second_iter: second.into_iter().fuse(),
158 is_finished: false,
159 }
160 }
161}
162
163macro_rules! impl_product {
164 ($head:ident: $head_ty:ident, $($tail:ident: $tail_ty:ident),+) => {
165 impl<$head_ty, $($tail_ty,)+> IntoIterator for Product<($head_ty, $($tail_ty,)+)>
166 where
167 $head_ty: 'static + Clone + IntoIterator,
168 $($tail_ty: 'static + Clone + IntoIterator,)+
169 {
170 type Item = ($head_ty::Item, $($tail_ty::Item,)+);
171 type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
172
173 fn into_iter(self) -> Self::IntoIter {
174 let ($head, $($tail,)+) = self.0;
175 let tail = Product(($($tail,)+));
176 let iter = Product(($head, tail))
177 .into_iter()
178 .map(|($head, ($($tail,)+))| ($head, $($tail,)+));
179 Box::new(iter)
180 }
181 }
182 };
183}
184
185impl_product!(t: T, u: U, v: V);
186impl_product!(t: T, u: U, v: V, w: W);
187impl_product!(t: T, u: U, v: V, w: W, x: X);
188impl_product!(t: T, u: U, v: V, w: W, x: X, y: Y);
189impl_product!(t: T, u: U, v: V, w: W, x: X, y: Y, z: Z);
190
191#[derive(Debug)]
193pub struct ProductIter<T: IntoIterator, U: IntoIterator> {
194 sources: (T, U),
195 first_idx: usize,
196 second_iter: Fuse<U::IntoIter>,
197 is_finished: bool,
198}
199
200impl<T, U> Iterator for ProductIter<T, U>
201where
202 T: Clone + IntoIterator,
203 U: Clone + IntoIterator,
204{
205 type Item = (T::Item, U::Item);
206
207 fn next(&mut self) -> Option<Self::Item> {
208 if self.is_finished {
209 return None;
210 }
211
212 loop {
213 if let Some(second_case) = self.second_iter.next() {
214 let mut first_iter = self.sources.0.clone().into_iter();
215 let Some(first_case) = first_iter.nth(self.first_idx) else {
216 self.is_finished = true;
217 return None;
218 };
219 return Some((first_case, second_case));
220 }
221 self.first_idx += 1;
222 self.second_iter = self.sources.1.clone().into_iter().fuse();
223 }
224 }
225}
226
227#[cfg(doctest)]
228doc_comment::doctest!("../README.md");
229
230#[cfg(test)]
231mod tests {
232 use std::collections::HashSet;
233
234 use super::*;
235
236 #[test]
237 fn cartesian_product() {
238 let numbers = cases!(0..3);
239 let strings = cases!(["0", "1"]);
240 let cases: Vec<_> = Product((numbers, strings)).into_iter().collect();
241 assert_eq!(
242 cases.as_slice(),
243 [(0, "0"), (0, "1"), (1, "0"), (1, "1"), (2, "0"), (2, "1")]
244 );
245
246 let booleans = [false, true];
247 let cases: HashSet<_> = Product((numbers, strings, booleans)).into_iter().collect();
248 assert_eq!(cases.len(), 12); }
250
251 #[test]
252 fn unit_test_detection_works() {
253 assert!(option_env!("CARGO_TARGET_TMPDIR").is_none());
254 }
255}