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::description::Description;
16 use crate::matcher::{Matcher, MatcherResult};
17 use std::fmt::Debug;
18 use std::marker::PhantomData;
19
20 /// Matches a container equal (in the sense of `==`) to `expected`.
21 ///
22 /// This is similar to [`crate::matchers::eq`] except that an assertion failure
23 /// message generated from this matcher will include the missing and unexpected
24 /// items in the actual value, e.g.:
25 ///
26 /// ```text
27 /// Expected container to equal [1, 2, 3]
28 /// but was: [1, 2, 4]
29 /// Missing: [3]
30 /// Unexpected: [4]
31 /// ```
32 ///
33 /// The actual value must be a container such as a `Vec`, an array, or a
34 /// dereferenced slice. More precisely, a shared borrow of the actual value must
35 /// implement [`IntoIterator`] whose `Item` type implements
36 /// [`PartialEq<ExpectedT>`], where `ExpectedT` is the element type of the
37 /// expected value.
38 ///
39 /// If the container type is a `Vec`, then the expected type may be a slice of
40 /// the same element type. For example:
41 ///
42 /// ```
43 /// # use googletest::prelude::*;
44 /// # fn should_pass() -> Result<()> {
45 /// let vec = vec![1, 2, 3];
46 /// verify_that!(vec, container_eq([1, 2, 3]))?;
47 /// # Ok(())
48 /// # }
49 /// # should_pass().unwrap();
50 /// ```
51 ///
52 /// As an exception, if the actual type is a `Vec<String>`, the expected type
53 /// may be a slice of `&str`:
54 ///
55 /// ```
56 /// # use googletest::prelude::*;
57 /// # fn should_pass() -> Result<()> {
58 /// let vec: Vec<String> = vec!["A string".into(), "Another string".into()];
59 /// verify_that!(vec, container_eq(["A string", "Another string"]))?;
60 /// # Ok(())
61 /// # }
62 /// # should_pass().unwrap();
63 /// ```
64 ///
65 /// These exceptions allow one to avoid unnecessary allocations in test
66 /// assertions.
67 ///
68 /// One can also check container equality of a slice with an array. To do so,
69 /// dereference the slice:
70 ///
71 /// ```
72 /// # use googletest::prelude::*;
73 /// # fn should_pass() -> Result<()> {
74 /// let value = &[1, 2, 3];
75 /// verify_that!(*value, container_eq([1, 2, 3]))?;
76 /// # Ok(())
77 /// # }
78 /// # should_pass().unwrap();
79 /// ```
80 ///
81 /// Otherwise, the actual and expected types must be identical.
82 ///
83 /// *Performance note*: In the event of a mismatch leading to an assertion
84 /// failure, the construction of the lists of missing and unexpected values
85 /// uses a naive algorithm requiring time proportional to the product of the
86 /// sizes of the expected and actual values. This should therefore only be used
87 /// when the containers are small enough that this is not a problem.
88 // This returns ContainerEqMatcher and not impl Matcher because
89 // ContainerEqMatcher has some specialisations for slice types (see
90 // documentation above). Returning impl Matcher would hide those from the
91 // compiler.
container_eq<ActualContainerT, ExpectedContainerT>( expected: ExpectedContainerT, ) -> ContainerEqMatcher<ActualContainerT, ExpectedContainerT> where ActualContainerT: PartialEq<ExpectedContainerT> + Debug + ?Sized, ExpectedContainerT: Debug,92 pub fn container_eq<ActualContainerT, ExpectedContainerT>(
93 expected: ExpectedContainerT,
94 ) -> ContainerEqMatcher<ActualContainerT, ExpectedContainerT>
95 where
96 ActualContainerT: PartialEq<ExpectedContainerT> + Debug + ?Sized,
97 ExpectedContainerT: Debug,
98 {
99 ContainerEqMatcher { expected, phantom: Default::default() }
100 }
101
102 pub struct ContainerEqMatcher<ActualContainerT: ?Sized, ExpectedContainerT> {
103 expected: ExpectedContainerT,
104 phantom: PhantomData<ActualContainerT>,
105 }
106
107 impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT> Matcher
108 for ContainerEqMatcher<ActualContainerT, ExpectedContainerT>
109 where
110 ActualElementT: PartialEq<ExpectedElementT> + Debug + ?Sized,
111 ActualContainerT: PartialEq<ExpectedContainerT> + Debug + ?Sized,
112 ExpectedElementT: Debug,
113 ExpectedContainerT: Debug,
114 for<'a> &'a ActualContainerT: IntoIterator<Item = &'a ActualElementT>,
115 for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
116 {
117 type ActualT = ActualContainerT;
118
matches(&self, actual: &ActualContainerT) -> MatcherResult119 fn matches(&self, actual: &ActualContainerT) -> MatcherResult {
120 (*actual == self.expected).into()
121 }
122
explain_match(&self, actual: &ActualContainerT) -> Description123 fn explain_match(&self, actual: &ActualContainerT) -> Description {
124 build_explanation(self.get_missing_items(actual), self.get_unexpected_items(actual)).into()
125 }
126
describe(&self, matcher_result: MatcherResult) -> Description127 fn describe(&self, matcher_result: MatcherResult) -> Description {
128 match matcher_result {
129 MatcherResult::Match => format!("is equal to {:?}", self.expected).into(),
130 MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected).into(),
131 }
132 }
133 }
134
135 impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT>
136 ContainerEqMatcher<ActualContainerT, ExpectedContainerT>
137 where
138 ActualElementT: PartialEq<ExpectedElementT> + ?Sized,
139 ActualContainerT: PartialEq<ExpectedContainerT> + ?Sized,
140 for<'a> &'a ActualContainerT: IntoIterator<Item = &'a ActualElementT>,
141 for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
142 {
get_missing_items(&self, actual: &ActualContainerT) -> Vec<&ExpectedElementT>143 fn get_missing_items(&self, actual: &ActualContainerT) -> Vec<&ExpectedElementT> {
144 self.expected.into_iter().filter(|&i| !actual.into_iter().any(|j| j == i)).collect()
145 }
146
get_unexpected_items<'a>(&self, actual: &'a ActualContainerT) -> Vec<&'a ActualElementT>147 fn get_unexpected_items<'a>(&self, actual: &'a ActualContainerT) -> Vec<&'a ActualElementT> {
148 actual.into_iter().filter(|&i| !self.expected.into_iter().any(|j| i == j)).collect()
149 }
150 }
151
build_explanation<T: Debug, U: Debug>(missing: Vec<T>, unexpected: Vec<U>) -> String152 fn build_explanation<T: Debug, U: Debug>(missing: Vec<T>, unexpected: Vec<U>) -> String {
153 match (missing.len(), unexpected.len()) {
154 // TODO(b/261175849) add more data here (out of order elements, duplicated elements, etc...)
155 (0, 0) => "which contains all the elements".to_string(),
156 (0, 1) => format!("which contains the unexpected element {:?}", unexpected[0]),
157 (0, _) => format!("which contains the unexpected elements {unexpected:?}",),
158 (1, 0) => format!("which is missing the element {:?}", missing[0]),
159 (1, 1) => {
160 format!(
161 "which is missing the element {:?} and contains the unexpected element {:?}",
162 missing[0], unexpected[0]
163 )
164 }
165 (1, _) => {
166 format!(
167 "which is missing the element {:?} and contains the unexpected elements {unexpected:?}",
168 missing[0]
169 )
170 }
171 (_, 0) => format!("which is missing the elements {missing:?}"),
172 (_, 1) => {
173 format!(
174 "which is missing the elements {missing:?} and contains the unexpected element {:?}",
175 unexpected[0]
176 )
177 }
178 (_, _) => {
179 format!(
180 "which is missing the elements {missing:?} and contains the unexpected elements {unexpected:?}",
181 )
182 }
183 }
184 }
185
186 #[cfg(test)]
187 mod tests {
188 use super::container_eq;
189 use crate::matcher::{Matcher, MatcherResult};
190 use crate::prelude::*;
191 use indoc::indoc;
192 use std::collections::HashSet;
193
194 #[test]
container_eq_returns_match_when_containers_match() -> Result<()>195 fn container_eq_returns_match_when_containers_match() -> Result<()> {
196 verify_that!(vec![1, 2, 3], container_eq(vec![1, 2, 3]))
197 }
198
199 #[test]
container_eq_matches_array_with_slice() -> Result<()>200 fn container_eq_matches_array_with_slice() -> Result<()> {
201 let value = &[1, 2, 3];
202 verify_that!(*value, container_eq([1, 2, 3]))
203 }
204
205 #[test]
container_eq_matches_hash_set() -> Result<()>206 fn container_eq_matches_hash_set() -> Result<()> {
207 let value: HashSet<i32> = [1, 2, 3].into();
208 verify_that!(value, container_eq([1, 2, 3].into()))
209 }
210
211 #[test]
container_eq_full_error_message() -> Result<()>212 fn container_eq_full_error_message() -> Result<()> {
213 let result = verify_that!(vec![1, 3, 2], container_eq(vec![1, 2, 3]));
214 verify_that!(
215 result,
216 err(displays_as(contains_substring(indoc!(
217 "
218 Value of: vec![1, 3, 2]
219 Expected: is equal to [1, 2, 3]
220 Actual: [1, 3, 2],
221 which contains all the elements
222 "
223 ))))
224 )
225 }
226
227 #[test]
container_eq_returns_mismatch_when_elements_out_of_order() -> Result<()>228 fn container_eq_returns_mismatch_when_elements_out_of_order() -> Result<()> {
229 verify_that!(
230 container_eq(vec![1, 2, 3]).explain_match(&vec![1, 3, 2]),
231 displays_as(eq("which contains all the elements"))
232 )
233 }
234
235 #[test]
container_eq_mismatch_shows_missing_elements_in_container() -> Result<()>236 fn container_eq_mismatch_shows_missing_elements_in_container() -> Result<()> {
237 verify_that!(
238 container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2]),
239 displays_as(eq("which is missing the element 3"))
240 )
241 }
242
243 #[test]
container_eq_mismatch_shows_surplus_elements_in_container() -> Result<()>244 fn container_eq_mismatch_shows_surplus_elements_in_container() -> Result<()> {
245 verify_that!(
246 container_eq(vec![1, 2]).explain_match(&vec![1, 2, 3]),
247 displays_as(eq("which contains the unexpected element 3"))
248 )
249 }
250
251 #[test]
container_eq_mismatch_shows_missing_and_surplus_elements_in_container() -> Result<()>252 fn container_eq_mismatch_shows_missing_and_surplus_elements_in_container() -> Result<()> {
253 verify_that!(
254 container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2, 4]),
255 displays_as(eq("which is missing the element 3 and contains the unexpected element 4"))
256 )
257 }
258
259 #[test]
container_eq_mismatch_does_not_show_duplicated_element() -> Result<()>260 fn container_eq_mismatch_does_not_show_duplicated_element() -> Result<()> {
261 verify_that!(
262 container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2, 3, 3]),
263 displays_as(eq("which contains all the elements"))
264 )
265 }
266
267 #[test]
container_eq_matches_owned_vec_with_array() -> Result<()>268 fn container_eq_matches_owned_vec_with_array() -> Result<()> {
269 let vector = vec![123, 234];
270 verify_that!(vector, container_eq([123, 234]))
271 }
272
273 #[test]
container_eq_matches_owned_vec_of_owned_strings_with_slice_of_string_references() -> Result<()>274 fn container_eq_matches_owned_vec_of_owned_strings_with_slice_of_string_references()
275 -> Result<()> {
276 let vector = vec!["A string".to_string(), "Another string".to_string()];
277 verify_that!(vector, container_eq(["A string", "Another string"]))
278 }
279
280 #[test]
container_eq_matches_owned_vec_of_owned_strings_with_shorter_slice_of_string_references() -> Result<()>281 fn container_eq_matches_owned_vec_of_owned_strings_with_shorter_slice_of_string_references()
282 -> Result<()> {
283 let actual = vec!["A string".to_string(), "Another string".to_string()];
284 let matcher = container_eq(["A string"]);
285
286 let result = matcher.matches(&actual);
287
288 verify_that!(result, eq(MatcherResult::NoMatch))
289 }
290
291 #[test]
container_eq_mismatch_with_slice_shows_missing_elements_in_container() -> Result<()>292 fn container_eq_mismatch_with_slice_shows_missing_elements_in_container() -> Result<()> {
293 verify_that!(
294 container_eq([1, 2, 3]).explain_match(&vec![1, 2]),
295 displays_as(eq("which is missing the element 3"))
296 )
297 }
298
299 #[test]
container_eq_mismatch_with_str_slice_shows_missing_elements_in_container() -> Result<()>300 fn container_eq_mismatch_with_str_slice_shows_missing_elements_in_container() -> Result<()> {
301 verify_that!(
302 container_eq(["A", "B", "C"]).explain_match(&vec!["A".to_string(), "B".to_string()]),
303 displays_as(eq("which is missing the element \"C\""))
304 )
305 }
306
307 #[test]
container_eq_mismatch_with_str_slice_shows_surplus_elements_in_container() -> Result<()>308 fn container_eq_mismatch_with_str_slice_shows_surplus_elements_in_container() -> Result<()> {
309 verify_that!(
310 container_eq(["A", "B"]).explain_match(&vec![
311 "A".to_string(),
312 "B".to_string(),
313 "C".to_string()
314 ]),
315 displays_as(eq("which contains the unexpected element \"C\""))
316 )
317 }
318 }
319