1use 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
22pub fn eq<T>(expected: T) -> EqMatcher<T> {
75 EqMatcher { expected }
76}
77
78#[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 &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 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}