1use crate::description::Description;
16use crate::matcher::{Matcher, MatcherBase, MatcherResult};
17use crate::matcher_support::match_matrix::internal::{MatchMatrix, Requirements};
18use crate::matchers::eq_matcher::eq;
19use std::fmt::Debug;
20
21pub fn container_eq<ExpectedContainerT>(
56 expected: ExpectedContainerT,
57) -> ContainerEqMatcher<ExpectedContainerT>
58where
59 ExpectedContainerT: Debug,
60{
61 ContainerEqMatcher { expected }
62}
63
64#[derive(MatcherBase)]
65pub struct ContainerEqMatcher<ExpectedContainerT> {
66 expected: ExpectedContainerT,
67}
68
69impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT>
70 Matcher<ActualContainerT> for ContainerEqMatcher<ExpectedContainerT>
71where
72 ActualElementT: for<'a> PartialEq<&'a ExpectedElementT> + Debug + Copy,
73 ActualContainerT: for<'a> PartialEq<&'a ExpectedContainerT> + Debug + Copy,
74 ExpectedElementT: Debug,
75 ExpectedContainerT: Debug,
76 ActualContainerT: IntoIterator<Item = ActualElementT>,
77 for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
78{
79 fn matches(&self, actual: ActualContainerT) -> MatcherResult {
80 (actual == &self.expected).into()
81 }
82
83 fn explain_match(&self, actual: ActualContainerT) -> Description {
84 build_explanation(self.get_missing_items(actual), self.get_unexpected_items(actual)).into()
85 }
86
87 fn describe(&self, matcher_result: MatcherResult) -> Description {
88 match matcher_result {
89 MatcherResult::Match => format!("is equal to {:?}", self.expected).into(),
90 MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected).into(),
91 }
92 }
93}
94
95impl<ExpectedElementT, ExpectedContainerT> ContainerEqMatcher<ExpectedContainerT>
96where
97 for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
98{
99 fn get_missing_items<ActualElementT, ActualContainerT>(
100 &self,
101 actual: ActualContainerT,
102 ) -> Vec<&'_ ExpectedElementT>
103 where
104 ActualElementT: for<'a> PartialEq<&'a ExpectedElementT> + Copy,
105 ActualContainerT: for<'a> PartialEq<&'a ExpectedContainerT> + Copy,
106 ActualContainerT: IntoIterator<Item = ActualElementT>,
107 {
108 self.expected.into_iter().filter(|i| !actual.into_iter().any(|j| j == *i)).collect()
109 }
110
111 fn get_unexpected_items<ActualElementT, ActualContainerT>(
112 &self,
113 actual: ActualContainerT,
114 ) -> Vec<ActualElementT>
115 where
116 ActualElementT: for<'a> PartialEq<&'a ExpectedElementT> + Copy,
117 ActualContainerT: for<'a> PartialEq<&'a ExpectedContainerT> + Copy,
118 ActualContainerT: IntoIterator<Item = ActualElementT>,
119 {
120 actual.into_iter().filter(|i| !self.expected.into_iter().any(|j| i == &j)).collect()
121 }
122}
123
124fn build_explanation<T: Debug, U: Debug>(missing: Vec<T>, unexpected: Vec<U>) -> String {
125 match (missing.len(), unexpected.len()) {
126 (0, 0) => "which contains all the elements".to_string(),
128 (0, 1) => format!("which contains the unexpected element {:?}", unexpected[0]),
129 (0, _) => format!("which contains the unexpected elements {unexpected:?}",),
130 (1, 0) => format!("which is missing the element {:?}", missing[0]),
131 (1, 1) => {
132 format!(
133 "which is missing the element {:?} and contains the unexpected element {:?}",
134 missing[0], unexpected[0]
135 )
136 }
137 (1, _) => {
138 format!(
139 "which is missing the element {:?} and contains the unexpected elements {unexpected:?}",
140 missing[0]
141 )
142 }
143 (_, 0) => format!("which is missing the elements {missing:?}"),
144 (_, 1) => {
145 format!(
146 "which is missing the elements {missing:?} and contains the unexpected element {:?}",
147 unexpected[0]
148 )
149 }
150 (_, _) => {
151 format!(
152 "which is missing the elements {missing:?} and contains the unexpected elements {unexpected:?}",
153 )
154 }
155 }
156}
157
158impl<ExpectedContainerT> ContainerEqMatcher<ExpectedContainerT> {
159 pub fn ignore_order(self) -> IgnoringOrder<ExpectedContainerT> {
161 IgnoringOrder { expected: self.expected }
162 }
163}
164
165#[derive(MatcherBase)]
166pub struct IgnoringOrder<ExpectedContainerT> {
167 expected: ExpectedContainerT,
168}
169
170impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT>
172 Matcher<ActualContainerT> for IgnoringOrder<ExpectedContainerT>
173where
174 ActualElementT: Debug + Copy + for<'a> PartialEq<&'a ExpectedElementT>,
175 ActualContainerT: Debug + Copy + IntoIterator<Item = ActualElementT>,
176 ExpectedElementT: Debug,
177 for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
178{
179 fn matches(&self, actual: ActualContainerT) -> MatcherResult {
180 let expected: Vec<Box<dyn Matcher<ActualElementT>>> = self
181 .expected
182 .into_iter()
183 .map(|x| Box::new(eq(x)) as Box<dyn Matcher<ActualElementT>>)
184 .collect();
185 let match_matrix = MatchMatrix::generate(actual, &expected);
186 (match_matrix.is_match_for(Requirements::Subset)
190 && match_matrix.is_match_for(Requirements::Superset))
191 .into()
192 }
193
194 fn explain_match(&self, actual: ActualContainerT) -> Description {
195 let expected_items: Vec<&ExpectedElementT> = self.expected.into_iter().collect();
198 let actual_items: Vec<ActualElementT> = actual.into_iter().collect();
199
200 let expected_matchers: Vec<_> = expected_items
201 .iter()
202 .map(|&x| Box::new(eq(x)) as Box<dyn Matcher<ActualElementT>>)
203 .collect();
204 let match_matrix = MatchMatrix::generate(actual_items.iter().copied(), &expected_matchers);
205
206 let best_match = match_matrix.find_best_match();
207
208 let matches = best_match.get_matches().map(|(actual_idx, expected_idx)|{
211 Description::new().text(
212 format!(
213 "Actual element {:?} at index {actual_idx} is equal to expected element at index {expected_idx}.",
214 actual_items[actual_idx],
215 ))});
216
217 let unmatched_actual = best_match.get_unmatched_actual().map(|actual_idx| {
218 Description::new().text(
219 format!(
220 "Actual element {:?} at index {actual_idx} did not match any remaining expected element.",
221 actual_items[actual_idx],
222 ))
223 });
224
225 let unmatched_expected =
226 best_match.get_unmatched_expected().into_iter().map(|expected_idx| {
227 Description::new().text(format!(
228 "Expected element {:?} at index {expected_idx} did not match any remaining actual element.",
229 expected_items[expected_idx]
230 ))
231 });
232
233 Description::new()
234 .text("which does not have a perfect match. The best match found was:")
235 .collect(matches.chain(unmatched_actual).chain(unmatched_expected))
236 }
237
238 fn describe(&self, matcher_result: MatcherResult) -> Description {
239 Description::new()
240 .text(format!(
241 "{} all elements matching in any order:",
242 if matcher_result.into() { "contains" } else { "doesn't contain" },
243 ))
244 .nested(
245 self.expected
246 .into_iter()
247 .map(|element| format!("{element:?}"))
248 .collect::<Description>()
249 .bullet_list(),
250 )
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use crate::matcher::MatcherResult;
257 use crate::prelude::*;
258 use crate::Result;
259 use indoc::indoc;
260 use std::collections::HashSet;
261
262 #[test]
263 fn container_eq_returns_match_when_containers_match() -> Result<()> {
264 verify_that!(vec![1, 2, 3], container_eq(vec![1, 2, 3]))
265 }
266
267 #[test]
268 fn container_eq_matches_array_with_slice() -> Result<()> {
269 let value = &[1, 2, 3];
270 verify_that!(value, container_eq([1, 2, 3]))
271 }
272
273 #[test]
274 fn container_eq_matches_hash_set() -> Result<()> {
275 let value: HashSet<i32> = [1, 2, 3].into();
276 verify_that!(value, container_eq([1, 2, 3].into()))
277 }
278
279 #[test]
280 fn container_eq_full_error_message() -> Result<()> {
281 let result = verify_that!(vec![1, 3, 2], container_eq(vec![1, 2, 3]));
282 verify_that!(
283 result,
284 err(displays_as(contains_substring(indoc!(
285 "
286 Value of: vec![1, 3, 2]
287 Expected: is equal to [1, 2, 3]
288 Actual: [1, 3, 2],
289 which contains all the elements
290 "
291 ))))
292 )
293 }
294
295 #[test]
296 fn container_eq_returns_mismatch_when_elements_out_of_order() -> Result<()> {
297 verify_that!(
298 container_eq(vec![1, 2, 3]).explain_match(&vec![1, 3, 2]),
299 displays_as(eq("which contains all the elements"))
300 )
301 }
302
303 #[test]
304 fn container_eq_mismatch_shows_missing_elements_in_container() -> Result<()> {
305 verify_that!(
306 container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2]),
307 displays_as(eq("which is missing the element 3"))
308 )
309 }
310
311 #[test]
312 fn container_eq_mismatch_shows_surplus_elements_in_container() -> Result<()> {
313 verify_that!(
314 container_eq(vec![1, 2]).explain_match(&vec![1, 2, 3]),
315 displays_as(eq("which contains the unexpected element 3"))
316 )
317 }
318
319 #[test]
320 fn container_eq_mismatch_shows_missing_and_surplus_elements_in_container() -> Result<()> {
321 verify_that!(
322 container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2, 4]),
323 displays_as(eq("which is missing the element 3 and contains the unexpected element 4"))
324 )
325 }
326
327 #[test]
328 fn container_eq_mismatch_does_not_show_duplicated_element() -> Result<()> {
329 verify_that!(
330 container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2, 3, 3]),
331 displays_as(eq("which contains all the elements"))
332 )
333 }
334
335 #[test]
336 fn container_eq_matches_owned_vec_with_array() -> Result<()> {
337 let vector = vec![123, 234];
338 verify_that!(vector, container_eq([123, 234]))
339 }
340
341 #[test]
342 fn container_eq_matches_owned_vec_of_owned_strings_with_slice_of_string_references(
343 ) -> Result<()> {
344 let vector = vec!["A string".to_string(), "Another string".to_string()];
345 verify_that!(vector, container_eq(["A string", "Another string"]))
346 }
347
348 #[test]
349 fn container_eq_matches_owned_vec_of_owned_strings_with_shorter_slice_of_string_references(
350 ) -> Result<()> {
351 let actual = vec!["A string".to_string(), "Another string".to_string()];
352 let matcher = container_eq(["A string"]);
353
354 let result = matcher.matches(&actual);
355
356 verify_that!(result, eq(MatcherResult::NoMatch))
357 }
358
359 #[test]
360 fn container_eq_mismatch_with_slice_shows_missing_elements_in_container() -> Result<()> {
361 verify_that!(
362 container_eq([1, 2, 3]).explain_match(&vec![1, 2]),
363 displays_as(eq("which is missing the element 3"))
364 )
365 }
366
367 #[test]
368 fn container_eq_mismatch_with_str_slice_shows_missing_elements_in_container() -> Result<()> {
369 verify_that!(
370 container_eq(["A", "B", "C"]).explain_match(&vec!["A".to_string(), "B".to_string()]),
371 displays_as(eq("which is missing the element \"C\""))
372 )
373 }
374
375 #[test]
376 fn container_eq_mismatch_with_str_slice_shows_surplus_elements_in_container() -> Result<()> {
377 verify_that!(
378 container_eq(["A", "B"]).explain_match(&vec![
379 "A".to_string(),
380 "B".to_string(),
381 "C".to_string()
382 ]),
383 displays_as(eq("which contains the unexpected element \"C\""))
384 )
385 }
386
387 #[test]
388 fn ignoring_order_match() -> Result<()> {
389 verify_that!(vec!["a", "b"], container_eq(["b", "a"]).ignore_order())
390 }
391
392 #[test]
393 fn ignoring_order_mismatch() -> Result<()> {
394 verify_that!(vec!["a", "b"], not(container_eq(["1", "2"]).ignore_order()))
395 }
396
397 #[test]
398 fn ignoring_order_mismatch_explain() -> Result<()> {
399 let expected_err = verify_that!(vec!["a", "b"], container_eq(["1", "2"]).ignore_order());
400 verify_that!(
401 expected_err,
402 err(displays_as(contains_substring(indoc!(
403 r#"
404 Value of: vec!["a", "b"]
405 Expected: contains all elements matching in any order:
406 * "1"
407 * "2"
408 Actual: ["a", "b"],
409 which does not have a perfect match. The best match found was:
410 Actual element "a" at index 0 did not match any remaining expected element.
411 Actual element "b" at index 1 did not match any remaining expected element.
412 Expected element "1" at index 0 did not match any remaining actual element.
413 Expected element "2" at index 1 did not match any remaining actual element.
414 "#
415 ))))
416 )
417 }
418
419 #[test]
420 fn ignoring_order_unaccounted_extra_expected() -> Result<()> {
421 verify_that!(vec!["a", "b"], not(container_eq(["a", "b", "a"]).ignore_order()))
422 }
423
424 #[test]
425 fn ignoring_order_unaccounted_extra_expected_explain() -> Result<()> {
426 let expected_err =
427 verify_that!(vec!["a", "b"], container_eq(["a", "b", "a"]).ignore_order());
428 verify_that!(
429 expected_err,
430 err(displays_as(contains_substring(indoc!(
431 r#"
432 Value of: vec!["a", "b"]
433 Expected: contains all elements matching in any order:
434 * "a"
435 * "b"
436 * "a"
437 Actual: ["a", "b"],
438 which does not have a perfect match. The best match found was:
439 Actual element "a" at index 0 is equal to expected element at index 0.
440 Actual element "b" at index 1 is equal to expected element at index 1.
441 Expected element "a" at index 2 did not match any remaining actual element.
442 "#
443 ))))
444 )
445 }
446
447 #[test]
448 fn ignoring_order_unaccounted_extra_actual() -> Result<()> {
449 verify_that!(vec!["a", "b", "a"], not(container_eq(["b", "a"]).ignore_order()))
450 }
451
452 #[test]
453 fn ignoring_order_unaccounted_extra_actual_explain() -> Result<()> {
454 let expected_err =
455 verify_that!(vec!["a", "b", "a"], container_eq(["b", "a"]).ignore_order());
456
457 verify_that!(
458 expected_err,
459 err(displays_as(contains_substring(indoc!(
460 r#"
461 Value of: vec!["a", "b", "a"]
462 Expected: contains all elements matching in any order:
463 * "b"
464 * "a"
465 Actual: ["a", "b", "a"],
466 which does not have a perfect match. The best match found was:
467 Actual element "a" at index 0 is equal to expected element at index 1.
468 Actual element "b" at index 1 is equal to expected element at index 0.
469 Actual element "a" at index 2 did not match any remaining expected element.
470 "#
471 ))))
472 )
473 }
474
475 #[test]
476 fn ignoring_order_on_sets() -> Result<()> {
477 let mut actual = std::collections::HashSet::new();
478 actual.insert("b");
479 actual.insert("a");
480 actual.insert("c");
481 verify_that!(actual, container_eq(["c", "b", "a"]).ignore_order())
482 }
483
484 #[test]
485 fn ignoring_order_on_sets_explain() -> Result<()> {
486 let mut actual = std::collections::HashSet::new();
487 actual.insert("b");
488 actual.insert("a");
489 actual.insert("c");
490 let expected_err = verify_that!(actual, container_eq(["c", "a"]).ignore_order());
491 verify_that!(
492 expected_err,
493 err(displays_as(contains_regex(indoc!(
494 r#"
495 Value of: actual
496 Expected: contains all elements matching in any order:
497 \* "c"
498 \* "a"
499 Actual: \{"\w", "\w", "\w"\},
500 which does not have a perfect match. The best match found was:
501 Actual element "\w" at index \d is equal to expected element at index \d\.
502 Actual element "\w" at index \d is equal to expected element at index \d\.
503 Actual element "\w" at index \d did not match any remaining expected element\.
504 "#
505 ))))
506 )
507 }
508
509 #[test]
510 fn ignoring_order_on_number_sets() -> Result<()> {
511 let mut actual = std::collections::HashSet::new();
512 actual.insert(1);
513 actual.insert(2);
514 actual.insert(3);
515 verify_that!(actual, container_eq([3, 2, 1]).ignore_order())
516 }
517}