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 
15 use crate::{
16     description::Description,
17     matcher::{Matcher, MatcherResult},
18 };
19 use std::{fmt::Debug, marker::PhantomData};
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 a reference to which
28 /// implements `IntoIterator`. For instance, `ActualT` and `ExpectedT` can be a
29 /// common container like `Vec` or arrays. They need not be the same container
30 /// 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 /// One can also verify the contents of a slice by dereferencing it:
72 ///
73 /// ```
74 /// # use googletest::prelude::*;
75 /// # fn should_pass() -> Result<()> {
76 /// let value = &[1, 2, 3];
77 /// verify_that!(*value, superset_of([1, 2, 3]))?;
78 /// #     Ok(())
79 /// # }
80 /// # should_pass().unwrap();
81 /// ```
82 ///
83 /// A note on performance: This matcher uses a naive algorithm with a worst-case
84 /// runtime proportional to the *product* of the sizes of the actual and
85 /// expected containers as well as the time to check equality of each pair of
86 /// items. It should not be used on especially large containers.
superset_of<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug>( subset: ExpectedT, ) -> impl Matcher<ActualT = ActualT> where for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>, for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,87 pub fn superset_of<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug>(
88     subset: ExpectedT,
89 ) -> impl Matcher<ActualT = ActualT>
90 where
91     for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
92     for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
93 {
94     SupersetOfMatcher::<ActualT, _> { subset, phantom: Default::default() }
95 }
96 
97 struct SupersetOfMatcher<ActualT: ?Sized, ExpectedT> {
98     subset: ExpectedT,
99     phantom: PhantomData<ActualT>,
100 }
101 
102 impl<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug> Matcher
103     for SupersetOfMatcher<ActualT, ExpectedT>
104 where
105     for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
106     for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
107 {
108     type ActualT = ActualT;
109 
matches(&self, actual: &ActualT) -> MatcherResult110     fn matches(&self, actual: &ActualT) -> MatcherResult {
111         for expected_item in &self.subset {
112             if actual_is_missing(actual, expected_item) {
113                 return MatcherResult::NoMatch;
114             }
115         }
116         MatcherResult::Match
117     }
118 
explain_match(&self, actual: &ActualT) -> Description119     fn explain_match(&self, actual: &ActualT) -> Description {
120         let missing_items: Vec<_> = self
121             .subset
122             .into_iter()
123             .filter(|expected_item| actual_is_missing(actual, expected_item))
124             .map(|expected_item| format!("{expected_item:#?}"))
125             .collect();
126         match missing_items.len() {
127             0 => "whose no element is missing".into(),
128             1 => format!("whose element {} is missing", &missing_items[0]).into(),
129             _ => format!("whose elements {} are missing", missing_items.join(", ")).into(),
130         }
131     }
132 
describe(&self, matcher_result: MatcherResult) -> Description133     fn describe(&self, matcher_result: MatcherResult) -> Description {
134         match matcher_result {
135             MatcherResult::Match => format!("is a superset of {:#?}", self.subset).into(),
136             MatcherResult::NoMatch => format!("isn't a superset of {:#?}", self.subset).into(),
137         }
138     }
139 }
140 
actual_is_missing<ElementT: PartialEq, ActualT: ?Sized>( actual: &ActualT, needle: &ElementT, ) -> bool where for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,141 fn actual_is_missing<ElementT: PartialEq, ActualT: ?Sized>(
142     actual: &ActualT,
143     needle: &ElementT,
144 ) -> bool
145 where
146     for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
147 {
148     !actual.into_iter().any(|item| *item == *needle)
149 }
150 
151 #[cfg(test)]
152 mod tests {
153     use super::superset_of;
154     use crate::prelude::*;
155     use indoc::indoc;
156     use std::collections::HashSet;
157 
158     #[test]
superset_of_matches_empty_vec() -> Result<()>159     fn superset_of_matches_empty_vec() -> Result<()> {
160         let value: Vec<i32> = vec![];
161         verify_that!(value, superset_of([]))
162     }
163 
164     #[test]
superset_of_matches_vec_with_one_element() -> Result<()>165     fn superset_of_matches_vec_with_one_element() -> Result<()> {
166         let value = vec![1];
167         verify_that!(value, superset_of([1]))
168     }
169 
170     #[test]
superset_of_matches_vec_with_two_items() -> Result<()>171     fn superset_of_matches_vec_with_two_items() -> Result<()> {
172         let value = vec![1, 2];
173         verify_that!(value, superset_of([1, 2]))
174     }
175 
176     #[test]
superset_of_matches_vec_when_actual_has_excess_element() -> Result<()>177     fn superset_of_matches_vec_when_actual_has_excess_element() -> Result<()> {
178         let value = vec![1, 2, 3];
179         verify_that!(value, superset_of([1, 2]))
180     }
181 
182     #[test]
superset_of_matches_vec_when_actual_has_excess_element_first() -> Result<()>183     fn superset_of_matches_vec_when_actual_has_excess_element_first() -> Result<()> {
184         let value = vec![3, 1, 2];
185         verify_that!(value, superset_of([1, 2]))
186     }
187 
188     #[test]
superset_of_matches_slice_with_one_element() -> Result<()>189     fn superset_of_matches_slice_with_one_element() -> Result<()> {
190         let value = &[1];
191         verify_that!(*value, superset_of([1]))
192     }
193 
194     #[test]
superset_of_matches_hash_set_with_one_element() -> Result<()>195     fn superset_of_matches_hash_set_with_one_element() -> Result<()> {
196         let value: HashSet<i32> = [1].into();
197         verify_that!(value, superset_of([1]))
198     }
199 
200     #[test]
superset_of_does_not_match_when_first_element_does_not_match() -> Result<()>201     fn superset_of_does_not_match_when_first_element_does_not_match() -> Result<()> {
202         let value = vec![0];
203         verify_that!(value, not(superset_of([1])))
204     }
205 
206     #[test]
superset_of_does_not_match_when_second_element_does_not_match() -> Result<()>207     fn superset_of_does_not_match_when_second_element_does_not_match() -> Result<()> {
208         let value = vec![2];
209         verify_that!(value, not(superset_of([2, 0])))
210     }
211 
212     #[test]
superset_of_shows_correct_message_when_first_item_does_not_match() -> Result<()>213     fn superset_of_shows_correct_message_when_first_item_does_not_match() -> Result<()> {
214         let result = verify_that!(vec![0, 2, 3], superset_of([1, 2, 3]));
215 
216         verify_that!(
217             result,
218             err(displays_as(contains_substring(indoc!(
219                 "
220                     Value of: vec![0, 2, 3]
221                     Expected: is a superset of [
222                         1,
223                         2,
224                         3,
225                     ]
226                     Actual: [0, 2, 3],
227                       whose element 1 is missing
228                 "
229             ))))
230         )
231     }
232 
233     #[test]
superset_of_shows_correct_message_when_second_item_does_not_match() -> Result<()>234     fn superset_of_shows_correct_message_when_second_item_does_not_match() -> Result<()> {
235         let result = verify_that!(vec![1, 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![1, 0, 3]
242                     Expected: is a superset of [
243                         1,
244                         2,
245                         3,
246                     ]
247                     Actual: [1, 0, 3],
248                       whose element 2 is missing
249                 "
250             ))))
251         )
252     }
253 
254     #[test]
superset_of_shows_correct_message_when_first_two_items_do_not_match() -> Result<()>255     fn superset_of_shows_correct_message_when_first_two_items_do_not_match() -> Result<()> {
256         let result = verify_that!(vec![0, 0, 3], superset_of([1, 2, 3]));
257 
258         verify_that!(
259             result,
260             err(displays_as(contains_substring(indoc!(
261                 "
262                     Value of: vec![0, 0, 3]
263                     Expected: is a superset of [
264                         1,
265                         2,
266                         3,
267                     ]
268                     Actual: [0, 0, 3],
269                       whose elements 1, 2 are missing
270                 "
271             ))))
272         )
273     }
274 }
275