Skip to main content

googletest/matchers/
container_eq_matcher.rs

1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
21/// Matches a container equal (in the sense of `==`) to `expected`.
22///
23/// This is similar to [`crate::matchers::eq`] except that an assertion failure
24/// message generated from this matcher will include the missing and unexpected
25/// items in the actual value, e.g.:
26///
27/// ```text
28/// Expected container to equal [1, 2, 3]
29///   but was: [1, 2, 4]
30///   Missing: [3]
31///   Unexpected: [4]
32/// ```
33///
34/// The actual value must be a container such as a `&Vec`, an array, or a
35/// dereferenced slice. More precisely, the actual value must
36/// implement [`IntoIterator`] whose `Item` type implements
37/// [`PartialEq<ExpectedT>`], where `ExpectedT` is the element type of the
38/// expected value.
39///
40/// ```
41/// # use googletest::prelude::*;
42/// # fn should_pass() -> Result<()> {
43/// let vec = vec![1, 2, 3];
44/// verify_that!(vec, container_eq([1, 2, 3]))?;
45/// #     Ok(())
46/// # }
47/// # should_pass().unwrap();
48/// ```
49///
50/// *Performance note*: In the event of a mismatch leading to an assertion
51/// failure, the construction of the lists of missing and unexpected values
52/// uses a naive algorithm requiring time proportional to the product of the
53/// sizes of the expected and actual values. This should therefore only be used
54/// when the containers are small enough that this is not a problem.
55pub 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        // TODO(b/261175849) add more data here (out of order elements, duplicated elements, etc...)
127        (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    /// Match container equality, but ignoring element order.
160    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
170/// Implements a matcher that ignores the relative order of the elements.
171impl<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        // TODO: investigate why Requirements::PerfectMatch and
187        // match_matrix.is_full_match is not doing what we expect
188        // here.
189        (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        // We need to materialize the collections in order to have reliable iteration
196        // order when generating our reports.
197        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        // Since we are doing equality checks, we can generate a slightly less verbose
209        // message than BestMatch::get_explanation.
210        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}