1use crate::{
17 description::Description,
18 matcher::{Describable, Matcher, MatcherResult},
19 matchers::{
20 containers::{
21 OwnedItems, RefItems, container_contains::unordered_matcher::__internal::MatchMatrix,
22 },
23 eq, eq_deref_of,
24 },
25};
26use alloc::{
27 string::{String, ToString},
28 vec::Vec,
29};
30use core::{fmt::Debug, marker::PhantomData};
31
32use super::container_contains::Requirements;
33
34pub fn container_eq<ExpectedContainerT, Mode>(
121 expected: ExpectedContainerT,
122) -> ContainerEqMatcher<ExpectedContainerT, Mode> {
123 ContainerEqMatcher { expected, ignore_order: false, phantom: Default::default() }
124}
125
126pub struct ContainerEqMatcher<ExpectedContainerT, Mode> {
129 expected: ExpectedContainerT,
130 ignore_order: bool,
131 phantom: PhantomData<Mode>,
132}
133
134impl<ExpectedContainerT, Mode> ContainerEqMatcher<ExpectedContainerT, Mode> {
135 pub fn ignoring_order(self) -> Self {
142 Self { ignore_order: true, ..self }
143 }
144}
145
146impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT>
147 Matcher<ActualContainerT> for ContainerEqMatcher<ExpectedContainerT, RefItems>
148where
149 ActualElementT: PartialEq<ExpectedElementT> + Debug + ?Sized,
150 ActualContainerT: PartialEq<ExpectedContainerT> + Debug + ?Sized,
151 ExpectedElementT: Debug,
152 ExpectedContainerT: Debug,
153 for<'a> &'a ActualContainerT: IntoIterator<Item = &'a ActualElementT>,
154 for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
155{
156 fn matches(&self, actual: &ActualContainerT) -> MatcherResult {
157 if self.ignore_order {
158 let expected: Vec<_> = self.expected.into_iter().map(eq_deref_of).collect();
159 let match_matrix = MatchMatrix::generate_for_fixed_matcher::<_, ActualElementT, _>(
160 actual.into_iter(),
161 expected.len(),
162 &expected,
163 );
164 match_matrix.is_match_for(Requirements::PerfectMatch).into()
165 } else {
166 (*actual == self.expected).into()
167 }
168 }
169
170 fn explain_match(&self, actual: &ActualContainerT) -> Description {
171 build_explanation(
172 self.get_missing_items(actual),
173 self.get_unexpected_items(actual),
174 self.matches(actual),
175 )
176 .into()
177 }
178}
179
180impl<ExpectedElementT, ExpectedContainerT> ContainerEqMatcher<ExpectedContainerT, RefItems>
181where
182 for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
183{
184 fn get_missing_items<ActualElementT, ActualContainerT>(
185 &self,
186 actual: &ActualContainerT,
187 ) -> Vec<&ExpectedElementT>
188 where
189 ActualElementT: PartialEq<ExpectedElementT> + ?Sized,
190 ActualContainerT: PartialEq<ExpectedContainerT> + ?Sized,
191 ExpectedElementT: Debug,
192 for<'a> &'a ActualContainerT: IntoIterator<Item = &'a ActualElementT>,
193 {
194 self.expected.into_iter().filter(|&i| !actual.into_iter().any(|j| j == i)).collect()
195 }
196
197 fn get_unexpected_items<'a, ActualElementT, ActualContainerT>(
198 &self,
199 actual: &'a ActualContainerT,
200 ) -> Vec<&'a ActualElementT>
201 where
202 ActualElementT: PartialEq<ExpectedElementT> + ?Sized,
203 ActualContainerT: PartialEq<ExpectedContainerT> + ?Sized,
204 ExpectedElementT: Debug,
205 for<'b> &'b ActualContainerT: IntoIterator<Item = &'b ActualElementT>,
206 {
207 actual.into_iter().filter(|&i| !self.expected.into_iter().any(|j| i == j)).collect()
208 }
209}
210
211impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT>
212 Matcher<ActualContainerT> for ContainerEqMatcher<ExpectedContainerT, OwnedItems>
213where
214 ActualElementT: PartialEq<ExpectedElementT> + Debug,
215 ActualContainerT: PartialEq<ExpectedContainerT> + Debug + ?Sized,
216 ExpectedElementT: Debug,
217 ExpectedContainerT: Debug,
218 for<'a> &'a ActualContainerT: IntoIterator<Item = ActualElementT>,
219 for<'a> &'a ExpectedContainerT: IntoIterator<Item = ExpectedElementT>,
220{
221 fn matches(&self, actual: &ActualContainerT) -> MatcherResult {
222 if self.ignore_order {
223 let expected: Vec<_> = self.expected.into_iter().map(eq).collect();
224 let match_matrix = MatchMatrix::generate_for_fixed_matcher(
225 actual.into_iter(),
226 expected.len(),
227 &expected,
228 );
229 match_matrix.is_match_for(Requirements::PerfectMatch).into()
230 } else {
231 (*actual == self.expected).into()
232 }
233 }
234
235 fn explain_match(&self, actual: &ActualContainerT) -> Description {
236 build_explanation(
237 self.get_missing_items(actual),
238 self.get_unexpected_items(actual),
239 self.matches(actual),
240 )
241 .into()
242 }
243}
244
245impl<ExpectedElementT, ExpectedContainerT> ContainerEqMatcher<ExpectedContainerT, OwnedItems>
246where
247 for<'a> &'a ExpectedContainerT: IntoIterator<Item = ExpectedElementT>,
248{
249 fn get_missing_items<ActualElementT, ActualContainerT>(
250 &self,
251 actual: &ActualContainerT,
252 ) -> Vec<ExpectedElementT>
253 where
254 ActualElementT: PartialEq<ExpectedElementT>,
255 ActualContainerT: PartialEq<ExpectedContainerT> + ?Sized,
256 ExpectedElementT: Debug,
257 for<'a> &'a ActualContainerT: IntoIterator<Item = ActualElementT>,
258 {
259 self.expected.into_iter().filter(|i| !actual.into_iter().any(|j| j == *i)).collect()
260 }
261
262 fn get_unexpected_items<'a, ActualElementT, ActualContainerT>(
263 &self,
264 actual: &'a ActualContainerT,
265 ) -> Vec<ActualElementT>
266 where
267 ActualElementT: PartialEq<ExpectedElementT>,
268 ActualContainerT: PartialEq<ExpectedContainerT> + ?Sized,
269 ExpectedElementT: Debug,
270 for<'b> &'b ActualContainerT: IntoIterator<Item = ActualElementT>,
271 {
272 actual.into_iter().filter(|i| !self.expected.into_iter().any(|j| *i == j)).collect()
273 }
274}
275
276impl<ExpectedContainerT: Debug, Mode> Describable for ContainerEqMatcher<ExpectedContainerT, Mode> {
277 fn describe(&self, matcher_result: MatcherResult) -> Description {
278 match matcher_result {
279 MatcherResult::Match => format!("is equal to {:?}", self.expected).into(),
280 MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected).into(),
281 }
282 }
283}
284
285fn build_explanation<T: Debug, U: Debug>(
286 missing: Vec<T>,
287 unexpected: Vec<U>,
288 matcher_result: MatcherResult,
289) -> String {
290 match (missing.len(), unexpected.len(), matcher_result) {
291 (0, 0, MatcherResult::NoMatch) => {
293 "which contains all the elements but in the wrong order".to_string()
294 }
295 (0, 0, MatcherResult::Match) => "which contains all the elements".to_string(),
296 (0, 1, _) => format!("which contains the unexpected element {:?}", unexpected[0]),
297 (0, _, _) => format!("which contains the unexpected elements {unexpected:?}",),
298 (1, 0, _) => format!("which is missing the element {:?}", missing[0]),
299 (1, 1, _) => {
300 format!(
301 "which is missing the element {:?} and contains the unexpected element {:?}",
302 missing[0], unexpected[0]
303 )
304 }
305 (1, _, _) => {
306 format!(
307 "which is missing the element {:?} and contains the unexpected elements {unexpected:?}",
308 missing[0]
309 )
310 }
311 (_, 0, _) => format!("which is missing the elements {missing:?}"),
312 (_, 1, _) => {
313 format!(
314 "which is missing the elements {missing:?} and contains the unexpected element {:?}",
315 unexpected[0]
316 )
317 }
318 (_, _, _) => {
319 format!(
320 "which is missing the elements {missing:?} and contains the unexpected elements {unexpected:?}",
321 )
322 }
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use crate::prelude::*;
329 use alloc::{
330 string::{String, ToString},
331 vec::Vec,
332 };
333 use indoc::indoc;
334
335 #[test]
336 fn container_eq_returns_match_when_containers_match() -> TestResult<()> {
337 verify_that!(vec![1, 2, 3], container_eq(vec![1, 2, 3]))
338 }
339
340 #[test]
341 fn container_eq_matches_array_with_array() -> TestResult<()> {
342 verify_that!([1, 2, 3], container_eq([1, 2, 3]))
343 }
344
345 #[test]
346 fn container_eq_matches_vec_with_array() -> TestResult<()> {
347 verify_that!(vec![1, 2, 3], container_eq([1, 2, 3]))
348 }
349
350 #[test]
351 fn container_eq_matches_slice_using_points_to() -> TestResult<()> {
352 let value = vec![1, 2, 3];
353 let slice = value.as_slice();
354 verify_that!(slice, points_to(container_eq([1, 2, 3])))
355 }
356
357 #[test]
358 fn container_eq_matches_slice_using_deref_notation() -> TestResult<()> {
359 let value = vec![1, 2, 3];
360 let slice = value.as_slice();
361 verify_that!(*slice, container_eq([1, 2, 3]))
362 }
363
364 #[test]
365 fn container_eq_matches_ref_to_array_using_points_to() -> TestResult<()> {
366 verify_that!(&[1, 2, 3], points_to(container_eq([1, 2, 3])))
367 }
368
369 #[test]
370 fn container_eq_matches_ref_to_array_using_deref_notation() -> TestResult<()> {
371 let value = &[1, 2, 3];
372 verify_that!(*value, container_eq([1, 2, 3]))
373 }
374
375 #[test]
376 fn container_eq_matches_owned_vec_of_owned_strings_with_array_of_string_slices()
377 -> TestResult<()> {
378 verify_that!(
379 vec!["A string".to_string(), "Another string".to_string()],
380 container_eq(["A string", "Another string"])
381 )
382 }
383
384 #[test]
385 fn container_eq_does_not_match_vec_of_owned_strings_with_shorter_array_of_string_slices()
386 -> TestResult<()> {
387 verify_that!(
388 vec!["A string".to_string(), "Another string".to_string()],
389 not(container_eq(["A string"]))
390 )
391 }
392
393 #[test]
394 fn container_eq_matches_vec_of_string_slices_with_array_of_string_slices() -> TestResult<()> {
395 verify_that!(
396 vec!["A string", "Another string"],
397 container_eq(["A string", "Another string"])
398 )
399 }
400
401 #[test]
402 fn container_eq_matches_array_of_string_slices_with_array_of_string_slices() -> TestResult<()> {
403 verify_that!(["A string", "Another string"], container_eq(["A string", "Another string"]))
404 }
405
406 #[test]
407 fn container_eq_matches_array_of_string_slices_with_non_static_lifetime_with_array_of_string_slices()
408 -> TestResult<()> {
409 let string_1 = String::from("A string");
410 let string_2 = String::from("Another string");
411 verify_that!(
412 [string_1.as_str(), string_2.as_str()],
413 container_eq(["A string", "Another string"])
414 )
415 }
416
417 #[cfg(feature = "std")]
418 #[test]
419 fn container_eq_matches_hash_set_with_array() -> TestResult<()> {
420 use std::collections::HashSet;
421 verify_that!(HashSet::from([1, 2, 3]), container_eq([1, 2, 3].into()))
422 }
423
424 #[test]
425 fn container_eq_produces_correct_failure_message() -> TestResult<()> {
426 let result = verify_that!(vec![1, 3, 2], container_eq(vec![1, 2, 3]));
427 verify_that!(
428 result,
429 err(displays_as(contains_substring(indoc!(
430 "
431 Value of: vec![1, 3, 2]
432 Expected: is equal to [1, 2, 3]
433 Actual: [1, 3, 2],
434 which contains all the elements but in the wrong order
435 "
436 ))))
437 )
438 }
439
440 #[test]
441 fn container_eq_returns_mismatch_when_elements_out_of_order() -> TestResult<()> {
442 let result = verify_that!(vec![1, 3, 2], container_eq(vec![1, 2, 3]));
443 verify_that!(
444 result,
445 err(displays_as(contains_substring(
446 "which contains all the elements but in the wrong order"
447 )))
448 )
449 }
450
451 #[test]
452 fn container_eq_does_not_show_part_about_wrong_order_when_ignoring_order() -> TestResult<()> {
453 let result = verify_that!(vec![1, 3, 2], not(container_eq(vec![1, 2, 3]).ignoring_order()));
454 verify_that!(result, err(displays_as(not(contains_substring("but in the wrong order")))))
455 }
456
457 #[test]
458 fn container_eq_mismatch_shows_missing_elements_in_container() -> TestResult<()> {
459 let result = verify_that!(vec![1, 2], container_eq(vec![1, 2, 3]));
460 verify_that!(result, err(displays_as(contains_substring("which is missing the element 3"))))
461 }
462
463 #[test]
464 fn container_eq_mismatch_shows_missing_elements_in_container_when_matching_vec_with_array()
465 -> TestResult<()> {
466 let result = verify_that!(vec![1, 2], container_eq([1, 2, 3]));
467 verify_that!(result, err(displays_as(contains_substring("which is missing the element 3"))))
468 }
469
470 #[test]
471 fn container_eq_mismatch_shows_surplus_elements_in_container() -> TestResult<()> {
472 let result = verify_that!(vec![1, 2, 3], container_eq(vec![1, 2]));
473 verify_that!(
474 result,
475 err(displays_as(contains_substring("which contains the unexpected element 3")))
476 )
477 }
478
479 #[test]
480 fn container_eq_mismatch_shows_missing_and_surplus_elements_in_container() -> TestResult<()> {
481 let result = verify_that!(vec![1, 2, 4], container_eq(vec![1, 2, 3]));
482 verify_that!(
483 result,
484 err(displays_as(contains_substring(
485 "which is missing the element 3 and contains the unexpected element 4"
486 )))
487 )
488 }
489
490 #[test]
491 fn container_eq_mismatch_does_not_show_duplicated_element() -> TestResult<()> {
492 let result = verify_that!(vec![1, 2, 3, 3], container_eq(vec![1, 2, 3]));
493 verify_that!(
494 result,
495 err(displays_as(contains_substring("which contains all the elements")))
496 )
497 }
498
499 #[test]
500 fn container_eq_mismatch_with_str_slice_shows_missing_elements_in_container() -> TestResult<()>
501 {
502 let result =
503 verify_that!(vec!["A".to_string(), "B".to_string()], container_eq(["A", "B", "C"]));
504 verify_that!(
505 result,
506 err(displays_as(contains_substring(r#"which is missing the element "C""#)))
507 )
508 }
509
510 #[test]
511 fn container_eq_mismatch_with_str_slice_shows_surplus_elements_in_container() -> TestResult<()>
512 {
513 let result = verify_that!(
514 vec!["A".to_string(), "B".to_string(), "C".to_string()],
515 container_eq(["A", "B"])
516 );
517 verify_that!(
518 result,
519 err(displays_as(contains_substring(r#"which contains the unexpected element "C""#)))
520 )
521 }
522
523 #[derive(Debug, PartialEq)]
524 struct OwnedItemContainer(Vec<i32>);
525
526 impl<'a> IntoIterator for &'a OwnedItemContainer {
527 type Item = i32;
528 type IntoIter = core::iter::Copied<core::slice::Iter<'a, i32>>;
529 fn into_iter(self) -> Self::IntoIter {
530 self.0.iter().copied()
531 }
532 }
533
534 #[test]
535 fn container_eq_matches_on_container_when_ref_to_container_has_into_iterator_producing_owned_values()
536 -> TestResult<()> {
537 verify_that!(OwnedItemContainer(vec![1]), container_eq(OwnedItemContainer(vec![1])))
538 }
539
540 #[test]
541 fn container_eq_matches_vec_of_string_slices() -> TestResult<()> {
542 verify_that!(
543 vec!["String 1", "String 2", "String 3"],
544 container_eq(["String 1", "String 2", "String 3"])
545 )
546 }
547
548 #[test]
549 fn container_eq_matches_vec_of_string_slices_with_non_static_lifetime() -> TestResult<()> {
550 let string_1 = String::from("String 1");
551 let string_2 = String::from("String 2");
552 let string_3 = String::from("String 3");
553 verify_that!(
554 vec![string_1.as_str(), string_2.as_str(), string_3.as_str()],
555 container_eq(["String 1", "String 2", "String 3"])
556 )
557 }
558
559 #[test]
560 fn container_eq_matches_slice_of_string_slices() -> TestResult<()> {
561 let value = vec!["String 1", "String 2", "String 3"];
562 verify_that!(
563 value.as_slice(),
564 points_to(container_eq(["String 1", "String 2", "String 3"]))
565 )
566 }
567
568 #[test]
569 fn container_eq_matches_slice_of_string_slices_with_non_static_lifetime() -> TestResult<()> {
570 let string_1 = String::from("String 1");
571 let string_2 = String::from("String 2");
572 let string_3 = String::from("String 3");
573 let value = vec![string_1.as_str(), string_2.as_str(), string_3.as_str()];
574 verify_that!(
575 value.as_slice(),
576 points_to(container_eq(["String 1", "String 2", "String 3"]))
577 )
578 }
579
580 #[test]
581 fn container_eq_matches_ignoring_order_when_requested() -> TestResult<()> {
582 verify_that!(vec![1, 2, 3], container_eq(vec![3, 2, 1]).ignoring_order())
583 }
584
585 #[test]
586 fn container_eq_does_not_match_non_matching_container_when_ignoring_order() -> TestResult<()> {
587 verify_that!(vec![1, 2, 3], not(container_eq(vec![4, 2, 1]).ignoring_order()))
588 }
589
590 #[test]
591 fn container_eq_matches_slice_of_strings_while_ignoring_order() -> TestResult<()> {
592 let string_1 = String::from("String 1");
593 let string_2 = String::from("String 2");
594 let string_3 = String::from("String 3");
595 let value = vec![string_1, string_2, string_3];
596 verify_that!(
597 value.as_slice(),
598 points_to(container_eq(["String 3", "String 2", "String 1"]).ignoring_order())
599 )
600 }
601
602 #[test]
603 fn container_eq_matches_with_owned_item_conatiner_ignoring_order_when_requested()
604 -> TestResult<()> {
605 verify_that!(
606 OwnedItemContainer(vec![1, 2, 3]),
607 container_eq(OwnedItemContainer(vec![3, 2, 1])).ignoring_order()
608 )
609 }
610
611 #[test]
612 fn container_eq_does_not_match_non_matching_with_owned_item_conatiner_when_ignoring_order()
613 -> TestResult<()> {
614 verify_that!(
615 OwnedItemContainer(vec![1, 2, 3]),
616 not(container_eq(OwnedItemContainer(vec![4, 2, 1])).ignoring_order())
617 )
618 }
619}