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::collections::HashMap;
18 use std::fmt::Debug;
19 use std::hash::Hash;
20 use std::marker::PhantomData;
21 
22 /// Matches a HashMap containing the given `key` whose value is matched by the
23 /// matcher `inner`.
24 ///
25 /// ```
26 /// # use googletest::prelude::*;
27 /// # use std::collections::HashMap;
28 /// # fn should_pass() -> Result<()> {
29 /// let value = HashMap::from([(0, 1), (1, -1)]);
30 /// verify_that!(value, has_entry(0, eq(1)))?;  // Passes
31 /// #     Ok(())
32 /// # }
33 /// # fn should_fail_1() -> Result<()> {
34 /// # let value = HashMap::from([(0, 1), (1, -1)]);
35 /// verify_that!(value, has_entry(1, gt(0)))?;  // Fails: value not matched
36 /// #     Ok(())
37 /// # }
38 /// # fn should_fail_2() -> Result<()> {
39 /// # let value = HashMap::from([(0, 1), (1, -1)]);
40 /// verify_that!(value, has_entry(2, eq(0)))?;  // Fails: key not present
41 /// #     Ok(())
42 /// # }
43 /// # should_pass().unwrap();
44 /// # should_fail_1().unwrap_err();
45 /// # should_fail_2().unwrap_err();
46 /// ```
47 ///
48 /// Note: One could obtain the same effect by collecting entries into a `Vec`
49 /// and using `contains`:
50 ///
51 /// ```
52 /// # use googletest::prelude::*;
53 /// # use std::collections::HashMap;
54 /// # fn should_pass() -> Result<()> {
55 /// let value = HashMap::from([(0, 1), (1, -1)]);
56 /// verify_that!(value.into_iter().collect::<Vec<_>>(), contains(eq((0, 1))))?;
57 /// #     Ok(())
58 /// # }
59 /// # should_pass().unwrap();
60 /// ```
61 ///
62 /// However, `has_entry` will offer somewhat better diagnostic messages in the
63 /// case of assertion failure. And it avoid the extra allocation hidden in the
64 /// code above.
has_entry<KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<ActualT = ValueT>>( key: KeyT, inner: MatcherT, ) -> impl Matcher<ActualT = HashMap<KeyT, ValueT>>65 pub fn has_entry<KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<ActualT = ValueT>>(
66     key: KeyT,
67     inner: MatcherT,
68 ) -> impl Matcher<ActualT = HashMap<KeyT, ValueT>> {
69     HasEntryMatcher { key, inner, phantom: Default::default() }
70 }
71 
72 struct HasEntryMatcher<KeyT, ValueT, MatcherT> {
73     key: KeyT,
74     inner: MatcherT,
75     phantom: PhantomData<ValueT>,
76 }
77 
78 impl<KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<ActualT = ValueT>> Matcher
79     for HasEntryMatcher<KeyT, ValueT, MatcherT>
80 {
81     type ActualT = HashMap<KeyT, ValueT>;
82 
matches(&self, actual: &HashMap<KeyT, ValueT>) -> MatcherResult83     fn matches(&self, actual: &HashMap<KeyT, ValueT>) -> MatcherResult {
84         if let Some(value) = actual.get(&self.key) {
85             self.inner.matches(value)
86         } else {
87             MatcherResult::NoMatch
88         }
89     }
90 
explain_match(&self, actual: &HashMap<KeyT, ValueT>) -> Description91     fn explain_match(&self, actual: &HashMap<KeyT, ValueT>) -> Description {
92         if let Some(value) = actual.get(&self.key) {
93             format!(
94                 "which contains key {:?}, but is mapped to value {:#?}, {}",
95                 self.key,
96                 value,
97                 self.inner.explain_match(value)
98             )
99             .into()
100         } else {
101             format!("which doesn't contain key {:?}", self.key).into()
102         }
103     }
104 
describe(&self, matcher_result: MatcherResult) -> Description105     fn describe(&self, matcher_result: MatcherResult) -> Description {
106         match matcher_result {
107             MatcherResult::Match => format!(
108                 "contains key {:?}, which value {}",
109                 self.key,
110                 self.inner.describe(MatcherResult::Match)
111             )
112             .into(),
113             MatcherResult::NoMatch => format!(
114                 "doesn't contain key {:?} or contains key {:?}, which value {}",
115                 self.key,
116                 self.key,
117                 self.inner.describe(MatcherResult::NoMatch)
118             )
119             .into(),
120         }
121     }
122 }
123 
124 #[cfg(test)]
125 mod tests {
126     use super::has_entry;
127     use crate::prelude::*;
128     use indoc::indoc;
129     use std::collections::HashMap;
130 
131     #[test]
has_entry_does_not_match_empty_hash_map() -> Result<()>132     fn has_entry_does_not_match_empty_hash_map() -> Result<()> {
133         let value: HashMap<i32, i32> = HashMap::new();
134         verify_that!(value, not(has_entry(0, eq(0))))
135     }
136 
137     #[test]
has_entry_matches_hash_map_with_value() -> Result<()>138     fn has_entry_matches_hash_map_with_value() -> Result<()> {
139         let value: HashMap<i32, i32> = HashMap::from([(0, 0)]);
140         verify_that!(value, has_entry(0, eq(0)))
141     }
142 
143     #[test]
has_entry_does_not_match_hash_map_with_wrong_value() -> Result<()>144     fn has_entry_does_not_match_hash_map_with_wrong_value() -> Result<()> {
145         let value: HashMap<i32, i32> = HashMap::from([(0, 1)]);
146         verify_that!(value, not(has_entry(0, eq(0))))
147     }
148 
149     #[test]
has_entry_does_not_match_hash_map_with_wrong_key() -> Result<()>150     fn has_entry_does_not_match_hash_map_with_wrong_key() -> Result<()> {
151         let value: HashMap<i32, i32> = HashMap::from([(1, 0)]);
152         verify_that!(value, not(has_entry(0, eq(0))))
153     }
154 
155     #[test]
has_entry_shows_correct_message_when_key_is_not_present() -> Result<()>156     fn has_entry_shows_correct_message_when_key_is_not_present() -> Result<()> {
157         let result = verify_that!(HashMap::from([(0, 0)]), has_entry(1, eq(0)));
158 
159         verify_that!(
160             result,
161             err(displays_as(contains_substring(indoc!(
162                 "
163                 Value of: HashMap::from([(0, 0)])
164                 Expected: contains key 1, which value is equal to 0
165                 Actual: {0: 0},
166                   which doesn't contain key 1
167                 "
168             ))))
169         )
170     }
171 
172     #[test]
has_entry_shows_correct_message_when_key_has_non_matching_value() -> Result<()>173     fn has_entry_shows_correct_message_when_key_has_non_matching_value() -> Result<()> {
174         let result = verify_that!(HashMap::from([(0, 0)]), has_entry(0, eq(1)));
175 
176         verify_that!(
177             result,
178             err(displays_as(contains_substring(indoc!(
179                 "
180                 Value of: HashMap::from([(0, 0)])
181                 Expected: contains key 0, which value is equal to 1
182                 Actual: {0: 0},
183                   which contains key 0, but is mapped to value 0, which isn't equal to 1
184                 "
185             ))))
186         )
187     }
188 }
189