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 crate::matcher_support::edit_distance;
18 use crate::matcher_support::summarize_diff::create_diff;
19
20 use std::{fmt::Debug, marker::PhantomData};
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<A: ?Sized, T>(expected: T) -> EqMatcher<A, T>74 pub fn eq<A: ?Sized, T>(expected: T) -> EqMatcher<A, T> {
75 EqMatcher { expected, phantom: Default::default() }
76 }
77
78 /// A matcher which matches a value equal to `expected`.
79 ///
80 /// See [`eq`].
81 pub struct EqMatcher<A: ?Sized, T> {
82 pub(crate) expected: T,
83 phantom: PhantomData<A>,
84 }
85
86 impl<T: Debug, A: Debug + ?Sized + PartialEq<T>> Matcher for EqMatcher<A, T> {
87 type ActualT = A;
88
matches(&self, actual: &A) -> MatcherResult89 fn matches(&self, actual: &A) -> MatcherResult {
90 (*actual == self.expected).into()
91 }
92
describe(&self, matcher_result: MatcherResult) -> Description93 fn describe(&self, matcher_result: MatcherResult) -> Description {
94 match matcher_result {
95 MatcherResult::Match => format!("is equal to {:?}", self.expected).into(),
96 MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected).into(),
97 }
98 }
99
explain_match(&self, actual: &A) -> Description100 fn explain_match(&self, actual: &A) -> Description {
101 let expected_debug = format!("{:#?}", self.expected);
102 let actual_debug = format!("{:#?}", actual);
103 let description = self.describe(self.matches(actual));
104
105 let diff = if is_multiline_string_debug(&actual_debug)
106 && is_multiline_string_debug(&expected_debug)
107 {
108 create_diff(
109 // The two calls below return None if and only if the strings expected_debug
110 // respectively actual_debug are not enclosed in ". The calls to
111 // is_multiline_string_debug above ensure that they are. So the calls cannot
112 // actually return None and unwrap() should not panic.
113 &to_display_output(&actual_debug).unwrap(),
114 &to_display_output(&expected_debug).unwrap(),
115 edit_distance::Mode::Exact,
116 )
117 } else {
118 create_diff(&actual_debug, &expected_debug, edit_distance::Mode::Exact)
119 };
120
121 format!("which {description}{diff}").into()
122 }
123 }
124
is_multiline_string_debug(string: &str) -> bool125 fn is_multiline_string_debug(string: &str) -> bool {
126 string.starts_with('"')
127 && string.ends_with('"')
128 && !string.contains('\n')
129 && string.contains("\\n")
130 }
131
to_display_output(string: &str) -> Option<String>132 fn to_display_output(string: &str) -> Option<String> {
133 Some(string.strip_prefix('"')?.strip_suffix('"')?.split("\\n").collect::<Vec<_>>().join("\n"))
134 }
135
136 #[cfg(test)]
137 mod tests {
138 use super::eq;
139 use crate::prelude::*;
140 use indoc::indoc;
141
142 #[test]
eq_matches_string_reference_with_string_reference() -> Result<()>143 fn eq_matches_string_reference_with_string_reference() -> Result<()> {
144 verify_that!("A string", eq("A string"))
145 }
146
147 #[test]
eq_matches_owned_string_with_string_reference() -> Result<()>148 fn eq_matches_owned_string_with_string_reference() -> Result<()> {
149 let value = "A string".to_string();
150 verify_that!(value, eq("A string"))
151 }
152
153 #[test]
eq_matches_owned_string_reference_with_string_reference() -> Result<()>154 fn eq_matches_owned_string_reference_with_string_reference() -> Result<()> {
155 let value = "A string".to_string();
156 verify_that!(&value, eq("A string"))
157 }
158
159 #[test]
eq_matches_i32_with_i32() -> Result<()>160 fn eq_matches_i32_with_i32() -> Result<()> {
161 verify_that!(123, eq(123))
162 }
163
164 #[test]
eq_struct_debug_diff() -> Result<()>165 fn eq_struct_debug_diff() -> Result<()> {
166 #[derive(Debug, PartialEq)]
167 struct Strukt {
168 int: i32,
169 string: String,
170 }
171
172 let result = verify_that!(
173 Strukt { int: 123, string: "something".into() },
174 eq(Strukt { int: 321, string: "someone".into() })
175 );
176 verify_that!(
177 result,
178 err(displays_as(contains_substring(indoc! {
179 "
180 Actual: Strukt { int: 123, string: \"something\" },
181 which isn't equal to Strukt { int: 321, string: \"someone\" }
182 Difference(-actual / +expected):
183 Strukt {
184 - int: 123,
185 + int: 321,
186 - string: \"something\",
187 + string: \"someone\",
188 }
189 "})))
190 )
191 }
192
193 #[test]
eq_vec_debug_diff() -> Result<()>194 fn eq_vec_debug_diff() -> Result<()> {
195 let result = verify_that!(vec![1, 2, 3], eq(vec![1, 3, 4]));
196 verify_that!(
197 result,
198 err(displays_as(contains_substring(indoc! {
199 "
200 Value of: vec![1, 2, 3]
201 Expected: is equal to [1, 3, 4]
202 Actual: [1, 2, 3],
203 which isn't equal to [1, 3, 4]
204 Difference(-actual / +expected):
205 [
206 1,
207 - 2,
208 3,
209 + 4,
210 ]
211 "})))
212 )
213 }
214
215 #[test]
eq_vec_debug_diff_length_mismatch() -> Result<()>216 fn eq_vec_debug_diff_length_mismatch() -> Result<()> {
217 let result = verify_that!(vec![1, 2, 3, 4, 5], eq(vec![1, 3, 5]));
218 verify_that!(
219 result,
220 err(displays_as(contains_substring(indoc! {
221 "
222 Value of: vec![1, 2, 3, 4, 5]
223 Expected: is equal to [1, 3, 5]
224 Actual: [1, 2, 3, 4, 5],
225 which isn't equal to [1, 3, 5]
226 Difference(-actual / +expected):
227 [
228 1,
229 - 2,
230 3,
231 - 4,
232 5,
233 ]
234 "})))
235 )
236 }
237
238 #[test]
eq_debug_diff_common_lines_omitted() -> Result<()>239 fn eq_debug_diff_common_lines_omitted() -> Result<()> {
240 let result = verify_that!((1..50).collect::<Vec<_>>(), eq((3..52).collect::<Vec<_>>()));
241 verify_that!(
242 result,
243 err(displays_as(contains_substring(indoc! {
244 "
245 ],
246 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]
247 Difference(-actual / +expected):
248 [
249 - 1,
250 - 2,
251 3,
252 4,
253 <---- 43 common lines omitted ---->
254 48,
255 49,
256 + 50,
257 + 51,
258 ]"})))
259 )
260 }
261
262 #[test]
eq_debug_diff_5_common_lines_not_omitted() -> Result<()>263 fn eq_debug_diff_5_common_lines_not_omitted() -> Result<()> {
264 let result = verify_that!((1..8).collect::<Vec<_>>(), eq((3..10).collect::<Vec<_>>()));
265 verify_that!(
266 result,
267 err(displays_as(contains_substring(indoc! {
268 "
269 Actual: [1, 2, 3, 4, 5, 6, 7],
270 which isn't equal to [3, 4, 5, 6, 7, 8, 9]
271 Difference(-actual / +expected):
272 [
273 - 1,
274 - 2,
275 3,
276 4,
277 5,
278 6,
279 7,
280 + 8,
281 + 9,
282 ]"})))
283 )
284 }
285
286 #[test]
eq_debug_diff_start_common_lines_omitted() -> Result<()>287 fn eq_debug_diff_start_common_lines_omitted() -> Result<()> {
288 let result = verify_that!((1..50).collect::<Vec<_>>(), eq((1..52).collect::<Vec<_>>()));
289 verify_that!(
290 result,
291 err(displays_as(contains_substring(indoc! {
292 "
293 ],
294 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]
295 Difference(-actual / +expected):
296 [
297 1,
298 <---- 46 common lines omitted ---->
299 48,
300 49,
301 + 50,
302 + 51,
303 ]"})))
304 )
305 }
306
307 #[test]
eq_debug_diff_end_common_lines_omitted() -> Result<()>308 fn eq_debug_diff_end_common_lines_omitted() -> Result<()> {
309 let result = verify_that!((1..52).collect::<Vec<_>>(), eq((3..52).collect::<Vec<_>>()));
310 verify_that!(
311 result,
312 err(displays_as(contains_substring(indoc! {
313 "
314 ],
315 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]
316 Difference(-actual / +expected):
317 [
318 - 1,
319 - 2,
320 3,
321 4,
322 <---- 46 common lines omitted ---->
323 51,
324 ]"})))
325 )
326 }
327
328 #[test]
eq_multi_line_string_debug_diff() -> Result<()>329 fn eq_multi_line_string_debug_diff() -> Result<()> {
330 let result = verify_that!("One\nTwo\nThree", eq("One\nSix\nThree"));
331 // TODO: b/257454450 - Make this more useful, by potentially unescaping the
332 // line return.
333 verify_that!(
334 result,
335 err(displays_as(contains_substring(indoc! {
336 r#"
337 Value of: "One\nTwo\nThree"
338 Expected: is equal to "One\nSix\nThree"
339 Actual: "One\nTwo\nThree",
340 which isn't equal to "One\nSix\nThree"
341 "#})))
342 )
343 }
344
345 #[test]
match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()>346 fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()> {
347 let result = verify_that!(
348 indoc!(
349 "
350 First line
351 Second line
352 Third line
353 "
354 ),
355 eq(indoc!(
356 "
357 First line
358 Second lines
359 Third line
360 "
361 ))
362 );
363
364 verify_that!(
365 result,
366 err(displays_as(contains_substring(
367 "\
368 First line
369 -Second line
370 +Second lines
371 Third line"
372 )))
373 )
374 }
375
376 #[test]
match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()>377 fn match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()> {
378 let result = verify_that!(
379 "First line",
380 eq(indoc!(
381 "
382 First line
383 Second line
384 Third line
385 "
386 ))
387 );
388
389 verify_that!(
390 result,
391 err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
392 )
393 }
394
395 #[test]
match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()>396 fn match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()> {
397 let result = verify_that!(
398 indoc!(
399 "
400 First line
401 Second line
402 Third line
403 "
404 ),
405 eq("First line")
406 );
407
408 verify_that!(
409 result,
410 err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
411 )
412 }
413 }
414