Skip to main content

googletest/matchers/
superset_of_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::{
16    description::Description,
17    matcher::{Matcher, MatcherBase, MatcherResult},
18};
19use std::fmt::Debug;
20
21/// Matches a container containing all of the items in the given container
22/// `subset`.
23///
24/// The element type `ElementT` must implement `PartialEq` to allow element
25/// comparison.
26///
27/// `ActualT` and `ExpectedT` can each be any container which implements
28/// `IntoIterator`. For instance, `ActualT` and `ExpectedT` can be a
29/// common container like `&Vec`, arrays or slices. They need not be the same
30/// container type.
31///
32/// ```
33/// # use googletest::prelude::*;
34/// # use std::collections::HashSet;
35/// # fn should_pass_1() -> Result<()> {
36/// let value = vec![1, 2, 3];
37/// verify_that!(value, superset_of([&1, &2]))?;  // Passes
38/// let array_value = [1, 2, 3];
39/// verify_that!(array_value, superset_of([1, 2]))?;  // Passes
40/// #     Ok(())
41/// # }
42/// # fn should_fail() -> Result<()> {
43/// # let value = vec![1, 2, 3];
44/// verify_that!(value, superset_of([&1, &2, &4]))?;  // Fails: 4 is not in the subset
45/// #     Ok(())
46/// # }
47/// # should_pass_1().unwrap();
48/// # should_fail().unwrap_err();
49///
50/// # fn should_pass_2() -> Result<()> {
51/// let value: HashSet<i32> = [1, 2, 3].into();
52/// verify_that!(value, superset_of([&1, &2, &3]))?;  // Passes
53/// #     Ok(())
54/// # }
55/// # should_pass_2().unwrap();
56/// ```
57///
58/// Item multiplicity in both the actual and expected containers is ignored:
59///
60/// ```
61/// # use googletest::prelude::*;
62/// # fn should_pass() -> Result<()> {
63/// let value: Vec<i32> = vec![0, 0, 1];
64/// verify_that!(value, superset_of([&0, &1]))?;  // Passes
65/// verify_that!(value, superset_of([&0, &1, &1]))?;  // Passes
66/// #     Ok(())
67/// # }
68/// # should_pass().unwrap();
69/// ```
70///
71/// A note on performance: This matcher uses a naive algorithm with a worst-case
72/// runtime proportional to the *product* of the sizes of the actual and
73/// expected containers as well as the time to check equality of each pair of
74/// items. It should not be used on especially large containers.
75pub fn superset_of<ExpectedT>(subset: ExpectedT) -> SupersetOfMatcher<ExpectedT> {
76    SupersetOfMatcher { subset }
77}
78
79#[derive(MatcherBase)]
80pub struct SupersetOfMatcher<ExpectedT> {
81    subset: ExpectedT,
82}
83
84impl<ElementT: Debug + Copy + PartialEq, ActualT: Debug + Copy, ExpectedT: Debug> Matcher<ActualT>
85    for SupersetOfMatcher<ExpectedT>
86where
87    ActualT: IntoIterator<Item = ElementT>,
88    for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
89{
90    fn matches(&self, actual: ActualT) -> MatcherResult {
91        for expected_item in &self.subset {
92            if actual_is_missing(actual, expected_item) {
93                return MatcherResult::NoMatch;
94            }
95        }
96        MatcherResult::Match
97    }
98
99    fn explain_match(&self, actual: ActualT) -> Description {
100        let missing_items: Vec<_> = self
101            .subset
102            .into_iter()
103            .filter(|expected_item| actual_is_missing(actual, *expected_item))
104            .map(|expected_item| format!("{expected_item:#?}"))
105            .collect();
106        match missing_items.len() {
107            0 => "whose no element is missing".into(),
108            1 => format!("whose element {} is missing", &missing_items[0]).into(),
109            _ => format!("whose elements {} are missing", missing_items.join(", ")).into(),
110        }
111    }
112
113    fn describe(&self, matcher_result: MatcherResult) -> Description {
114        match matcher_result {
115            MatcherResult::Match => format!("is a superset of {:#?}", self.subset).into(),
116            MatcherResult::NoMatch => format!("isn't a superset of {:#?}", self.subset).into(),
117        }
118    }
119}
120
121fn actual_is_missing<ElementT: PartialEq, ActualT>(actual: ActualT, needle: &ElementT) -> bool
122where
123    ActualT: IntoIterator<Item = ElementT>,
124{
125    !actual.into_iter().any(|item| &item == needle)
126}
127#[cfg(test)]
128mod tests {
129    use crate as googletest;
130    use crate::prelude::*;
131    use indoc::indoc;
132    use std::collections::HashSet;
133
134    #[test]
135    fn superset_of_matches_empty_vec() -> googletest::Result<()> {
136        let value: Vec<i32> = vec![];
137        verify_that!(value, superset_of([]))
138    }
139
140    #[test]
141    fn superset_of_matches_vec_with_one_element() -> googletest::Result<()> {
142        let value = vec![1];
143        verify_that!(value, superset_of([&1]))
144    }
145
146    #[test]
147    fn superset_of_matches_vec_with_two_items() -> googletest::Result<()> {
148        let value = vec![1, 2];
149        verify_that!(value, superset_of([&1, &2]))
150    }
151
152    #[test]
153    fn superset_of_matches_vec_when_actual_has_excess_element() -> googletest::Result<()> {
154        let value = vec![1, 2, 3];
155        verify_that!(value, superset_of([&1, &2]))
156    }
157
158    #[test]
159    fn superset_of_matches_vec_when_actual_has_excess_element_first() -> googletest::Result<()> {
160        let value = vec![3, 1, 2];
161        verify_that!(value, superset_of([&1, &2]))
162    }
163
164    #[test]
165    fn superset_of_matches_slice_with_one_element() -> googletest::Result<()> {
166        let value = &[1];
167        verify_that!(value, superset_of([&1]))
168    }
169
170    #[test]
171    fn superset_of_matches_hash_set_with_one_element() -> googletest::Result<()> {
172        let value: HashSet<i32> = [1].into();
173        verify_that!(value, superset_of([&1]))
174    }
175
176    #[test]
177    fn superset_of_does_not_match_when_first_element_does_not_match() -> googletest::Result<()> {
178        let value = vec![0];
179        verify_that!(value, not(superset_of([&1])))
180    }
181
182    #[test]
183    fn superset_of_does_not_match_when_second_element_does_not_match() -> googletest::Result<()> {
184        let value = vec![2];
185        verify_that!(value, not(superset_of([&2, &0])))
186    }
187
188    #[test]
189    fn superset_of_shows_correct_message_when_first_item_does_not_match() -> googletest::Result<()>
190    {
191        let result = verify_that!(vec![0, 2, 3], superset_of([&1, &2, &3]));
192
193        verify_that!(
194            result,
195            err(displays_as(contains_substring(indoc!(
196                "
197                    Value of: vec![0, 2, 3]
198                    Expected: is a superset of [
199                        1,
200                        2,
201                        3,
202                    ]
203                    Actual: [0, 2, 3],
204                      whose element 1 is missing
205                "
206            ))))
207        )
208    }
209
210    #[test]
211    fn superset_of_shows_correct_message_when_second_item_does_not_match() -> googletest::Result<()>
212    {
213        let result = verify_that!(vec![1, 0, 3], superset_of([&1, &2, &3]));
214
215        verify_that!(
216            result,
217            err(displays_as(contains_substring(indoc!(
218                "
219                    Value of: vec![1, 0, 3]
220                    Expected: is a superset of [
221                        1,
222                        2,
223                        3,
224                    ]
225                    Actual: [1, 0, 3],
226                      whose element 2 is missing
227                "
228            ))))
229        )
230    }
231
232    #[test]
233    fn superset_of_shows_correct_message_when_first_two_items_do_not_match(
234    ) -> googletest::Result<()> {
235        let result = verify_that!(vec![0, 0, 3], superset_of([&1, &2, &3]));
236
237        verify_that!(
238            result,
239            err(displays_as(contains_substring(indoc!(
240                "
241                    Value of: vec![0, 0, 3]
242                    Expected: is a superset of [
243                        1,
244                        2,
245                        3,
246                    ]
247                    Actual: [0, 0, 3],
248                      whose elements 1, 2 are missing
249                "
250            ))))
251        )
252    }
253}