Skip to main content

googletest/matchers/
eq_matcher.rs

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
15use crate::description::Description;
16use crate::matcher::{Matcher, MatcherBase, MatcherResult};
17use crate::matcher_support::edit_distance;
18use crate::matcher_support::summarize_diff::create_diff;
19
20use 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.
74pub 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)]
82pub struct EqMatcher<T> {
83    pub(crate) expected: T,
84}
85
86impl<T: Debug, A: Debug + Copy + PartialEq<T>> Matcher<A> for EqMatcher<T> {
87    fn matches(&self, actual: A) -> MatcherResult {
88        (actual == self.expected).into()
89    }
90
91    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
98    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
127fn 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
134fn 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)]
139mod tests {
140    use crate::prelude::*;
141    use crate::Result;
142    use indoc::indoc;
143
144    #[test]
145    fn eq_matches_string_reference_with_string_reference() -> Result<()> {
146        verify_that!("A string", eq("A string"))
147    }
148
149    #[test]
150    fn eq_matches_owned_string_with_string_reference() -> Result<()> {
151        let value = "A string".to_string();
152        verify_that!(value, eq("A string"))
153    }
154
155    #[test]
156    fn eq_matches_owned_string_reference_with_string_reference() -> Result<()> {
157        let value = "A string".to_string();
158        verify_that!(&value, eq("A string"))
159    }
160
161    #[test]
162    fn eq_matches_i32_with_i32() -> Result<()> {
163        verify_that!(123, eq(123))
164    }
165
166    #[test]
167    fn eq_struct_debug_diff() -> Result<()> {
168        #[derive(Debug, PartialEq)]
169        struct Strukt {
170            int: i32,
171            string: String,
172        }
173
174        let result = verify_that!(
175            Strukt { int: 123, string: "something".into() },
176            eq(&Strukt { int: 321, string: "someone".into() })
177        );
178        verify_that!(
179            result,
180            err(displays_as(contains_substring(indoc! {
181            "
182            Actual: Strukt { int: 123, string: \"something\" },
183              which isn't equal to Strukt { int: 321, string: \"someone\" }
184              
185              Difference(-actual / +expected):
186               Strukt {
187              -    int: 123,
188              +    int: 321,
189              -    string: \"something\",
190              +    string: \"someone\",
191               }
192            "})))
193        )
194    }
195
196    #[test]
197    fn eq_vec_debug_diff() -> Result<()> {
198        let result = verify_that!(vec![1, 2, 3], eq(&vec![1, 3, 4]));
199        verify_that!(
200            result,
201            err(displays_as(contains_substring(indoc! {
202            "
203            Value of: vec![1, 2, 3]
204            Expected: is equal to [1, 3, 4]
205            Actual: [1, 2, 3],
206              which isn't equal to [1, 3, 4]
207              
208              Difference(-actual / +expected):
209               [
210                   1,
211              -    2,
212                   3,
213              +    4,
214               ]
215            "})))
216        )
217    }
218
219    #[test]
220    fn eq_vec_debug_diff_length_mismatch() -> Result<()> {
221        let result = verify_that!(vec![1, 2, 3, 4, 5], eq(&vec![1, 3, 5]));
222        verify_that!(
223            result,
224            err(displays_as(contains_substring(indoc! {
225            "
226            Value of: vec![1, 2, 3, 4, 5]
227            Expected: is equal to [1, 3, 5]
228            Actual: [1, 2, 3, 4, 5],
229              which isn't equal to [1, 3, 5]
230              
231              Difference(-actual / +expected):
232               [
233                   1,
234              -    2,
235                   3,
236              -    4,
237                   5,
238               ]
239            "})))
240        )
241    }
242
243    #[test]
244    fn eq_debug_diff_common_lines_omitted() -> Result<()> {
245        let result = verify_that!((1..50).collect::<Vec<_>>(), eq(&(3..52).collect::<Vec<_>>()));
246        verify_that!(
247            result,
248            err(displays_as(contains_substring(indoc! {
249            "
250            ],
251              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]
252              
253              Difference(-actual / +expected):
254               [
255              -    1,
256              -    2,
257                   3,
258                   4,
259               <---- 43 common lines omitted ---->
260                   48,
261                   49,
262              +    50,
263              +    51,
264               ]"})))
265        )
266    }
267
268    #[test]
269    fn eq_debug_diff_5_common_lines_not_omitted() -> Result<()> {
270        let result = verify_that!((1..8).collect::<Vec<_>>(), eq(&(3..10).collect::<Vec<_>>()));
271        verify_that!(
272            result,
273            err(displays_as(contains_substring(indoc! {
274            "
275            Actual: [1, 2, 3, 4, 5, 6, 7],
276              which isn't equal to [3, 4, 5, 6, 7, 8, 9]
277              
278              Difference(-actual / +expected):
279               [
280              -    1,
281              -    2,
282                   3,
283                   4,
284                   5,
285                   6,
286                   7,
287              +    8,
288              +    9,
289               ]"})))
290        )
291    }
292
293    #[test]
294    fn eq_debug_diff_start_common_lines_omitted() -> Result<()> {
295        let result = verify_that!((1..50).collect::<Vec<_>>(), eq(&(1..52).collect::<Vec<_>>()));
296        verify_that!(
297            result,
298            err(displays_as(contains_substring(indoc! {
299            "
300            ],
301              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]
302              
303              Difference(-actual / +expected):
304               [
305                   1,
306               <---- 46 common lines omitted ---->
307                   48,
308                   49,
309              +    50,
310              +    51,
311               ]"})))
312        )
313    }
314
315    #[test]
316    fn eq_debug_diff_end_common_lines_omitted() -> Result<()> {
317        let result = verify_that!((1..52).collect::<Vec<_>>(), eq(&(3..52).collect::<Vec<_>>()));
318        verify_that!(
319            result,
320            err(displays_as(contains_substring(indoc! {
321            "
322            ],
323              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]
324              
325              Difference(-actual / +expected):
326               [
327              -    1,
328              -    2,
329                   3,
330                   4,
331               <---- 46 common lines omitted ---->
332                   51,
333               ]"})))
334        )
335    }
336
337    #[test]
338    fn eq_multi_line_string_debug_diff() -> Result<()> {
339        let result = verify_that!("One\nTwo\nThree", eq("One\nSix\nThree"));
340        // TODO: b/257454450 - Make this more useful, by potentially unescaping the
341        // line return.
342        verify_that!(
343            result,
344            err(displays_as(contains_substring(indoc! {
345            r#"
346            Value of: "One\nTwo\nThree"
347            Expected: is equal to "One\nSix\nThree"
348            Actual: "One\nTwo\nThree",
349              which isn't equal to "One\nSix\nThree"
350            "#})))
351        )
352    }
353
354    #[test]
355    fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()> {
356        let result = verify_that!(
357            indoc!(
358                "
359                    First line
360                    Second line
361                    Third line
362                "
363            ),
364            eq(indoc!(
365                "
366                    First line
367                    Second lines
368                    Third line
369                "
370            ))
371        );
372
373        verify_that!(
374            result,
375            err(displays_as(contains_substring(
376                "\
377   First line
378  -Second line
379  +Second lines
380   Third line"
381            )))
382        )
383    }
384
385    #[test]
386    fn match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()> {
387        let result = verify_that!(
388            "First line",
389            eq(indoc!(
390                "
391                    First line
392                    Second line
393                    Third line
394                "
395            ))
396        );
397
398        verify_that!(
399            result,
400            err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
401        )
402    }
403
404    #[test]
405    fn match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()> {
406        let result = verify_that!(
407            indoc!(
408                "
409                    First line
410                    Second line
411                    Third line
412                "
413            ),
414            eq("First line")
415        );
416
417        verify_that!(
418            result,
419            err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
420        )
421    }
422}