1#![doc(hidden)]
19
20#[doc(hidden)]
24pub mod __internal {
25 use crate::{
26 description::Description,
27 matcher::{Describable, Matcher, MatcherResult},
28 matcher_support::count_elements::count_elements,
29 matchers::__internal::ContainerContainsOrderedMatcher,
30 matchers::containers::{
31 OwnedItems, RefItems,
32 container_contains::{PairBorrow, Requirements},
33 },
34 };
35 use alloc::{boxed::Box, collections::BTreeSet, vec::Vec};
36 use core::{borrow::Borrow, fmt::Debug, marker::PhantomData};
37
38 pub struct ContainerContainsUnorderedMatcher<
44 'matchers,
45 ContainerT: ?Sized,
46 T: Debug,
47 ModeT,
48 const N: usize,
49 > {
50 elements: [Box<dyn Matcher<T> + 'matchers>; N],
51 requirements: Requirements,
52 _phantom: PhantomData<(*const ContainerT, ModeT)>,
53 }
54
55 impl<'matchers, ContainerT: ?Sized, T: Debug, ModeT, const N: usize>
56 ContainerContainsUnorderedMatcher<'matchers, ContainerT, T, ModeT, N>
57 {
58 #[doc(hidden)]
65 pub fn new(
66 elements: [Box<dyn Matcher<T> + 'matchers>; N],
67 requirements: Requirements,
68 ) -> Self {
69 Self { elements, requirements, _phantom: PhantomData }
70 }
71
72 pub fn in_order(
75 self,
76 ) -> ContainerContainsOrderedMatcher<'matchers, ContainerT, T, ModeT, N> {
77 ContainerContainsOrderedMatcher::new(self.elements, self.requirements)
78 }
79
80 fn matches_with_iter<ItemT: Borrow<T>>(
81 &self,
82 n: usize,
83 actual: impl Iterator<Item = ItemT>,
84 ) -> MatcherResult {
85 let match_matrix = MatchMatrix::generate(actual, n, &self.elements);
86 match_matrix.is_match_for(self.requirements).into()
87 }
88
89 fn explain_match_with_iters<ItemT1: Borrow<T>, ItemT2: Borrow<T>>(
97 &self,
98 size_mismatch: Option<Description>,
99 n: usize,
100 iter_for_generate: impl Iterator<Item = ItemT1>,
101 iter_for_explain: impl Iterator<Item = ItemT2>,
102 ) -> Description {
103 if let Some(explanation) = size_mismatch {
104 return explanation;
105 }
106 let match_matrix = MatchMatrix::generate(iter_for_generate, n, &self.elements);
107 if let Some(unmatchable) = match_matrix.explain_unmatchable(self.requirements) {
108 return unmatchable;
109 }
110 let best_match = match_matrix.find_best_match();
111 best_match
112 .get_explanation(iter_for_explain, &self.elements, self.requirements)
113 .unwrap_or("whose elements all match".into())
114 }
115 }
116
117 impl<'matchers, T: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher<ContainerT>
118 for ContainerContainsUnorderedMatcher<'matchers, ContainerT, T, RefItems, N>
119 where
120 for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
121 {
122 fn matches(&self, actual: &ContainerT) -> MatcherResult {
123 self.matches_with_iter(count_elements(actual), actual.into_iter())
124 }
125
126 fn explain_match(&self, actual: &ContainerT) -> Description {
127 self.explain_match_with_iters(
128 self.requirements.explain_size_mismatch(actual, N),
129 count_elements(actual),
130 actual.into_iter(),
131 actual.into_iter(),
132 )
133 }
134 }
135
136 impl<'matchers, T: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher<ContainerT>
137 for ContainerContainsUnorderedMatcher<'matchers, ContainerT, T, OwnedItems, N>
138 where
139 for<'b> &'b ContainerT: IntoIterator<Item = T>,
140 {
141 fn matches(&self, actual: &ContainerT) -> MatcherResult {
142 self.matches_with_iter(count_elements(actual), actual.into_iter())
143 }
144
145 fn explain_match(&self, actual: &ContainerT) -> Description {
146 self.explain_match_with_iters(
147 self.requirements.explain_size_mismatch(actual, N),
148 count_elements(actual),
149 actual.into_iter(),
150 actual.into_iter(),
151 )
152 }
153 }
154
155 impl<'matchers, T: Debug, ContainerT: ?Sized, ModeT, const N: usize> Describable
156 for ContainerContainsUnorderedMatcher<'matchers, ContainerT, T, ModeT, N>
157 {
158 fn describe(&self, matcher_result: MatcherResult) -> Description {
159 format!(
160 "{} elements matching in any order:\n{}",
161 if matcher_result.into() { "contains" } else { "doesn't contain" },
162 self.elements
163 .iter()
164 .map(|matcher| matcher.describe(MatcherResult::Match))
165 .collect::<Description>()
166 .enumerate()
167 .indent()
168 )
169 .into()
170 }
171 }
172
173 type KeyValueMatcher<'a, KeyT, ValueT> =
174 (Box<dyn Matcher<KeyT> + 'a>, Box<dyn Matcher<ValueT> + 'a>);
175
176 #[doc(hidden)]
181 pub struct MapContainsMatcher<'matchers, ContainerT, KeyT, ValueT, ModeT, const N: usize>
182 where
183 ContainerT: ?Sized,
184 KeyT: Debug,
185 ValueT: Debug,
186 {
187 elements: [KeyValueMatcher<'matchers, KeyT, ValueT>; N],
188 requirements: Requirements,
189 phantom: PhantomData<(ModeT, ContainerT)>,
190 }
191
192 impl<'matchers, ContainerT: ?Sized, KeyT: Debug, ValueT: Debug, ModeT, const N: usize>
193 MapContainsMatcher<'matchers, ContainerT, KeyT, ValueT, ModeT, N>
194 {
195 pub fn new(
196 elements: [KeyValueMatcher<'matchers, KeyT, ValueT>; N],
197 requirements: Requirements,
198 ) -> Self {
199 Self { elements, requirements, phantom: PhantomData }
200 }
201
202 fn matches_with_iter<ItemT: PairBorrow<KeyT, ValueT>>(
203 &self,
204 n: usize,
205 actual: impl Iterator<Item = ItemT>,
206 ) -> MatcherResult {
207 let match_matrix = MatchMatrix::generate_for_map(actual, n, &self.elements);
208 match_matrix.is_match_for(self.requirements).into()
209 }
210
211 fn explain_match_with_iters<
212 ItemT1: PairBorrow<KeyT, ValueT>,
213 ItemT2: PairBorrow<KeyT, ValueT>,
214 >(
215 &self,
216 size_mismatch: Option<Description>,
217 n: usize,
218 iter_for_generate: impl Iterator<Item = ItemT1>,
219 iter_for_explain: impl Iterator<Item = ItemT2>,
220 ) -> Description {
221 if let Some(explanation) = size_mismatch {
222 return explanation;
223 }
224 let match_matrix = MatchMatrix::generate_for_map(iter_for_generate, n, &self.elements);
225 if let Some(unmatchable) = match_matrix.explain_unmatchable(self.requirements) {
226 return unmatchable;
227 }
228 let best_match = match_matrix.find_best_match();
229 best_match
230 .get_explanation_for_map(iter_for_explain, &self.elements, self.requirements)
231 .unwrap_or("whose elements all match".into())
232 }
233 }
234
235 impl<'matchers, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized, const N: usize>
236 Matcher<ContainerT> for MapContainsMatcher<'matchers, ContainerT, KeyT, ValueT, RefItems, N>
237 where
238 for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
239 {
240 fn matches(&self, actual: &ContainerT) -> MatcherResult {
241 self.matches_with_iter(count_elements(actual), actual.into_iter())
242 }
243
244 fn explain_match(&self, actual: &ContainerT) -> Description {
245 self.explain_match_with_iters(
246 self.requirements.explain_size_mismatch(actual, N),
247 count_elements(actual),
248 actual.into_iter(),
249 actual.into_iter(),
250 )
251 }
252 }
253
254 impl<'matchers, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized, const N: usize>
255 Matcher<ContainerT>
256 for MapContainsMatcher<'matchers, ContainerT, KeyT, ValueT, OwnedItems, N>
257 where
258 for<'b> &'b ContainerT: IntoIterator<Item = (KeyT, ValueT)>,
259 {
260 fn matches(&self, actual: &ContainerT) -> MatcherResult {
261 self.matches_with_iter(count_elements(actual), actual.into_iter())
262 }
263
264 fn explain_match(&self, actual: &ContainerT) -> Description {
265 self.explain_match_with_iters(
266 self.requirements.explain_size_mismatch(actual, N),
267 count_elements(actual),
268 actual.into_iter(),
269 actual.into_iter(),
270 )
271 }
272 }
273
274 impl<'matchers, KeyT: Debug, ValueT: Debug, ContainerT: ?Sized, ModeT, const N: usize>
275 Describable for MapContainsMatcher<'matchers, ContainerT, KeyT, ValueT, ModeT, N>
276 {
277 fn describe(&self, matcher_result: MatcherResult) -> Description {
278 format!(
279 "{} elements matching in any order:\n{}",
280 if matcher_result.into() { "contains" } else { "doesn't contain" },
281 self.elements
282 .iter()
283 .map(|(key_matcher, value_matcher)| format!(
284 "{} => {}",
285 key_matcher.describe(MatcherResult::Match),
286 value_matcher.describe(MatcherResult::Match)
287 ))
288 .collect::<Description>()
289 .indent()
290 )
291 .into()
292 }
293 }
294
295 struct MatchMatrix<const N: usize>(Vec<[MatcherResult; N]>);
297
298 impl<const N: usize> MatchMatrix<N> {
299 fn generate<'matchers, T: Debug + 'matchers, ItemT: Borrow<T>>(
300 actual_items: impl Iterator<Item = ItemT>,
301 n_actual: usize,
302 expected: &[Box<dyn Matcher<T> + 'matchers>; N],
303 ) -> Self {
304 let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; n_actual]);
305 for (actual_idx, actual_item) in actual_items.enumerate() {
306 for (expected_idx, expected) in expected.iter().enumerate() {
307 matrix.0[actual_idx][expected_idx] = expected.matches(actual_item.borrow());
308 }
309 }
310 matrix
311 }
312
313 fn generate_for_map<
314 'matchers,
315 KeyT: Debug,
316 ValueT: Debug,
317 ItemT: PairBorrow<KeyT, ValueT>,
318 >(
319 actual_items: impl Iterator<Item = ItemT>,
320 n_actual: usize,
321 expected: &[KeyValueMatcher<'matchers, KeyT, ValueT>; N],
322 ) -> Self {
323 let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; n_actual]);
324 for (actual_idx, actual_item) in actual_items.enumerate() {
325 for (expected_idx, (expected_key, expected_value)) in expected.iter().enumerate() {
326 matrix.0[actual_idx][expected_idx] =
327 (expected_key.matches(actual_item.borrow_key()).into()
328 && expected_value.matches(actual_item.borrow_value()).into())
329 .into();
330 }
331 }
332 matrix
333 }
334
335 fn is_match_for(&self, requirements: Requirements) -> bool {
336 match requirements {
337 Requirements::PerfectMatch => {
338 !self.find_unmatchable_elements().has_unmatchable_elements()
339 && self.find_best_match().is_full_match()
340 }
341 Requirements::Superset => {
342 !self.find_unmatched_expected().has_unmatchable_elements()
343 && self.find_best_match().is_superset_match()
344 }
345 Requirements::Subset => {
346 !self.find_unmatched_actual().has_unmatchable_elements()
347 && self.find_best_match().is_subset_match()
348 }
349 }
350 }
351
352 fn explain_unmatchable(&self, requirements: Requirements) -> Option<Description> {
353 let unmatchable_elements = match requirements {
354 Requirements::PerfectMatch => self.find_unmatchable_elements(),
355 Requirements::Superset => self.find_unmatched_expected(),
356 Requirements::Subset => self.find_unmatched_actual(),
357 };
358 unmatchable_elements.get_explanation()
359 }
360
361 fn find_unmatchable_elements(&self) -> UnmatchableElements<N> {
366 let unmatchable_actual =
367 self.0.iter().map(|row| row.iter().all(|&e| e.is_no_match())).collect();
368 let mut unmatchable_expected = [false; N];
369 for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() {
370 *expected = self.0.iter().map(|row| row[col_idx]).all(|e| e.is_no_match());
371 }
372 UnmatchableElements { unmatchable_actual, unmatchable_expected }
373 }
374
375 fn find_unmatched_expected(&self) -> UnmatchableElements<N> {
376 let mut unmatchable_expected = [false; N];
377 for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() {
378 *expected = self.0.iter().map(|row| row[col_idx]).all(|e| e.is_no_match());
379 }
380 UnmatchableElements { unmatchable_actual: vec![false; N], unmatchable_expected }
381 }
382
383 fn find_unmatched_actual(&self) -> UnmatchableElements<N> {
384 let unmatchable_actual =
385 self.0.iter().map(|row| row.iter().all(|e| e.is_no_match())).collect();
386 UnmatchableElements { unmatchable_actual, unmatchable_expected: [false; N] }
387 }
388
389 fn find_best_match(&self) -> BestMatch<N> {
454 let mut actual_match = vec![None; self.0.len()];
455 let mut expected_match: [Option<usize>; N] = [None; N];
456 for actual_idx in 0..self.0.len() {
470 assert!(actual_match[actual_idx].is_none());
471 let mut seen = [false; N];
472 self.try_augment(actual_idx, &mut seen, &mut actual_match, &mut expected_match);
473 }
474 BestMatch(actual_match)
475 }
476
477 fn try_augment(
496 &self,
497 actual_idx: usize,
498 seen: &mut [bool; N],
499 actual_match: &mut [Option<usize>],
500 expected_match: &mut [Option<usize>; N],
501 ) -> bool {
502 for expected_idx in 0..N {
503 if seen[expected_idx] {
504 continue;
505 }
506 if self.0[actual_idx][expected_idx].is_no_match() {
507 continue;
508 }
509 seen[expected_idx] = true;
511 if expected_match[expected_idx].is_none()
523 || self.try_augment(
524 expected_match[expected_idx].unwrap(),
525 seen,
526 actual_match,
527 expected_match,
528 )
529 {
530 expected_match[expected_idx] = Some(actual_idx);
535 actual_match[actual_idx] = Some(expected_idx);
536 return true;
537 }
538 }
539 false
540 }
541 }
542
543 struct UnmatchableElements<const N: usize> {
549 unmatchable_actual: Vec<bool>,
550 unmatchable_expected: [bool; N],
551 }
552
553 impl<const N: usize> UnmatchableElements<N> {
554 fn has_unmatchable_elements(&self) -> bool {
555 self.unmatchable_actual.iter().any(|b| *b)
556 || self.unmatchable_expected.iter().any(|b| *b)
557 }
558
559 fn get_explanation(&self) -> Option<Description> {
560 let unmatchable_actual = self.unmatchable_actual();
561 let actual_idx = unmatchable_actual
562 .iter()
563 .map(|idx| format!("#{}", idx))
564 .collect::<Vec<_>>()
565 .join(", ");
566 let unmatchable_expected = self.unmatchable_expected();
567 let expected_idx = unmatchable_expected
568 .iter()
569 .map(|idx| format!("#{}", idx))
570 .collect::<Vec<_>>()
571 .join(", ");
572 match (unmatchable_actual.len(), unmatchable_expected.len()) {
573 (0, 0) => None,
574 (1, 0) => {
575 Some(format!("whose element {actual_idx} does not match any expected elements").into())
576 }
577 (_, 0) => {
578 Some(format!("whose elements {actual_idx} do not match any expected elements",).into())
579 }
580 (0, 1) => Some(format!(
581 "which has no element matching the expected element {expected_idx}"
582 ).into()),
583 (0, _) => Some(format!(
584 "which has no elements matching the expected elements {expected_idx}"
585 ).into()),
586 (1, 1) => Some(format!(
587 "whose element {actual_idx} does not match any expected elements and no elements match the expected element {expected_idx}"
588 ).into()),
589 (_, 1) => Some(format!(
590 "whose elements {actual_idx} do not match any expected elements and no elements match the expected element {expected_idx}"
591 ).into()),
592 (1, _) => Some(format!(
593 "whose element {actual_idx} does not match any expected elements and no elements match the expected elements {expected_idx}"
594 ).into()),
595 (_, _) => Some(format!(
596 "whose elements {actual_idx} do not match any expected elements and no elements match the expected elements {expected_idx}"
597 ).into()),
598 }
599 }
600
601 fn unmatchable_actual(&self) -> Vec<usize> {
602 self.unmatchable_actual
603 .iter()
604 .enumerate()
605 .filter_map(|(idx, b)| if *b { Some(idx) } else { None })
606 .collect()
607 }
608
609 fn unmatchable_expected(&self) -> Vec<usize> {
610 self.unmatchable_expected
611 .iter()
612 .enumerate()
613 .filter_map(|(idx, b)| if *b { Some(idx) } else { None })
614 .collect()
615 }
616 }
617
618 struct BestMatch<const N: usize>(Vec<Option<usize>>);
626
627 impl<const N: usize> BestMatch<N> {
628 fn is_full_match(&self) -> bool {
629 self.0.iter().all(|o| o.is_some())
630 }
631
632 fn is_subset_match(&self) -> bool {
633 self.is_full_match()
634 }
635
636 fn is_superset_match(&self) -> bool {
637 self.get_unmatched_expected().is_empty()
638 }
639
640 fn get_matches(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
641 self.0.iter().enumerate().filter_map(|(actual_idx, maybe_expected_idx)| {
642 maybe_expected_idx.map(|expected_idx| (actual_idx, expected_idx))
643 })
644 }
645
646 fn get_unmatched_actual(&self) -> impl Iterator<Item = usize> + '_ {
647 self.0
648 .iter()
649 .enumerate()
650 .filter(|&(_, o)| o.is_none())
651 .map(|(actual_idx, _)| actual_idx)
652 }
653
654 fn get_unmatched_expected(&self) -> Vec<usize> {
655 let matched_expected: BTreeSet<_> = self.0.iter().flatten().collect();
656 (0..N).filter(|expected_idx| !matched_expected.contains(expected_idx)).collect()
657 }
658
659 fn get_explanation<'matchers, T: Debug, ItemT: Borrow<T>>(
660 &self,
661 actual_items: impl Iterator<Item = ItemT>,
662 expected: &[Box<dyn Matcher<T> + 'matchers>; N],
663 requirements: Requirements,
664 ) -> Option<Description> {
665 let actual: Vec<ItemT> = actual_items.collect();
666 if self.is_full_match() {
667 return None;
668 }
669
670 let matches = self.get_matches().map(|(actual_idx, expected_idx)| {
671 format!(
672 "Actual element {:?} at index {actual_idx} matched expected element `{}` at index {expected_idx}.",
673 actual[actual_idx].borrow(),
674 expected[expected_idx].describe(MatcherResult::Match),
675 )
676 });
677
678 let unmatched_actual = self.get_unmatched_actual().map(|actual_idx| {
679 format!(
680 "Actual element {:#?} at index {actual_idx} did not match any remaining expected element.",
681 actual[actual_idx].borrow()
682 )
683 });
684
685 let unmatched_expected =
686 self.get_unmatched_expected().into_iter().map(|expected_idx| {
687 format!(
688 "Expected element `{}` at index {expected_idx} did not match any remaining actual element.",
689 expected[expected_idx].describe(MatcherResult::Match)
690 )
691 });
692
693 let best_match = matches
694 .chain(unmatched_actual)
695 .chain(unmatched_expected)
696 .collect::<Description>()
697 .indent();
698 Some(
699 format!(
700 "which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
701 )
702 .into(),
703 )
704 }
705
706 fn get_explanation_for_map<
707 'matchers,
708 KeyT: Debug,
709 ValueT: Debug,
710 ItemT: PairBorrow<KeyT, ValueT>,
711 >(
712 &self,
713 actual_items: impl Iterator<Item = ItemT>,
714 expected: &[KeyValueMatcher<'matchers, KeyT, ValueT>; N],
715 requirements: Requirements,
716 ) -> Option<Description> {
717 let actual: Vec<ItemT> = actual_items.collect();
718 if self.is_full_match() {
719 return None;
720 }
721
722 let matches = self.get_matches().map(|(actual_idx, expected_idx)| {
723 format!(
724 "Actual element {:?} => {:?} at index {actual_idx} matched expected element `{}` => `{}` at index {expected_idx}.",
725 actual[actual_idx].borrow_key(),
726 actual[actual_idx].borrow_value(),
727 expected[expected_idx].0.describe(MatcherResult::Match),
728 expected[expected_idx].1.describe(MatcherResult::Match),
729 )
730 });
731
732 let unmatched_actual = self.get_unmatched_actual().map(|actual_idx| {
733 format!(
734 "Actual element {:#?} => {:#?} at index {actual_idx} did not match any remaining expected element.",
735 actual[actual_idx].borrow_key(),
736 actual[actual_idx].borrow_value(),
737 )
738 });
739
740 let unmatched_expected =
741 self.get_unmatched_expected().into_iter().map(|expected_idx| {
742 format!(
743 "Expected element `{}` => `{}` at index {expected_idx} did not match any remaining actual element.",
744 expected[expected_idx].0.describe(MatcherResult::Match),
745 expected[expected_idx].1.describe(MatcherResult::Match),
746 )
747 });
748
749 let best_match = matches
750 .chain(unmatched_actual)
751 .chain(unmatched_expected)
752 .collect::<Description>()
753 .indent();
754 Some(
755 format!(
756 "which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
757 )
758 .into(),
759 )
760 }
761 }
762}
763
764#[cfg(all(test, feature = "std"))]
765mod tests {
766 use super::__internal::MapContainsMatcher;
767 use crate::matcher::Matcher;
768 use crate::matchers::containers::RefItems;
769 use crate::prelude::*;
770 use indoc::indoc;
771 use std::collections::HashMap;
772
773 #[test]
774 fn has_correct_description_for_map() -> TestResult<()> {
775 let matchers = ((eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three")));
781 let result = verify_that!(
782 HashMap::from([(1, "one")]),
783 contains_exactly![
784 (matchers.0.0 => matchers.0.1),
785 (matchers.1.0 => matchers.1.1),
786 (matchers.2.0 => matchers.2.1)
787 ]
788 );
789 verify_that!(
790 result,
791 err(displays_as(contains_substring(indoc!(
792 "
793 contains elements matching in any order:
794 is equal to 2 => is equal to \"Two\"
795 is equal to 1 => is equal to \"One\"
796 is equal to 3 => is equal to \"Three\""
797 ))))
798 )
799 }
800
801 #[cfg(feature = "regex")]
802 #[test]
803 fn contains_exactly_description_no_full_match_with_map() -> TestResult<()> {
804 let matchers = ((anything(), eq(1)), (anything(), eq(2)), (anything(), eq(2)));
810 let matcher: MapContainsMatcher<HashMap<u32, u32>, _, _, RefItems, 3> = contains_exactly![
811 (matchers.0.0 => matchers.0.1),
812 (matchers.1.0 => matchers.1.1),
813 (matchers.2.0 => matchers.2.1),
814 ];
815 let value: HashMap<u32, u32> = HashMap::from_iter([(0, 1), (1, 1), (2, 2)]);
816 verify_that!(
817 matcher.explain_match(&value),
818 displays_as(
819 contains_regex(
820 "Actual element 2 => 2 at index [0-2] matched expected element `is anything` => `is equal to 2` at index [0-2]."
821 )
822 )
823 .and(
824 displays_as(
825 contains_regex(
826 "Actual element [0-1] => [0-1] at index [0-2] did not match any remaining expected element."
827 )
828 )
829 )
830 .and(
831 displays_as(
832 contains_substring(
833 "Expected element `is anything` => `is equal to 2` at index 2 did not match any remaining actual element."
834 )
835 )
836 )
837 )
838 }
839}