ironbeam/testing/assertions.rs
1//! Assertion functions for testing pipeline outputs.
2//!
3//! This module provides specialized assertion functions for comparing
4//! collections produced by pipelines with expected results.
5
6use std::collections::{HashMap, HashSet};
7use std::fmt::Debug;
8use std::hash::{BuildHasher, Hash};
9
10/// Assert that two collections are equal in order and content.
11///
12/// This function compares two vectors element-by-element and panics with
13/// a detailed message if they differ.
14///
15/// # Panics
16///
17/// Panics if the collections differ in length or content.
18///
19/// # Example
20///
21/// ```
22/// use ironbeam::testing::assert_collections_equal;
23///
24/// let actual = vec![1, 2, 3];
25/// let expected = vec![1, 2, 3];
26/// assert_collections_equal(&actual, &expected);
27/// ```
28pub fn assert_collections_equal<T: Debug + PartialEq>(actual: &[T], expected: &[T]) {
29 assert_eq!(
30 actual.len(),
31 expected.len(),
32 "Collection length mismatch:\n Expected length: {}\n Actual length: {}\n Expected: {expected:?}\n Actual: {actual:?}",
33 expected.len(),
34 actual.len()
35 );
36
37 for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
38 assert_eq!(
39 a, e,
40 "Collection mismatch at index {i}:\n Expected: {e:?}\n Actual: {a:?}\n Full expected: {expected:?}\n Full actual: {actual:?}"
41 );
42 }
43}
44
45/// Assert that two collections contain the same elements, ignoring order.
46///
47/// This function converts both collections to sets and compares them.
48/// Useful for testing transformations that may not preserve element order.
49///
50/// # Panics
51///
52/// Panics if the collections differ in content (ignoring order).
53///
54/// # Example
55///
56/// ```
57/// use ironbeam::testing::assert_collections_unordered_equal;
58///
59/// let actual = vec![3, 1, 2];
60/// let expected = vec![1, 2, 3];
61/// assert_collections_unordered_equal(&actual, &expected);
62/// ```
63pub fn assert_collections_unordered_equal<T: Debug + Eq + Hash>(actual: &[T], expected: &[T]) {
64 let actual_set: HashSet<_> = actual.iter().collect();
65 let expected_set: HashSet<_> = expected.iter().collect();
66
67 assert_eq!(
68 actual.len(),
69 expected.len(),
70 "Collection length mismatch:\n Expected length: {}\n Actual length: {}\n Expected: {expected:?}\n Actual: {actual:?}",
71 expected.len(),
72 actual.len()
73 );
74
75 if actual_set != expected_set {
76 let missing: Vec<_> = expected_set.difference(&actual_set).collect();
77 let extra: Vec<_> = actual_set.difference(&expected_set).collect();
78
79 panic!(
80 "Collection content mismatch:\n Missing elements: {missing:?}\n Extra elements: {extra:?}\n Expected: {expected:?}\n Actual: {actual:?}"
81 );
82 }
83}
84
85/// Assert that two collections of key-value pairs are equal after sorting by key.
86///
87/// This function is specifically designed for testing grouped or aggregated data
88/// where the order of keys may vary between runs but should be consistent when sorted.
89///
90/// # Panics
91///
92/// Panics if the collections differ after sorting by key.
93///
94/// # Example
95///
96/// ```
97/// use ironbeam::testing::assert_kv_collections_equal;
98///
99/// let actual = vec![("b", 2), ("a", 1)];
100/// let expected = vec![("a", 1), ("b", 2)];
101/// assert_kv_collections_equal(actual, expected);
102/// ```
103pub fn assert_kv_collections_equal<K, V>(mut actual: Vec<(K, V)>, mut expected: Vec<(K, V)>)
104where
105 K: Debug + Ord,
106 V: Debug + PartialEq,
107{
108 actual.sort_by(|a, b| a.0.cmp(&b.0));
109 expected.sort_by(|a, b| a.0.cmp(&b.0));
110
111 assert_eq!(
112 actual.len(),
113 expected.len(),
114 "Collection length mismatch:\n Expected length: {}\n Actual length: {}\n Expected: {expected:?}\n Actual: {actual:?}",
115 expected.len(),
116 actual.len()
117 );
118
119 for (i, ((ak, av), (ek, ev))) in actual.iter().zip(expected.iter()).enumerate() {
120 assert!(
121 !(ak != ek || av != ev),
122 "Collection mismatch at index {i} after sorting:\n Expected: ({ek:?}, {ev:?})\n Actual: ({ak:?}, {av:?})\n Full expected: {expected:?}\n Full actual: {actual:?}"
123 );
124 }
125}
126
127/// Assert that two collections of key-value pairs with grouped values are equal.
128///
129/// This function compares grouped data (e.g., output from `group_by_key`) where
130/// each key maps to a vector of values. The keys are compared in sorted order,
131/// and the value vectors are compared as unordered sets.
132///
133/// # Panics
134///
135/// Panics if the collections differ in keys or values.
136///
137/// # Example
138///
139/// ```
140/// use ironbeam::testing::assert_grouped_kv_equal;
141///
142/// let actual = vec![("a", vec![1, 2]), ("b", vec![3])];
143/// let expected = vec![("a", vec![2, 1]), ("b", vec![3])];
144/// assert_grouped_kv_equal(actual, expected);
145/// ```
146pub fn assert_grouped_kv_equal<K, V>(mut actual: Vec<(K, Vec<V>)>, mut expected: Vec<(K, Vec<V>)>)
147where
148 K: Debug + Ord,
149 V: Debug + Eq + Hash,
150{
151 actual.sort_by(|a, b| a.0.cmp(&b.0));
152 expected.sort_by(|a, b| a.0.cmp(&b.0));
153
154 assert_eq!(
155 actual.len(),
156 expected.len(),
157 "Collection length mismatch:\n Expected length: {}\n Actual length: {}",
158 expected.len(),
159 actual.len()
160 );
161
162 for (i, ((ak, av), (ek, ev))) in actual.iter().zip(expected.iter()).enumerate() {
163 assert_eq!(
164 ak, ek,
165 "Key mismatch at index {i}:\n Expected: {ek:?}\n Actual: {ak:?}"
166 );
167
168 let av_set: HashSet<_> = av.iter().collect();
169 let ev_set: HashSet<_> = ev.iter().collect();
170
171 assert_eq!(
172 av_set, ev_set,
173 "Value mismatch for key {ak:?} at index {i}:\n Expected values: {ev:?}\n Actual values: {av:?}"
174 );
175 }
176}
177
178/// Assert that all elements in a collection satisfy a predicate.
179///
180/// # Panics
181///
182/// Panics if any element does not satisfy the predicate.
183///
184/// # Example
185///
186/// ```
187/// use ironbeam::testing::assert_all;
188///
189/// let data = vec![2, 4, 6, 8];
190/// assert_all(&data, |x| x % 2 == 0);
191/// ```
192pub fn assert_all<T: Debug>(collection: &[T], predicate: impl Fn(&T) -> bool) {
193 for (i, item) in collection.iter().enumerate() {
194 assert!(
195 predicate(item),
196 "Predicate failed for element at index {i}:\n Element: {item:?}\n Collection: {collection:?}"
197 );
198 }
199}
200
201/// Assert that at least one element in a collection satisfies a predicate.
202///
203/// # Panics
204///
205/// Panics if no elements satisfy the predicate.
206///
207/// # Example
208///
209/// ```
210/// use ironbeam::testing::assert_any;
211///
212/// let data = vec![1, 2, 3, 4];
213/// assert_any(&data, |x| x % 2 == 0);
214/// ```
215pub fn assert_any<T: Debug>(collection: &[T], predicate: impl Fn(&T) -> bool) {
216 assert!(
217 collection.iter().any(&predicate),
218 "No elements satisfied the predicate:\n Collection: {collection:?}"
219 );
220}
221
222/// Assert that no elements in a collection satisfy a predicate.
223///
224/// # Panics
225///
226/// Panics if any element satisfies the predicate.
227///
228/// # Example
229///
230/// ```
231/// use ironbeam::testing::assert_none;
232///
233/// let data = vec![1, 3, 5, 7];
234/// assert_none(&data, |x| x % 2 == 0);
235/// ```
236pub fn assert_none<T: Debug>(collection: &[T], predicate: impl Fn(&T) -> bool) {
237 for (i, item) in collection.iter().enumerate() {
238 assert!(
239 !predicate(item),
240 "Predicate unexpectedly succeeded for element at index {i}:\n Element: {item:?}\n Collection: {collection:?}"
241 );
242 }
243}
244
245/// Assert that a collection has the expected size.
246///
247/// # Panics
248///
249/// Panics if the collection size doesn't match the expected size.
250///
251/// # Example
252///
253/// ```
254/// use ironbeam::testing::assert_collection_size;
255///
256/// let data = vec![1, 2, 3];
257/// assert_collection_size(&data, 3);
258/// ```
259pub fn assert_collection_size<T>(collection: &[T], expected_size: usize) {
260 assert_eq!(
261 collection.len(),
262 expected_size,
263 "Collection size mismatch:\n Expected: {expected_size}\n Actual: {}",
264 collection.len()
265 );
266}
267
268/// Assert that a collection contains a specific element.
269///
270/// # Panics
271///
272/// Panics if the element is not found in the collection.
273///
274/// # Example
275///
276/// ```
277/// use ironbeam::testing::assert_contains;
278///
279/// let data = vec![1, 2, 3, 4];
280/// assert_contains(&data, &3);
281/// ```
282pub fn assert_contains<T: Debug + PartialEq>(collection: &[T], element: &T) {
283 assert!(
284 collection.contains(element),
285 "Element not found in collection:\n Looking for: {element:?}\n Collection: {collection:?}"
286 );
287}
288
289/// Assert that two hashmaps are equal.
290///
291/// # Panics
292///
293/// Panics if the hashmaps differ in keys or values.
294///
295/// # Example
296///
297/// ```
298/// use ironbeam::testing::assert_maps_equal;
299/// use std::collections::HashMap;
300///
301/// let mut actual = HashMap::new();
302/// actual.insert("a", 1);
303/// actual.insert("b", 2);
304///
305/// let mut expected = HashMap::new();
306/// expected.insert("a", 1);
307/// expected.insert("b", 2);
308///
309/// assert_maps_equal(&actual, &expected);
310/// ```
311pub fn assert_maps_equal<K, V, S: BuildHasher>(
312 actual: &HashMap<K, V, S>,
313 expected: &HashMap<K, V, S>,
314) where
315 K: Debug + Eq + Hash,
316 V: Debug + PartialEq,
317{
318 assert_eq!(
319 actual.len(),
320 expected.len(),
321 "HashMap size mismatch:\n Expected size: {}\n Actual size: {}\n Expected: {expected:?}\n Actual: {actual:?}",
322 expected.len(),
323 actual.len()
324 );
325
326 for (key, expected_value) in expected {
327 match actual.get(key) {
328 Some(actual_value) if actual_value == expected_value => {}
329 Some(actual_value) => {
330 panic!(
331 "HashMap value mismatch for key {key:?}:\n Expected: {expected_value:?}\n Actual: {actual_value:?}"
332 );
333 }
334 None => {
335 panic!("HashMap missing key: {key:?}");
336 }
337 }
338 }
339}