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
38pub struct TestCases<T> {
59 lazy: fn() -> Box<dyn Iterator<Item = T>>,
60}
61
62impl<T> fmt::Debug for TestCases<T> {
63 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
64 formatter.debug_struct("TestCases").finish_non_exhaustive()
65 }
66}
67
68impl<T> Clone for TestCases<T> {
69 fn clone(&self) -> Self {
70 *self
71 }
72}
73
74impl<T> Copy for TestCases<T> {}
75
76impl<T> TestCases<T> {
77 pub const fn new(lazy: fn() -> Box<dyn Iterator<Item = T>>) -> Self {
79 Self { lazy }
80 }
81}
82
83impl<T> IntoIterator for TestCases<T> {
84 type Item = T;
85 type IntoIter = Box<dyn Iterator<Item = T>>;
86
87 fn into_iter(self) -> Self::IntoIter {
88 (self.lazy)()
89 }
90}
91
92#[macro_export]
99macro_rules! cases {
100 ($iter:expr) => {
101 $crate::TestCases::<_>::new(|| {
102 std::boxed::Box::new(core::iter::IntoIterator::into_iter($iter))
103 })
104 };
105}
106
107#[derive(Debug, Clone, Copy)]
125pub struct Product<Ts>(pub Ts);
126
127impl<T, U> IntoIterator for Product<(T, U)>
128where
129 T: Clone + IntoIterator,
130 U: Clone + IntoIterator,
131{
132 type Item = (T::Item, U::Item);
133 type IntoIter = ProductIter<T, U>;
134
135 fn into_iter(self) -> Self::IntoIter {
136 let (first, second) = &self.0;
137 let second = second.clone().into_iter();
138 let size_hint = product_size_hint(&first.clone().into_iter(), &second);
139 ProductIter {
140 sources: self.0,
141 size_hint,
142 first_idx: 0,
143 second_iter: second.fuse(),
144 is_finished: false,
145 }
146 }
147}
148
149fn product_size_hint(first: &impl Iterator, second: &impl Iterator) -> (usize, Option<usize>) {
150 let (first_lo, first_hi) = first.size_hint();
151 let (second_lo, second_hi) = second.size_hint();
152 let lo = first_lo.saturating_mul(second_lo);
153 let hi = if let (Some(x), Some(y)) = (first_hi, second_hi) {
154 x.checked_mul(y)
155 } else {
156 None
157 };
158 (lo, hi)
159}
160
161macro_rules! impl_product {
162 ($head:ident: $head_ty:ident, $($tail:ident: $tail_ty:ident),+) => {
163 impl<$head_ty, $($tail_ty,)+> IntoIterator for Product<($head_ty, $($tail_ty,)+)>
164 where
165 $head_ty: 'static + Clone + IntoIterator,
166 $($tail_ty: 'static + Clone + IntoIterator,)+
167 {
168 type Item = ($head_ty::Item, $($tail_ty::Item,)+);
169 type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
170
171 fn into_iter(self) -> Self::IntoIter {
172 let ($head, $($tail,)+) = self.0;
173 let tail = Product(($($tail,)+));
174 let iter = Product(($head, tail))
175 .into_iter()
176 .map(|($head, ($($tail,)+))| ($head, $($tail,)+));
177 Box::new(iter)
178 }
179 }
180 };
181}
182
183impl_product!(t: T, u: U, v: V);
184impl_product!(t: T, u: U, v: V, w: W);
185impl_product!(t: T, u: U, v: V, w: W, x: X);
186impl_product!(t: T, u: U, v: V, w: W, x: X, y: Y);
187impl_product!(t: T, u: U, v: V, w: W, x: X, y: Y, z: Z);
188
189#[derive(Debug)]
191pub struct ProductIter<T: IntoIterator, U: IntoIterator> {
192 sources: (T, U),
193 size_hint: (usize, Option<usize>),
194 first_idx: usize,
195 second_iter: Fuse<U::IntoIter>,
196 is_finished: bool,
197}
198
199impl<T, U> Iterator for ProductIter<T, U>
200where
201 T: Clone + IntoIterator,
202 U: Clone + IntoIterator,
203{
204 type Item = (T::Item, U::Item);
205
206 fn next(&mut self) -> Option<Self::Item> {
207 if self.is_finished {
208 return None;
209 }
210
211 loop {
212 if let Some(second_case) = self.second_iter.next() {
213 let mut first_iter = self.sources.0.clone().into_iter();
214 let Some(first_case) = first_iter.nth(self.first_idx) else {
215 self.is_finished = true;
216 return None;
217 };
218 return Some((first_case, second_case));
219 }
220 self.first_idx += 1;
221 self.second_iter = self.sources.1.clone().into_iter().fuse();
222 }
223 }
224
225 fn size_hint(&self) -> (usize, Option<usize>) {
226 self.size_hint
227 }
228}
229
230impl<T, U> ExactSizeIterator for ProductIter<T, U>
231where
232 T: Clone + IntoIterator<IntoIter: ExactSizeIterator>,
233 U: Clone + IntoIterator<IntoIter: ExactSizeIterator>,
234{
235 fn len(&self) -> usize {
236 self.size_hint.0
238 }
239}
240
241#[cfg(doctest)]
242doc_comment::doctest!("../README.md");
243
244#[cfg(test)]
245mod tests {
246 use std::collections::HashSet;
247
248 use super::*;
249
250 #[test]
251 fn cartesian_product() {
252 let numbers = cases!(0..3);
253 let strings = cases!(["0", "1"]);
254 let prod = Product((numbers, strings)).into_iter();
255 assert_eq!(prod.size_hint(), (6, Some(6)));
256 let cases: Vec<_> = prod.into_iter().collect();
257 assert_eq!(
258 cases.as_slice(),
259 [(0, "0"), (0, "1"), (1, "0"), (1, "1"), (2, "0"), (2, "1")]
260 );
261
262 let booleans = [false, true];
263 let prod = Product((numbers, strings, booleans)).into_iter();
264 assert_eq!(prod.size_hint(), (12, Some(12)));
265 let cases: HashSet<_> = prod.collect();
266 assert_eq!(cases.len(), 12); }
268
269 #[test]
270 fn exact_size_iterator_for_product() {
271 let numbers = 0..3;
272 let strings = ["0", "1"];
273 let prod = Product((numbers, strings)).into_iter();
274 assert_eq!(prod.size_hint(), (6, Some(6)));
275 assert_eq!(prod.len(), 6);
276 let cases: Vec<_> = prod.into_iter().collect();
277 assert_eq!(
278 cases.as_slice(),
279 [(0, "0"), (0, "1"), (1, "0"), (1, "1"), (2, "0"), (2, "1")]
280 );
281 }
282
283 #[test]
284 fn unit_test_detection_works() {
285 assert!(option_env!("CARGO_TARGET_TMPDIR").is_none());
286 }
287}