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 fn assert_case_count(iter: impl IntoIterator, expected_count: usize) {
18 const MAX_SIZE_HINT: usize = 1_000;
19
20 let iter = iter.into_iter();
21 let (lo, hi) = iter.size_hint();
22 let actual_count = if Some(lo) == hi {
23 lo
24 } else if hi.is_none_or(|hi| hi > MAX_SIZE_HINT) {
25 return;
28 } else {
29 iter.count()
30 };
31
32 assert_eq!(
33 actual_count, expected_count,
34 "Unexpected number of test cases; use #[test_casing({actual_count}, ..)]"
35 );
36}
37
38#[doc(hidden)] pub trait ArgNames<T: fmt::Debug>: Copy + IntoIterator<Item = &'static str> {
41 fn print_with_args(self, args: &T) -> String;
42}
43
44impl<T: fmt::Debug> ArgNames<T> for [&'static str; 1] {
45 fn print_with_args(self, args: &T) -> String {
46 format!("{name} = {args:?}", name = self[0])
47 }
48}
49
50macro_rules! impl_arg_names {
51 ($n:tt => $($idx:tt: $arg_ty:ident),+) => {
52 impl<$($arg_ty : fmt::Debug,)+> ArgNames<($($arg_ty,)+)> for [&'static str; $n] {
53 fn print_with_args(self, args: &($($arg_ty,)+)) -> String {
54 use std::fmt::Write as _;
55
56 let mut buffer = String::new();
57 $(
58 write!(buffer, "{} = {:?}", self[$idx], args.$idx).unwrap();
59 if $idx + 1 < self.len() {
60 buffer.push_str(", ");
61 }
62 )+
63 buffer
64 }
65 }
66 };
67}
68
69impl_arg_names!(2 => 0: T, 1: U);
70impl_arg_names!(3 => 0: T, 1: U, 2: V);
71impl_arg_names!(4 => 0: T, 1: U, 2: V, 3: W);
72impl_arg_names!(5 => 0: T, 1: U, 2: V, 3: W, 4: X);
73impl_arg_names!(6 => 0: T, 1: U, 2: V, 3: W, 4: X, 5: Y);
74impl_arg_names!(7 => 0: T, 1: U, 2: V, 3: W, 4: X, 5: Y, 6: Z);
75
76pub struct TestCases<T> {
97 lazy: fn() -> Box<dyn Iterator<Item = T>>,
98}
99
100impl<T> fmt::Debug for TestCases<T> {
101 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
102 formatter.debug_struct("TestCases").finish_non_exhaustive()
103 }
104}
105
106impl<T> Clone for TestCases<T> {
107 fn clone(&self) -> Self {
108 *self
109 }
110}
111
112impl<T> Copy for TestCases<T> {}
113
114impl<T> TestCases<T> {
115 pub const fn new(lazy: fn() -> Box<dyn Iterator<Item = T>>) -> Self {
117 Self { lazy }
118 }
119}
120
121impl<T> IntoIterator for TestCases<T> {
122 type Item = T;
123 type IntoIter = Box<dyn Iterator<Item = T>>;
124
125 fn into_iter(self) -> Self::IntoIter {
126 (self.lazy)()
127 }
128}
129
130#[macro_export]
137macro_rules! cases {
138 ($iter:expr) => {
139 $crate::TestCases::<_>::new(|| {
140 std::boxed::Box::new(core::iter::IntoIterator::into_iter($iter))
141 })
142 };
143}
144
145#[derive(Debug, Clone, Copy)]
163pub struct Product<Ts>(pub Ts);
164
165impl<T, U> IntoIterator for Product<(T, U)>
166where
167 T: Clone + IntoIterator,
168 U: Clone + IntoIterator,
169{
170 type Item = (T::Item, U::Item);
171 type IntoIter = ProductIter<T, U>;
172
173 fn into_iter(self) -> Self::IntoIter {
174 let (first, second) = &self.0;
175 let second = second.clone().into_iter();
176 let size_hint = product_size_hint(&first.clone().into_iter(), &second);
177 ProductIter {
178 sources: self.0,
179 size_hint,
180 first_idx: 0,
181 second_iter: second.fuse(),
182 is_finished: false,
183 }
184 }
185}
186
187fn product_size_hint(first: &impl Iterator, second: &impl Iterator) -> (usize, Option<usize>) {
188 let (first_lo, first_hi) = first.size_hint();
189 let (second_lo, second_hi) = second.size_hint();
190 let lo = first_lo.saturating_mul(second_lo);
191 let hi = if let (Some(x), Some(y)) = (first_hi, second_hi) {
192 x.checked_mul(y)
193 } else {
194 None
195 };
196 (lo, hi)
197}
198
199macro_rules! impl_product {
200 ($head:ident: $head_ty:ident, $($tail:ident: $tail_ty:ident),+) => {
201 impl<$head_ty, $($tail_ty,)+> IntoIterator for Product<($head_ty, $($tail_ty,)+)>
202 where
203 $head_ty: 'static + Clone + IntoIterator,
204 $($tail_ty: 'static + Clone + IntoIterator,)+
205 {
206 type Item = ($head_ty::Item, $($tail_ty::Item,)+);
207 type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
208
209 fn into_iter(self) -> Self::IntoIter {
210 let ($head, $($tail,)+) = self.0;
211 let tail = Product(($($tail,)+));
212 let iter = Product(($head, tail))
213 .into_iter()
214 .map(|($head, ($($tail,)+))| ($head, $($tail,)+));
215 Box::new(iter)
216 }
217 }
218 };
219}
220
221impl_product!(t: T, u: U, v: V);
222impl_product!(t: T, u: U, v: V, w: W);
223impl_product!(t: T, u: U, v: V, w: W, x: X);
224impl_product!(t: T, u: U, v: V, w: W, x: X, y: Y);
225impl_product!(t: T, u: U, v: V, w: W, x: X, y: Y, z: Z);
226
227#[derive(Debug)]
229pub struct ProductIter<T: IntoIterator, U: IntoIterator> {
230 sources: (T, U),
231 size_hint: (usize, Option<usize>),
232 first_idx: usize,
233 second_iter: Fuse<U::IntoIter>,
234 is_finished: bool,
235}
236
237impl<T, U> Iterator for ProductIter<T, U>
238where
239 T: Clone + IntoIterator,
240 U: Clone + IntoIterator,
241{
242 type Item = (T::Item, U::Item);
243
244 fn next(&mut self) -> Option<Self::Item> {
245 if self.is_finished {
246 return None;
247 }
248
249 loop {
250 if let Some(second_case) = self.second_iter.next() {
251 let mut first_iter = self.sources.0.clone().into_iter();
252 let Some(first_case) = first_iter.nth(self.first_idx) else {
253 self.is_finished = true;
254 return None;
255 };
256 return Some((first_case, second_case));
257 }
258 self.first_idx += 1;
259 self.second_iter = self.sources.1.clone().into_iter().fuse();
260 }
261 }
262
263 fn size_hint(&self) -> (usize, Option<usize>) {
264 self.size_hint
265 }
266}
267
268impl<T, U> ExactSizeIterator for ProductIter<T, U>
269where
270 T: Clone + IntoIterator<IntoIter: ExactSizeIterator>,
271 U: Clone + IntoIterator<IntoIter: ExactSizeIterator>,
272{
273 fn len(&self) -> usize {
274 self.size_hint.0
276 }
277}
278
279#[cfg(doctest)]
280doc_comment::doctest!("../README.md");
281
282#[cfg(test)]
283mod tests {
284 use std::collections::HashSet;
285
286 use super::*;
287
288 #[test]
289 fn cartesian_product() {
290 let numbers = cases!(0..3);
291 let strings = cases!(["0", "1"]);
292 let prod = Product((numbers, strings)).into_iter();
293 assert_eq!(prod.size_hint(), (6, Some(6)));
294 let cases: Vec<_> = prod.into_iter().collect();
295 assert_eq!(
296 cases.as_slice(),
297 [(0, "0"), (0, "1"), (1, "0"), (1, "1"), (2, "0"), (2, "1")]
298 );
299
300 let booleans = [false, true];
301 let prod = Product((numbers, strings, booleans)).into_iter();
302 assert_eq!(prod.size_hint(), (12, Some(12)));
303 let cases: HashSet<_> = prod.collect();
304 assert_eq!(cases.len(), 12); }
306
307 #[test]
308 fn exact_size_iterator_for_product() {
309 let numbers = 0..3;
310 let strings = ["0", "1"];
311 let prod = Product((numbers, strings)).into_iter();
312 assert_eq!(prod.size_hint(), (6, Some(6)));
313 assert_eq!(prod.len(), 6);
314 let cases: Vec<_> = prod.into_iter().collect();
315 assert_eq!(
316 cases.as_slice(),
317 [(0, "0"), (0, "1"), (1, "0"), (1, "1"), (2, "0"), (2, "1")]
318 );
319 }
320
321 #[test]
322 fn unit_test_detection_works() {
323 assert!(option_env!("CARGO_TARGET_TMPDIR").is_none());
324 }
325}