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, MatcherBase, MatcherResult};
17 use crate::matcher_support::edit_distance;
18 use crate::matcher_support::summarize_diff::create_diff;
19
20 use std::fmt::Debug;
21
22 /// Matches a value equal (in the sense of `==`) to `expected`.
23 ///
24 /// The type of `expected` must implement the [`PartialEq`] trait so that the
25 /// expected and actual values can be compared.
26 ///
27 /// ```
28 /// # use googletest::prelude::*;
29 /// # fn should_pass() -> Result<()> {
30 /// verify_that!(123, eq(123))?; // Passes
31 /// # Ok(())
32 /// # }
33 /// # fn should_fail() -> Result<()> {
34 /// verify_that!(123, eq(234))?; // Fails
35 /// # Ok(())
36 /// # }
37 /// # should_pass().unwrap();
38 /// # should_fail().unwrap_err();
39 /// ```
40 ///
41 /// `expected` to `actual` must be comparable with one another via the
42 /// [`PartialEq`] trait. In most cases, this means that they must be of the same
43 /// type. However, there are a few cases where different but closely related
44 /// types are comparable, for example `String` with `&str`.
45 ///
46 /// ```
47 /// # use googletest::prelude::*;
48 /// # fn should_pass() -> Result<()> {
49 /// verify_that!(String::from("Some value"), eq("Some value"))?; // Passes
50 /// # Ok(())
51 /// # }
52 /// # should_pass().unwrap();
53 /// ```
54 ///
55 /// In most cases however, one must convert one of the arguments explicitly.
56 /// This can be surprising when comparing integer types or references.
57 ///
58 /// ```compile_fail
59 /// verify_that!(123u32, eq(123u64))?; // Does not compile
60 /// verify_that!(123u32 as u64, eq(123u64))?; // Passes
61 /// ```
62 ///
63 /// ```ignore
64 /// let actual: &T = ...;
65 /// let expected: T = T{...};
66 /// verify_that(actual, eq(expected))?; // Does not compile
67 /// verify_that(actual, eq(&expected))?; // Compiles
68 /// ```
69 ///
70 /// When matching with string types (`&str` and `String`), one can set more
71 /// options on how equality is checked through the
72 /// [`StrMatcherConfigurator`][crate::matchers::str_matcher::StrMatcherConfigurator]
73 /// extension trait, which is implemented for this matcher.
eq<T>(expected: T) -> EqMatcher<T>74 pub fn eq<T>(expected: T) -> EqMatcher<T> {
75 EqMatcher { expected }
76 }
77
78 /// A matcher which matches a value equal to `expected`.
79 ///
80 /// See [`eq`].
81 #[derive(MatcherBase)]
82 pub struct EqMatcher<T> {
83 pub(crate) expected: T,
84 }
85
86 impl<T: Debug, A: Debug + Copy + PartialEq<T>> Matcher<A> for EqMatcher<T> {
matches(&self, actual: A) -> MatcherResult87 fn matches(&self, actual: A) -> MatcherResult {
88 (actual == self.expected).into()
89 }
90
describe(&self, matcher_result: MatcherResult) -> Description91 fn describe(&self, matcher_result: MatcherResult) -> Description {
92 match matcher_result {
93 MatcherResult::Match => format!("is equal to {:?}", self.expected).into(),
94 MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected).into(),
95 }
96 }
97
explain_match(&self, actual: A) -> Description98 fn explain_match(&self, actual: A) -> Description {
99 let expected_debug = format!("{:#?}", self.expected);
100 let actual_debug = format!("{:#?}", actual);
101 let description = Matcher::<A>::describe(self, self.matches(actual));
102
103 let diff = if is_multiline_string_debug(&actual_debug)
104 && is_multiline_string_debug(&expected_debug)
105 {
106 create_diff(
107 // The two calls below return None if and only if the strings expected_debug
108 // respectively actual_debug are not enclosed in ". The calls to
109 // is_multiline_string_debug above ensure that they are. So the calls cannot
110 // actually return None and unwrap() should not panic.
111 &to_display_output(&actual_debug).unwrap(),
112 &to_display_output(&expected_debug).unwrap(),
113 edit_distance::Mode::Exact,
114 )
115 } else {
116 create_diff(&actual_debug, &expected_debug, edit_distance::Mode::Exact)
117 };
118
119 if diff.is_empty() {
120 format!("which {description}").into()
121 } else {
122 format!("which {description}\n\n{diff}").into()
123 }
124 }
125 }
126
is_multiline_string_debug(string: &str) -> bool127 fn is_multiline_string_debug(string: &str) -> bool {
128 string.starts_with('"')
129 && string.ends_with('"')
130 && !string.contains('\n')
131 && string.contains("\\n")
132 }
133
to_display_output(string: &str) -> Option<String>134 fn to_display_output(string: &str) -> Option<String> {
135 Some(string.strip_prefix('"')?.strip_suffix('"')?.split("\\n").collect::<Vec<_>>().join("\n"))
136 }
137
138 #[cfg(test)]
139 mod tests {
140 use crate::prelude::*;
141 use indoc::indoc;
142
143 #[test]
eq_matches_string_reference_with_string_reference() -> Result<()>144 fn eq_matches_string_reference_with_string_reference() -> Result<()> {
145 verify_that!("A string", eq("A string"))
146 }
147
148 #[test]
eq_matches_owned_string_with_string_reference() -> Result<()>149 fn eq_matches_owned_string_with_string_reference() -> Result<()> {
150 let value = "A string".to_string();
151 verify_that!(value, eq("A string"))
152 }
153
154 #[test]
eq_matches_owned_string_reference_with_string_reference() -> Result<()>155 fn eq_matches_owned_string_reference_with_string_reference() -> Result<()> {
156 let value = "A string".to_string();
157 verify_that!(&value, eq("A string"))
158 }
159
160 #[test]
eq_matches_i32_with_i32() -> Result<()>161 fn eq_matches_i32_with_i32() -> Result<()> {
162 verify_that!(123, eq(123))
163 }
164
165 #[test]
eq_struct_debug_diff() -> Result<()>166 fn eq_struct_debug_diff() -> Result<()> {
167 #[derive(Debug, PartialEq)]
168 struct Strukt {
169 int: i32,
170 string: String,
171 }
172
173 let result = verify_that!(
174 Strukt { int: 123, string: "something".into() },
175 eq(&Strukt { int: 321, string: "someone".into() })
176 );
177 verify_that!(
178 result,
179 err(displays_as(contains_substring(indoc! {
180 "
181 Actual: Strukt { int: 123, string: \"something\" },
182 which isn't equal to Strukt { int: 321, string: \"someone\" }
183
184 Difference(-actual / +expected):
185 Strukt {
186 - int: 123,
187 + int: 321,
188 - string: \"something\",
189 + string: \"someone\",
190 }
191 "})))
192 )
193 }
194
195 #[test]
eq_vec_debug_diff() -> Result<()>196 fn eq_vec_debug_diff() -> Result<()> {
197 let result = verify_that!(vec![1, 2, 3], eq(&vec![1, 3, 4]));
198 verify_that!(
199 result,
200 err(displays_as(contains_substring(indoc! {
201 "
202 Value of: vec![1, 2, 3]
203 Expected: is equal to [1, 3, 4]
204 Actual: [1, 2, 3],
205 which isn't equal to [1, 3, 4]
206
207 Difference(-actual / +expected):
208 [
209 1,
210 - 2,
211 3,
212 + 4,
213 ]
214 "})))
215 )
216 }
217
218 #[test]
eq_vec_debug_diff_length_mismatch() -> Result<()>219 fn eq_vec_debug_diff_length_mismatch() -> Result<()> {
220 let result = verify_that!(vec![1, 2, 3, 4, 5], eq(&vec![1, 3, 5]));
221 verify_that!(
222 result,
223 err(displays_as(contains_substring(indoc! {
224 "
225 Value of: vec![1, 2, 3, 4, 5]
226 Expected: is equal to [1, 3, 5]
227 Actual: [1, 2, 3, 4, 5],
228 which isn't equal to [1, 3, 5]
229
230 Difference(-actual / +expected):
231 [
232 1,
233 - 2,
234 3,
235 - 4,
236 5,
237 ]
238 "})))
239 )
240 }
241
242 #[test]
eq_debug_diff_common_lines_omitted() -> Result<()>243 fn eq_debug_diff_common_lines_omitted() -> Result<()> {
244 let result = verify_that!((1..50).collect::<Vec<_>>(), eq(&(3..52).collect::<Vec<_>>()));
245 verify_that!(
246 result,
247 err(displays_as(contains_substring(indoc! {
248 "
249 ],
250 which isn't equal to [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
251
252 Difference(-actual / +expected):
253 [
254 - 1,
255 - 2,
256 3,
257 4,
258 <---- 43 common lines omitted ---->
259 48,
260 49,
261 + 50,
262 + 51,
263 ]"})))
264 )
265 }
266
267 #[test]
eq_debug_diff_5_common_lines_not_omitted() -> Result<()>268 fn eq_debug_diff_5_common_lines_not_omitted() -> Result<()> {
269 let result = verify_that!((1..8).collect::<Vec<_>>(), eq(&(3..10).collect::<Vec<_>>()));
270 verify_that!(
271 result,
272 err(displays_as(contains_substring(indoc! {
273 "
274 Actual: [1, 2, 3, 4, 5, 6, 7],
275 which isn't equal to [3, 4, 5, 6, 7, 8, 9]
276
277 Difference(-actual / +expected):
278 [
279 - 1,
280 - 2,
281 3,
282 4,
283 5,
284 6,
285 7,
286 + 8,
287 + 9,
288 ]"})))
289 )
290 }
291
292 #[test]
eq_debug_diff_start_common_lines_omitted() -> Result<()>293 fn eq_debug_diff_start_common_lines_omitted() -> Result<()> {
294 let result = verify_that!((1..50).collect::<Vec<_>>(), eq(&(1..52).collect::<Vec<_>>()));
295 verify_that!(
296 result,
297 err(displays_as(contains_substring(indoc! {
298 "
299 ],
300 which isn't equal to [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
301
302 Difference(-actual / +expected):
303 [
304 1,
305 <---- 46 common lines omitted ---->
306 48,
307 49,
308 + 50,
309 + 51,
310 ]"})))
311 )
312 }
313
314 #[test]
eq_debug_diff_end_common_lines_omitted() -> Result<()>315 fn eq_debug_diff_end_common_lines_omitted() -> Result<()> {
316 let result = verify_that!((1..52).collect::<Vec<_>>(), eq(&(3..52).collect::<Vec<_>>()));
317 verify_that!(
318 result,
319 err(displays_as(contains_substring(indoc! {
320 "
321 ],
322 which isn't equal to [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
323
324 Difference(-actual / +expected):
325 [
326 - 1,
327 - 2,
328 3,
329 4,
330 <---- 46 common lines omitted ---->
331 51,
332 ]"})))
333 )
334 }
335
336 #[test]
eq_multi_line_string_debug_diff() -> Result<()>337 fn eq_multi_line_string_debug_diff() -> Result<()> {
338 let result = verify_that!("One\nTwo\nThree", eq("One\nSix\nThree"));
339 // TODO: b/257454450 - Make this more useful, by potentially unescaping the
340 // line return.
341 verify_that!(
342 result,
343 err(displays_as(contains_substring(indoc! {
344 r#"
345 Value of: "One\nTwo\nThree"
346 Expected: is equal to "One\nSix\nThree"
347 Actual: "One\nTwo\nThree",
348 which isn't equal to "One\nSix\nThree"
349 "#})))
350 )
351 }
352
353 #[test]
match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()>354 fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()> {
355 let result = verify_that!(
356 indoc!(
357 "
358 First line
359 Second line
360 Third line
361 "
362 ),
363 eq(indoc!(
364 "
365 First line
366 Second lines
367 Third line
368 "
369 ))
370 );
371
372 verify_that!(
373 result,
374 err(displays_as(contains_substring(
375 "\
376 First line
377 -Second line
378 +Second lines
379 Third line"
380 )))
381 )
382 }
383
384 #[test]
match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()>385 fn match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()> {
386 let result = verify_that!(
387 "First line",
388 eq(indoc!(
389 "
390 First line
391 Second line
392 Third line
393 "
394 ))
395 );
396
397 verify_that!(
398 result,
399 err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
400 )
401 }
402
403 #[test]
match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()>404 fn match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()> {
405 let result = verify_that!(
406 indoc!(
407 "
408 First line
409 Second line
410 Third line
411 "
412 ),
413 eq("First line")
414 );
415
416 verify_that!(
417 result,
418 err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
419 )
420 }
421 }
422