1 // Copyright 2023 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::{
16     description::Description,
17     matcher::{Matcher, MatcherResult},
18     matcher_support::{
19         edit_distance,
20         summarize_diff::{create_diff, create_diff_reversed},
21     },
22     matchers::{eq_deref_of_matcher::EqDerefOfMatcher, eq_matcher::EqMatcher},
23 };
24 use std::borrow::Cow;
25 use std::fmt::Debug;
26 use std::marker::PhantomData;
27 use std::ops::Deref;
28 
29 /// Matches a string containing a given substring.
30 ///
31 /// Both the actual value and the expected substring may be either a `String` or
32 /// a string reference.
33 ///
34 /// ```
35 /// # use googletest::prelude::*;
36 /// # fn should_pass_1() -> Result<()> {
37 /// verify_that!("Some value", contains_substring("Some"))?;  // Passes
38 /// #     Ok(())
39 /// # }
40 /// # fn should_fail() -> Result<()> {
41 /// verify_that!("Another value", contains_substring("Some"))?;   // Fails
42 /// #     Ok(())
43 /// # }
44 /// # fn should_pass_2() -> Result<()> {
45 /// verify_that!("Some value".to_string(), contains_substring("value"))?;   // Passes
46 /// verify_that!("Some value", contains_substring("value".to_string()))?;   // Passes
47 /// #     Ok(())
48 /// # }
49 /// # should_pass_1().unwrap();
50 /// # should_fail().unwrap_err();
51 /// # should_pass_2().unwrap();
52 /// ```
53 ///
54 /// See the [`StrMatcherConfigurator`] extension trait for more options on how
55 /// the string is matched.
56 ///
57 /// > Note on memory use: In most cases, this matcher does not allocate memory
58 /// > when matching strings. However, it must allocate copies of both the actual
59 /// > and expected values when matching strings while
60 /// > [`ignoring_ascii_case`][StrMatcherConfigurator::ignoring_ascii_case] is
61 /// > set.
contains_substring<A: ?Sized, T>(expected: T) -> StrMatcher<A, T>62 pub fn contains_substring<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> {
63     StrMatcher {
64         configuration: Configuration { mode: MatchMode::Contains, ..Default::default() },
65         expected,
66         phantom: Default::default(),
67     }
68 }
69 
70 /// Matches a string which starts with the given prefix.
71 ///
72 /// Both the actual value and the expected prefix may be either a `String` or
73 /// a string reference.
74 ///
75 /// ```
76 /// # use googletest::prelude::*;
77 /// # fn should_pass_1() -> Result<()> {
78 /// verify_that!("Some value", starts_with("Some"))?;  // Passes
79 /// #     Ok(())
80 /// # }
81 /// # fn should_fail_1() -> Result<()> {
82 /// verify_that!("Another value", starts_with("Some"))?;   // Fails
83 /// #     Ok(())
84 /// # }
85 /// # fn should_fail_2() -> Result<()> {
86 /// verify_that!("Some value", starts_with("value"))?;  // Fails
87 /// #     Ok(())
88 /// # }
89 /// # fn should_pass_2() -> Result<()> {
90 /// verify_that!("Some value".to_string(), starts_with("Some"))?;   // Passes
91 /// verify_that!("Some value", starts_with("Some".to_string()))?;   // Passes
92 /// #     Ok(())
93 /// # }
94 /// # should_pass_1().unwrap();
95 /// # should_fail_1().unwrap_err();
96 /// # should_fail_2().unwrap_err();
97 /// # should_pass_2().unwrap();
98 /// ```
99 ///
100 /// See the [`StrMatcherConfigurator`] extension trait for more options on how
101 /// the string is matched.
starts_with<A: ?Sized, T>(expected: T) -> StrMatcher<A, T>102 pub fn starts_with<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> {
103     StrMatcher {
104         configuration: Configuration { mode: MatchMode::StartsWith, ..Default::default() },
105         expected,
106         phantom: Default::default(),
107     }
108 }
109 
110 /// Matches a string which ends with the given suffix.
111 ///
112 /// Both the actual value and the expected suffix may be either a `String` or
113 /// a string reference.
114 ///
115 /// ```
116 /// # use googletest::prelude::*;
117 /// # fn should_pass_1() -> Result<()> {
118 /// verify_that!("Some value", ends_with("value"))?;  // Passes
119 /// #     Ok(())
120 /// # }
121 /// # fn should_fail_1() -> Result<()> {
122 /// verify_that!("Some value", ends_with("other value"))?;   // Fails
123 /// #     Ok(())
124 /// # }
125 /// # fn should_fail_2() -> Result<()> {
126 /// verify_that!("Some value", ends_with("Some"))?;  // Fails
127 /// #     Ok(())
128 /// # }
129 /// # fn should_pass_2() -> Result<()> {
130 /// verify_that!("Some value".to_string(), ends_with("value"))?;   // Passes
131 /// verify_that!("Some value", ends_with("value".to_string()))?;   // Passes
132 /// #     Ok(())
133 /// # }
134 /// # should_pass_1().unwrap();
135 /// # should_fail_1().unwrap_err();
136 /// # should_fail_2().unwrap_err();
137 /// # should_pass_2().unwrap();
138 /// ```
139 ///
140 /// See the [`StrMatcherConfigurator`] extension trait for more options on how
141 /// the string is matched.
ends_with<A: ?Sized, T>(expected: T) -> StrMatcher<A, T>142 pub fn ends_with<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> {
143     StrMatcher {
144         configuration: Configuration { mode: MatchMode::EndsWith, ..Default::default() },
145         expected,
146         phantom: Default::default(),
147     }
148 }
149 
150 /// Extension trait to configure [`StrMatcher`].
151 ///
152 /// Matchers which match against string values and, through configuration,
153 /// specialise to [`StrMatcher`] implement this trait. That includes
154 /// [`EqMatcher`] and [`StrMatcher`].
155 pub trait StrMatcherConfigurator<ActualT: ?Sized, ExpectedT> {
156     /// Configures the matcher to ignore any leading whitespace in either the
157     /// actual or the expected value.
158     ///
159     /// Whitespace is defined as in [`str::trim_start`].
160     ///
161     /// ```
162     /// # use googletest::prelude::*;
163     /// # fn should_pass() -> Result<()> {
164     /// verify_that!("A string", eq("   A string").ignoring_leading_whitespace())?; // Passes
165     /// verify_that!("   A string", eq("A string").ignoring_leading_whitespace())?; // Passes
166     /// #     Ok(())
167     /// # }
168     /// # should_pass().unwrap();
169     /// ```
170     ///
171     /// When all other configuration options are left as the defaults, this is
172     /// equivalent to invoking [`str::trim_start`] on both the expected and
173     /// actual value.
ignoring_leading_whitespace(self) -> StrMatcher<ActualT, ExpectedT>174     fn ignoring_leading_whitespace(self) -> StrMatcher<ActualT, ExpectedT>;
175 
176     /// Configures the matcher to ignore any trailing whitespace in either the
177     /// actual or the expected value.
178     ///
179     /// Whitespace is defined as in [`str::trim_end`].
180     ///
181     /// ```
182     /// # use googletest::prelude::*;
183     /// # fn should_pass() -> Result<()> {
184     /// verify_that!("A string", eq("A string   ").ignoring_trailing_whitespace())?; // Passes
185     /// verify_that!("A string   ", eq("A string").ignoring_trailing_whitespace())?; // Passes
186     /// #     Ok(())
187     /// # }
188     /// # should_pass().unwrap();
189     /// ```
190     ///
191     /// When all other configuration options are left as the defaults, this is
192     /// equivalent to invoking [`str::trim_end`] on both the expected and
193     /// actual value.
ignoring_trailing_whitespace(self) -> StrMatcher<ActualT, ExpectedT>194     fn ignoring_trailing_whitespace(self) -> StrMatcher<ActualT, ExpectedT>;
195 
196     /// Configures the matcher to ignore both leading and trailing whitespace in
197     /// either the actual or the expected value.
198     ///
199     /// Whitespace is defined as in [`str::trim`].
200     ///
201     /// ```
202     /// # use googletest::prelude::*;
203     /// # fn should_pass() -> Result<()> {
204     /// verify_that!("A string", eq("   A string   ").ignoring_outer_whitespace())?; // Passes
205     /// verify_that!("   A string   ", eq("A string").ignoring_outer_whitespace())?; // Passes
206     /// #     Ok(())
207     /// # }
208     /// # should_pass().unwrap();
209     /// ```
210     ///
211     /// This is equivalent to invoking both
212     /// [`ignoring_leading_whitespace`][StrMatcherConfigurator::ignoring_leading_whitespace] and
213     /// [`ignoring_trailing_whitespace`][StrMatcherConfigurator::ignoring_trailing_whitespace].
214     ///
215     /// When all other configuration options are left as the defaults, this is
216     /// equivalent to invoking [`str::trim`] on both the expected and actual
217     /// value.
ignoring_outer_whitespace(self) -> StrMatcher<ActualT, ExpectedT>218     fn ignoring_outer_whitespace(self) -> StrMatcher<ActualT, ExpectedT>;
219 
220     /// Configures the matcher to ignore ASCII case when comparing values.
221     ///
222     /// This uses the same rules for case as [`str::eq_ignore_ascii_case`].
223     ///
224     /// ```
225     /// # use googletest::prelude::*;
226     /// # fn should_pass() -> Result<()> {
227     /// verify_that!("Some value", eq("SOME VALUE").ignoring_ascii_case())?;  // Passes
228     /// #     Ok(())
229     /// # }
230     /// # fn should_fail() -> Result<()> {
231     /// verify_that!("Another value", eq("Some value").ignoring_ascii_case())?;   // Fails
232     /// #     Ok(())
233     /// # }
234     /// # should_pass().unwrap();
235     /// # should_fail().unwrap_err();
236     /// ```
237     ///
238     /// This is **not guaranteed** to match strings with differing upper/lower
239     /// case characters outside of the codepoints 0-127 covered by ASCII.
ignoring_ascii_case(self) -> StrMatcher<ActualT, ExpectedT>240     fn ignoring_ascii_case(self) -> StrMatcher<ActualT, ExpectedT>;
241 
242     /// Configures the matcher to match only strings which otherwise satisfy the
243     /// conditions a number times matched by the matcher `times`.
244     ///
245     /// ```
246     /// # use googletest::prelude::*;
247     /// # fn should_pass() -> Result<()> {
248     /// verify_that!("Some value\nSome value", contains_substring("value").times(eq(2)))?; // Passes
249     /// #     Ok(())
250     /// # }
251     /// # fn should_fail() -> Result<()> {
252     /// verify_that!("Some value", contains_substring("value").times(eq(2)))?; // Fails
253     /// #     Ok(())
254     /// # }
255     /// # should_pass().unwrap();
256     /// # should_fail().unwrap_err();
257     /// ```
258     ///
259     /// The matched substrings must be disjoint from one another to be counted.
260     /// For example:
261     ///
262     /// ```
263     /// # use googletest::prelude::*;
264     /// # fn should_fail() -> Result<()> {
265     /// // Fails: substrings distinct but not disjoint!
266     /// verify_that!("ababab", contains_substring("abab").times(eq(2)))?;
267     /// #     Ok(())
268     /// # }
269     /// # should_fail().unwrap_err();
270     /// ```
271     ///
272     /// This is only meaningful when the matcher was constructed with
273     /// [`contains_substring`]. This method will panic when it is used with any
274     /// other matcher construction.
times( self, times: impl Matcher<ActualT = usize> + 'static, ) -> StrMatcher<ActualT, ExpectedT>275     fn times(
276         self,
277         times: impl Matcher<ActualT = usize> + 'static,
278     ) -> StrMatcher<ActualT, ExpectedT>;
279 }
280 
281 /// A matcher which matches equality or containment of a string-like value in a
282 /// configurable way.
283 ///
284 /// The following matcher methods instantiate this:
285 ///
286 ///  * [`eq`][crate::matchers::eq_matcher::eq],
287 ///  * [`contains_substring`],
288 ///  * [`starts_with`],
289 ///  * [`ends_with`].
290 pub struct StrMatcher<ActualT: ?Sized, ExpectedT> {
291     expected: ExpectedT,
292     configuration: Configuration,
293     phantom: PhantomData<ActualT>,
294 }
295 
296 impl<ExpectedT, ActualT> Matcher for StrMatcher<ActualT, ExpectedT>
297 where
298     ExpectedT: Deref<Target = str> + Debug,
299     ActualT: AsRef<str> + Debug + ?Sized,
300 {
301     type ActualT = ActualT;
302 
matches(&self, actual: &ActualT) -> MatcherResult303     fn matches(&self, actual: &ActualT) -> MatcherResult {
304         self.configuration.do_strings_match(self.expected.deref(), actual.as_ref()).into()
305     }
306 
describe(&self, matcher_result: MatcherResult) -> Description307     fn describe(&self, matcher_result: MatcherResult) -> Description {
308         self.configuration.describe(matcher_result, self.expected.deref())
309     }
310 
explain_match(&self, actual: &ActualT) -> Description311     fn explain_match(&self, actual: &ActualT) -> Description {
312         self.configuration.explain_match(self.expected.deref(), actual.as_ref())
313     }
314 }
315 
316 impl<ActualT: ?Sized, ExpectedT, MatcherT: Into<StrMatcher<ActualT, ExpectedT>>>
317     StrMatcherConfigurator<ActualT, ExpectedT> for MatcherT
318 {
ignoring_leading_whitespace(self) -> StrMatcher<ActualT, ExpectedT>319     fn ignoring_leading_whitespace(self) -> StrMatcher<ActualT, ExpectedT> {
320         let existing = self.into();
321         StrMatcher {
322             configuration: existing.configuration.ignoring_leading_whitespace(),
323             ..existing
324         }
325     }
326 
ignoring_trailing_whitespace(self) -> StrMatcher<ActualT, ExpectedT>327     fn ignoring_trailing_whitespace(self) -> StrMatcher<ActualT, ExpectedT> {
328         let existing = self.into();
329         StrMatcher {
330             configuration: existing.configuration.ignoring_trailing_whitespace(),
331             ..existing
332         }
333     }
334 
ignoring_outer_whitespace(self) -> StrMatcher<ActualT, ExpectedT>335     fn ignoring_outer_whitespace(self) -> StrMatcher<ActualT, ExpectedT> {
336         let existing = self.into();
337         StrMatcher { configuration: existing.configuration.ignoring_outer_whitespace(), ..existing }
338     }
339 
ignoring_ascii_case(self) -> StrMatcher<ActualT, ExpectedT>340     fn ignoring_ascii_case(self) -> StrMatcher<ActualT, ExpectedT> {
341         let existing = self.into();
342         StrMatcher { configuration: existing.configuration.ignoring_ascii_case(), ..existing }
343     }
344 
times( self, times: impl Matcher<ActualT = usize> + 'static, ) -> StrMatcher<ActualT, ExpectedT>345     fn times(
346         self,
347         times: impl Matcher<ActualT = usize> + 'static,
348     ) -> StrMatcher<ActualT, ExpectedT> {
349         let existing = self.into();
350         if !matches!(existing.configuration.mode, MatchMode::Contains) {
351             panic!("The times() configurator is only meaningful with contains_substring().");
352         }
353         StrMatcher { configuration: existing.configuration.times(times), ..existing }
354     }
355 }
356 
357 impl<A: ?Sized, T: Deref<Target = str>> From<EqMatcher<A, T>> for StrMatcher<A, T> {
from(value: EqMatcher<A, T>) -> Self358     fn from(value: EqMatcher<A, T>) -> Self {
359         Self::with_default_config(value.expected)
360     }
361 }
362 
363 impl<A: ?Sized, T: Deref<Target = str>> From<EqDerefOfMatcher<A, T>> for StrMatcher<A, T> {
from(value: EqDerefOfMatcher<A, T>) -> Self364     fn from(value: EqDerefOfMatcher<A, T>) -> Self {
365         Self::with_default_config(value.expected)
366     }
367 }
368 
369 impl<A: ?Sized, T> StrMatcher<A, T> {
370     /// Returns a [`StrMatcher`] with a default configuration to match against
371     /// the given expected value.
372     ///
373     /// This default configuration is sensitive to whitespace and case.
with_default_config(expected: T) -> Self374     fn with_default_config(expected: T) -> Self {
375         Self { expected, configuration: Default::default(), phantom: Default::default() }
376     }
377 }
378 
379 // Holds all the information on how the expected and actual strings are to be
380 // compared. Its associated functions perform the actual matching operations
381 // on string references. The struct and comparison methods therefore need not be
382 // parameterised, saving compilation time and binary size on monomorphisation.
383 //
384 // The default value represents exact equality of the strings.
385 struct Configuration {
386     mode: MatchMode,
387     ignore_leading_whitespace: bool,
388     ignore_trailing_whitespace: bool,
389     case_policy: CasePolicy,
390     times: Option<Box<dyn Matcher<ActualT = usize>>>,
391 }
392 
393 #[derive(Clone)]
394 enum MatchMode {
395     Equals,
396     Contains,
397     StartsWith,
398     EndsWith,
399 }
400 
401 impl MatchMode {
to_diff_mode(&self) -> edit_distance::Mode402     fn to_diff_mode(&self) -> edit_distance::Mode {
403         match self {
404             MatchMode::StartsWith | MatchMode::EndsWith => edit_distance::Mode::Prefix,
405             MatchMode::Contains => edit_distance::Mode::Contains,
406             MatchMode::Equals => edit_distance::Mode::Exact,
407         }
408     }
409 }
410 
411 #[derive(Clone)]
412 enum CasePolicy {
413     Respect,
414     IgnoreAscii,
415 }
416 
417 impl Configuration {
418     // The entry point for all string matching. StrMatcher::matches redirects
419     // immediately to this function.
do_strings_match(&self, expected: &str, actual: &str) -> bool420     fn do_strings_match(&self, expected: &str, actual: &str) -> bool {
421         let (expected, actual) =
422             match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) {
423                 (true, true) => (expected.trim(), actual.trim()),
424                 (true, false) => (expected.trim_start(), actual.trim_start()),
425                 (false, true) => (expected.trim_end(), actual.trim_end()),
426                 (false, false) => (expected, actual),
427             };
428         match self.mode {
429             MatchMode::Equals => match self.case_policy {
430                 CasePolicy::Respect => expected == actual,
431                 CasePolicy::IgnoreAscii => expected.eq_ignore_ascii_case(actual),
432             },
433             MatchMode::Contains => match self.case_policy {
434                 CasePolicy::Respect => self.does_containment_match(actual, expected),
435                 CasePolicy::IgnoreAscii => self.does_containment_match(
436                     actual.to_ascii_lowercase().as_str(),
437                     expected.to_ascii_lowercase().as_str(),
438                 ),
439             },
440             MatchMode::StartsWith => match self.case_policy {
441                 CasePolicy::Respect => actual.starts_with(expected),
442                 CasePolicy::IgnoreAscii => {
443                     actual.len() >= expected.len()
444                         && actual[..expected.len()].eq_ignore_ascii_case(expected)
445                 }
446             },
447             MatchMode::EndsWith => match self.case_policy {
448                 CasePolicy::Respect => actual.ends_with(expected),
449                 CasePolicy::IgnoreAscii => {
450                     actual.len() >= expected.len()
451                         && actual[actual.len() - expected.len()..].eq_ignore_ascii_case(expected)
452                 }
453             },
454         }
455     }
456 
457     // Returns whether actual contains expected a number of times matched by the
458     // matcher self.times. Does not take other configuration into account.
does_containment_match(&self, actual: &str, expected: &str) -> bool459     fn does_containment_match(&self, actual: &str, expected: &str) -> bool {
460         if let Some(times) = self.times.as_ref() {
461             // Split returns an iterator over the "boundaries" left and right of the
462             // substring to be matched, of which there is one more than the number of
463             // substrings.
464             matches!(times.matches(&(actual.split(expected).count() - 1)), MatcherResult::Match)
465         } else {
466             actual.contains(expected)
467         }
468     }
469 
470     // StrMatcher::describe redirects immediately to this function.
describe(&self, matcher_result: MatcherResult, expected: &str) -> Description471     fn describe(&self, matcher_result: MatcherResult, expected: &str) -> Description {
472         let mut addenda: Vec<Cow<'static, str>> = Vec::with_capacity(3);
473         match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) {
474             (true, true) => addenda.push("ignoring leading and trailing whitespace".into()),
475             (true, false) => addenda.push("ignoring leading whitespace".into()),
476             (false, true) => addenda.push("ignoring trailing whitespace".into()),
477             (false, false) => {}
478         }
479         match self.case_policy {
480             CasePolicy::Respect => {}
481             CasePolicy::IgnoreAscii => addenda.push("ignoring ASCII case".into()),
482         }
483         if let Some(times) = self.times.as_ref() {
484             addenda.push(format!("count {}", times.describe(matcher_result)).into());
485         }
486         let extra =
487             if !addenda.is_empty() { format!(" ({})", addenda.join(", ")) } else { "".into() };
488         let match_mode_description = match self.mode {
489             MatchMode::Equals => match matcher_result {
490                 MatcherResult::Match => "is equal to",
491                 MatcherResult::NoMatch => "isn't equal to",
492             },
493             MatchMode::Contains => match matcher_result {
494                 MatcherResult::Match => "contains a substring",
495                 MatcherResult::NoMatch => "does not contain a substring",
496             },
497             MatchMode::StartsWith => match matcher_result {
498                 MatcherResult::Match => "starts with prefix",
499                 MatcherResult::NoMatch => "does not start with",
500             },
501             MatchMode::EndsWith => match matcher_result {
502                 MatcherResult::Match => "ends with suffix",
503                 MatcherResult::NoMatch => "does not end with",
504             },
505         };
506         format!("{match_mode_description} {expected:?}{extra}").into()
507     }
508 
explain_match(&self, expected: &str, actual: &str) -> Description509     fn explain_match(&self, expected: &str, actual: &str) -> Description {
510         let default_explanation = format!(
511             "which {}",
512             self.describe(self.do_strings_match(expected, actual).into(), expected)
513         )
514         .into();
515         if !expected.contains('\n') || !actual.contains('\n') {
516             return default_explanation;
517         }
518 
519         if self.ignore_leading_whitespace {
520             // TODO - b/283448414 : Support StrMatcher with ignore_leading_whitespace.
521             return default_explanation;
522         }
523 
524         if self.ignore_trailing_whitespace {
525             // TODO - b/283448414 : Support StrMatcher with ignore_trailing_whitespace.
526             return default_explanation;
527         }
528 
529         if self.times.is_some() {
530             // TODO - b/283448414 : Support StrMatcher with times.
531             return default_explanation;
532         }
533         if matches!(self.case_policy, CasePolicy::IgnoreAscii) {
534             // TODO - b/283448414 : Support StrMatcher with ignore ascii case policy.
535             return default_explanation;
536         }
537         if self.do_strings_match(expected, actual) {
538             // TODO - b/283448414 : Consider supporting debug difference if the
539             // strings match. This can be useful when a small contains is found
540             // in a long string.
541             return default_explanation;
542         }
543 
544         let diff = match self.mode {
545             MatchMode::Equals | MatchMode::StartsWith | MatchMode::Contains => {
546                 // TODO(b/287632452): Also consider improving the output in MatchMode::Contains
547                 // when the substring begins or ends in the middle of a line of the actual
548                 // value.
549                 create_diff(actual, expected, self.mode.to_diff_mode())
550             }
551             MatchMode::EndsWith => create_diff_reversed(actual, expected, self.mode.to_diff_mode()),
552         };
553 
554         format!("{default_explanation}\n{diff}").into()
555     }
556 
ignoring_leading_whitespace(self) -> Self557     fn ignoring_leading_whitespace(self) -> Self {
558         Self { ignore_leading_whitespace: true, ..self }
559     }
560 
ignoring_trailing_whitespace(self) -> Self561     fn ignoring_trailing_whitespace(self) -> Self {
562         Self { ignore_trailing_whitespace: true, ..self }
563     }
564 
ignoring_outer_whitespace(self) -> Self565     fn ignoring_outer_whitespace(self) -> Self {
566         Self { ignore_leading_whitespace: true, ignore_trailing_whitespace: true, ..self }
567     }
568 
ignoring_ascii_case(self) -> Self569     fn ignoring_ascii_case(self) -> Self {
570         Self { case_policy: CasePolicy::IgnoreAscii, ..self }
571     }
572 
times(self, times: impl Matcher<ActualT = usize> + 'static) -> Self573     fn times(self, times: impl Matcher<ActualT = usize> + 'static) -> Self {
574         Self { times: Some(Box::new(times)), ..self }
575     }
576 }
577 
578 impl Default for Configuration {
default() -> Self579     fn default() -> Self {
580         Self {
581             mode: MatchMode::Equals,
582             ignore_leading_whitespace: false,
583             ignore_trailing_whitespace: false,
584             case_policy: CasePolicy::Respect,
585             times: None,
586         }
587     }
588 }
589 
590 #[cfg(test)]
591 mod tests {
592     use super::{contains_substring, ends_with, starts_with, StrMatcher, StrMatcherConfigurator};
593     use crate::matcher::{Matcher, MatcherResult};
594     use crate::prelude::*;
595     use indoc::indoc;
596 
597     #[test]
matches_string_reference_with_equal_string_reference() -> Result<()>598     fn matches_string_reference_with_equal_string_reference() -> Result<()> {
599         let matcher = StrMatcher::with_default_config("A string");
600         verify_that!("A string", matcher)
601     }
602 
603     #[test]
does_not_match_string_reference_with_non_equal_string_reference() -> Result<()>604     fn does_not_match_string_reference_with_non_equal_string_reference() -> Result<()> {
605         let matcher = StrMatcher::with_default_config("Another string");
606         verify_that!("A string", not(matcher))
607     }
608 
609     #[test]
matches_owned_string_with_string_reference() -> Result<()>610     fn matches_owned_string_with_string_reference() -> Result<()> {
611         let matcher = StrMatcher::with_default_config("A string");
612         let value = "A string".to_string();
613         verify_that!(value, matcher)
614     }
615 
616     #[test]
matches_owned_string_reference_with_string_reference() -> Result<()>617     fn matches_owned_string_reference_with_string_reference() -> Result<()> {
618         let matcher = StrMatcher::with_default_config("A string");
619         let value = "A string".to_string();
620         verify_that!(&value, matcher)
621     }
622 
623     #[test]
ignores_leading_whitespace_in_expected_when_requested() -> Result<()>624     fn ignores_leading_whitespace_in_expected_when_requested() -> Result<()> {
625         let matcher = StrMatcher::with_default_config(" \n\tA string");
626         verify_that!("A string", matcher.ignoring_leading_whitespace())
627     }
628 
629     #[test]
ignores_leading_whitespace_in_actual_when_requested() -> Result<()>630     fn ignores_leading_whitespace_in_actual_when_requested() -> Result<()> {
631         let matcher = StrMatcher::with_default_config("A string");
632         verify_that!(" \n\tA string", matcher.ignoring_leading_whitespace())
633     }
634 
635     #[test]
does_not_match_unequal_remaining_string_when_ignoring_leading_whitespace() -> Result<()>636     fn does_not_match_unequal_remaining_string_when_ignoring_leading_whitespace() -> Result<()> {
637         let matcher = StrMatcher::with_default_config(" \n\tAnother string");
638         verify_that!("A string", not(matcher.ignoring_leading_whitespace()))
639     }
640 
641     #[test]
remains_sensitive_to_trailing_whitespace_when_ignoring_leading_whitespace() -> Result<()>642     fn remains_sensitive_to_trailing_whitespace_when_ignoring_leading_whitespace() -> Result<()> {
643         let matcher = StrMatcher::with_default_config("A string \n\t");
644         verify_that!("A string", not(matcher.ignoring_leading_whitespace()))
645     }
646 
647     #[test]
ignores_trailing_whitespace_in_expected_when_requested() -> Result<()>648     fn ignores_trailing_whitespace_in_expected_when_requested() -> Result<()> {
649         let matcher = StrMatcher::with_default_config("A string \n\t");
650         verify_that!("A string", matcher.ignoring_trailing_whitespace())
651     }
652 
653     #[test]
ignores_trailing_whitespace_in_actual_when_requested() -> Result<()>654     fn ignores_trailing_whitespace_in_actual_when_requested() -> Result<()> {
655         let matcher = StrMatcher::with_default_config("A string");
656         verify_that!("A string \n\t", matcher.ignoring_trailing_whitespace())
657     }
658 
659     #[test]
does_not_match_unequal_remaining_string_when_ignoring_trailing_whitespace() -> Result<()>660     fn does_not_match_unequal_remaining_string_when_ignoring_trailing_whitespace() -> Result<()> {
661         let matcher = StrMatcher::with_default_config("Another string \n\t");
662         verify_that!("A string", not(matcher.ignoring_trailing_whitespace()))
663     }
664 
665     #[test]
remains_sensitive_to_leading_whitespace_when_ignoring_trailing_whitespace() -> Result<()>666     fn remains_sensitive_to_leading_whitespace_when_ignoring_trailing_whitespace() -> Result<()> {
667         let matcher = StrMatcher::with_default_config(" \n\tA string");
668         verify_that!("A string", not(matcher.ignoring_trailing_whitespace()))
669     }
670 
671     #[test]
ignores_leading_and_trailing_whitespace_in_expected_when_requested() -> Result<()>672     fn ignores_leading_and_trailing_whitespace_in_expected_when_requested() -> Result<()> {
673         let matcher = StrMatcher::with_default_config(" \n\tA string \n\t");
674         verify_that!("A string", matcher.ignoring_outer_whitespace())
675     }
676 
677     #[test]
ignores_leading_and_trailing_whitespace_in_actual_when_requested() -> Result<()>678     fn ignores_leading_and_trailing_whitespace_in_actual_when_requested() -> Result<()> {
679         let matcher = StrMatcher::with_default_config("A string");
680         verify_that!(" \n\tA string \n\t", matcher.ignoring_outer_whitespace())
681     }
682 
683     #[test]
respects_ascii_case_by_default() -> Result<()>684     fn respects_ascii_case_by_default() -> Result<()> {
685         let matcher = StrMatcher::with_default_config("A string");
686         verify_that!("A STRING", not(matcher))
687     }
688 
689     #[test]
ignores_ascii_case_when_requested() -> Result<()>690     fn ignores_ascii_case_when_requested() -> Result<()> {
691         let matcher = StrMatcher::with_default_config("A string");
692         verify_that!("A STRING", matcher.ignoring_ascii_case())
693     }
694 
695     #[test]
allows_ignoring_leading_whitespace_from_eq() -> Result<()>696     fn allows_ignoring_leading_whitespace_from_eq() -> Result<()> {
697         verify_that!("A string", eq(" \n\tA string").ignoring_leading_whitespace())
698     }
699 
700     #[test]
allows_ignoring_trailing_whitespace_from_eq() -> Result<()>701     fn allows_ignoring_trailing_whitespace_from_eq() -> Result<()> {
702         verify_that!("A string", eq("A string \n\t").ignoring_trailing_whitespace())
703     }
704 
705     #[test]
allows_ignoring_outer_whitespace_from_eq() -> Result<()>706     fn allows_ignoring_outer_whitespace_from_eq() -> Result<()> {
707         verify_that!("A string", eq(" \n\tA string \n\t").ignoring_outer_whitespace())
708     }
709 
710     #[test]
allows_ignoring_ascii_case_from_eq() -> Result<()>711     fn allows_ignoring_ascii_case_from_eq() -> Result<()> {
712         verify_that!("A string", eq("A STRING").ignoring_ascii_case())
713     }
714 
715     #[test]
allows_ignoring_ascii_case_from_eq_deref_of_str_slice() -> Result<()>716     fn allows_ignoring_ascii_case_from_eq_deref_of_str_slice() -> Result<()> {
717         verify_that!("A string", eq_deref_of("A STRING").ignoring_ascii_case())
718     }
719 
720     #[test]
allows_ignoring_ascii_case_from_eq_deref_of_owned_string() -> Result<()>721     fn allows_ignoring_ascii_case_from_eq_deref_of_owned_string() -> Result<()> {
722         verify_that!("A string", eq_deref_of("A STRING".to_string()).ignoring_ascii_case())
723     }
724 
725     #[test]
matches_string_containing_expected_value_in_contains_mode() -> Result<()>726     fn matches_string_containing_expected_value_in_contains_mode() -> Result<()> {
727         verify_that!("Some string", contains_substring("str"))
728     }
729 
730     #[test]
matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case() -> Result<()>731     fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case()
732     -> Result<()> {
733         verify_that!("Some string", contains_substring("STR").ignoring_ascii_case())
734     }
735 
736     #[test]
contains_substring_matches_correct_number_of_substrings() -> Result<()>737     fn contains_substring_matches_correct_number_of_substrings() -> Result<()> {
738         verify_that!("Some string", contains_substring("str").times(eq(1)))
739     }
740 
741     #[test]
contains_substring_does_not_match_incorrect_number_of_substrings() -> Result<()>742     fn contains_substring_does_not_match_incorrect_number_of_substrings() -> Result<()> {
743         verify_that!("Some string\nSome string", not(contains_substring("string").times(eq(1))))
744     }
745 
746     #[test]
contains_substring_does_not_match_when_substrings_overlap() -> Result<()>747     fn contains_substring_does_not_match_when_substrings_overlap() -> Result<()> {
748         verify_that!("ababab", not(contains_substring("abab").times(eq(2))))
749     }
750 
751     #[test]
starts_with_matches_string_reference_with_prefix() -> Result<()>752     fn starts_with_matches_string_reference_with_prefix() -> Result<()> {
753         verify_that!("Some value", starts_with("Some"))
754     }
755 
756     #[test]
starts_with_matches_string_reference_with_prefix_ignoring_ascii_case() -> Result<()>757     fn starts_with_matches_string_reference_with_prefix_ignoring_ascii_case() -> Result<()> {
758         verify_that!("Some value", starts_with("SOME").ignoring_ascii_case())
759     }
760 
761     #[test]
starts_with_does_not_match_wrong_prefix_ignoring_ascii_case() -> Result<()>762     fn starts_with_does_not_match_wrong_prefix_ignoring_ascii_case() -> Result<()> {
763         verify_that!("Some value", not(starts_with("OTHER").ignoring_ascii_case()))
764     }
765 
766     #[test]
ends_with_does_not_match_short_string_ignoring_ascii_case() -> Result<()>767     fn ends_with_does_not_match_short_string_ignoring_ascii_case() -> Result<()> {
768         verify_that!("Some", not(starts_with("OTHER").ignoring_ascii_case()))
769     }
770 
771     #[test]
starts_with_does_not_match_string_without_prefix() -> Result<()>772     fn starts_with_does_not_match_string_without_prefix() -> Result<()> {
773         verify_that!("Some value", not(starts_with("Another")))
774     }
775 
776     #[test]
starts_with_does_not_match_string_with_substring_not_at_beginning() -> Result<()>777     fn starts_with_does_not_match_string_with_substring_not_at_beginning() -> Result<()> {
778         verify_that!("Some value", not(starts_with("value")))
779     }
780 
781     #[test]
ends_with_matches_string_reference_with_suffix() -> Result<()>782     fn ends_with_matches_string_reference_with_suffix() -> Result<()> {
783         verify_that!("Some value", ends_with("value"))
784     }
785 
786     #[test]
ends_with_matches_string_reference_with_suffix_ignoring_ascii_case() -> Result<()>787     fn ends_with_matches_string_reference_with_suffix_ignoring_ascii_case() -> Result<()> {
788         verify_that!("Some value", ends_with("VALUE").ignoring_ascii_case())
789     }
790 
791     #[test]
ends_with_does_not_match_wrong_suffix_ignoring_ascii_case() -> Result<()>792     fn ends_with_does_not_match_wrong_suffix_ignoring_ascii_case() -> Result<()> {
793         verify_that!("Some value", not(ends_with("OTHER").ignoring_ascii_case()))
794     }
795 
796     #[test]
ends_with_does_not_match_too_short_string_ignoring_ascii_case() -> Result<()>797     fn ends_with_does_not_match_too_short_string_ignoring_ascii_case() -> Result<()> {
798         verify_that!("Some", not(ends_with("OTHER").ignoring_ascii_case()))
799     }
800 
801     #[test]
ends_with_does_not_match_string_without_suffix() -> Result<()>802     fn ends_with_does_not_match_string_without_suffix() -> Result<()> {
803         verify_that!("Some value", not(ends_with("other value")))
804     }
805 
806     #[test]
ends_with_does_not_match_string_with_substring_not_at_end() -> Result<()>807     fn ends_with_does_not_match_string_with_substring_not_at_end() -> Result<()> {
808         verify_that!("Some value", not(ends_with("Some")))
809     }
810 
811     #[test]
describes_itself_for_matching_result() -> Result<()>812     fn describes_itself_for_matching_result() -> Result<()> {
813         let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string");
814         verify_that!(
815             Matcher::describe(&matcher, MatcherResult::Match),
816             displays_as(eq("is equal to \"A string\""))
817         )
818     }
819 
820     #[test]
describes_itself_for_non_matching_result() -> Result<()>821     fn describes_itself_for_non_matching_result() -> Result<()> {
822         let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string");
823         verify_that!(
824             Matcher::describe(&matcher, MatcherResult::NoMatch),
825             displays_as(eq("isn't equal to \"A string\""))
826         )
827     }
828 
829     #[test]
describes_itself_for_matching_result_ignoring_leading_whitespace() -> Result<()>830     fn describes_itself_for_matching_result_ignoring_leading_whitespace() -> Result<()> {
831         let matcher: StrMatcher<&str, _> =
832             StrMatcher::with_default_config("A string").ignoring_leading_whitespace();
833         verify_that!(
834             Matcher::describe(&matcher, MatcherResult::Match),
835             displays_as(eq("is equal to \"A string\" (ignoring leading whitespace)"))
836         )
837     }
838 
839     #[test]
describes_itself_for_non_matching_result_ignoring_leading_whitespace() -> Result<()>840     fn describes_itself_for_non_matching_result_ignoring_leading_whitespace() -> Result<()> {
841         let matcher: StrMatcher<&str, _> =
842             StrMatcher::with_default_config("A string").ignoring_leading_whitespace();
843         verify_that!(
844             Matcher::describe(&matcher, MatcherResult::NoMatch),
845             displays_as(eq("isn't equal to \"A string\" (ignoring leading whitespace)"))
846         )
847     }
848 
849     #[test]
describes_itself_for_matching_result_ignoring_trailing_whitespace() -> Result<()>850     fn describes_itself_for_matching_result_ignoring_trailing_whitespace() -> Result<()> {
851         let matcher: StrMatcher<&str, _> =
852             StrMatcher::with_default_config("A string").ignoring_trailing_whitespace();
853         verify_that!(
854             Matcher::describe(&matcher, MatcherResult::Match),
855             displays_as(eq("is equal to \"A string\" (ignoring trailing whitespace)"))
856         )
857     }
858 
859     #[test]
describes_itself_for_matching_result_ignoring_leading_and_trailing_whitespace() -> Result<()>860     fn describes_itself_for_matching_result_ignoring_leading_and_trailing_whitespace() -> Result<()>
861     {
862         let matcher: StrMatcher<&str, _> =
863             StrMatcher::with_default_config("A string").ignoring_outer_whitespace();
864         verify_that!(
865             Matcher::describe(&matcher, MatcherResult::Match),
866             displays_as(eq("is equal to \"A string\" (ignoring leading and trailing whitespace)"))
867         )
868     }
869 
870     #[test]
describes_itself_for_matching_result_ignoring_ascii_case() -> Result<()>871     fn describes_itself_for_matching_result_ignoring_ascii_case() -> Result<()> {
872         let matcher: StrMatcher<&str, _> =
873             StrMatcher::with_default_config("A string").ignoring_ascii_case();
874         verify_that!(
875             Matcher::describe(&matcher, MatcherResult::Match),
876             displays_as(eq("is equal to \"A string\" (ignoring ASCII case)"))
877         )
878     }
879 
880     #[test]
describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace() -> Result<()>881     fn describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace()
882     -> Result<()> {
883         let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string")
884             .ignoring_leading_whitespace()
885             .ignoring_ascii_case();
886         verify_that!(
887             Matcher::describe(&matcher, MatcherResult::Match),
888             displays_as(eq(
889                 "is equal to \"A string\" (ignoring leading whitespace, ignoring ASCII case)"
890             ))
891         )
892     }
893 
894     #[test]
describes_itself_for_matching_result_in_contains_mode() -> Result<()>895     fn describes_itself_for_matching_result_in_contains_mode() -> Result<()> {
896         let matcher: StrMatcher<&str, _> = contains_substring("A string");
897         verify_that!(
898             Matcher::describe(&matcher, MatcherResult::Match),
899             displays_as(eq("contains a substring \"A string\""))
900         )
901     }
902 
903     #[test]
describes_itself_for_non_matching_result_in_contains_mode() -> Result<()>904     fn describes_itself_for_non_matching_result_in_contains_mode() -> Result<()> {
905         let matcher: StrMatcher<&str, _> = contains_substring("A string");
906         verify_that!(
907             Matcher::describe(&matcher, MatcherResult::NoMatch),
908             displays_as(eq("does not contain a substring \"A string\""))
909         )
910     }
911 
912     #[test]
describes_itself_with_count_number() -> Result<()>913     fn describes_itself_with_count_number() -> Result<()> {
914         let matcher: StrMatcher<&str, _> = contains_substring("A string").times(gt(2));
915         verify_that!(
916             Matcher::describe(&matcher, MatcherResult::Match),
917             displays_as(eq("contains a substring \"A string\" (count is greater than 2)"))
918         )
919     }
920 
921     #[test]
describes_itself_for_matching_result_in_starts_with_mode() -> Result<()>922     fn describes_itself_for_matching_result_in_starts_with_mode() -> Result<()> {
923         let matcher: StrMatcher<&str, _> = starts_with("A string");
924         verify_that!(
925             Matcher::describe(&matcher, MatcherResult::Match),
926             displays_as(eq("starts with prefix \"A string\""))
927         )
928     }
929 
930     #[test]
describes_itself_for_non_matching_result_in_starts_with_mode() -> Result<()>931     fn describes_itself_for_non_matching_result_in_starts_with_mode() -> Result<()> {
932         let matcher: StrMatcher<&str, _> = starts_with("A string");
933         verify_that!(
934             Matcher::describe(&matcher, MatcherResult::NoMatch),
935             displays_as(eq("does not start with \"A string\""))
936         )
937     }
938 
939     #[test]
describes_itself_for_matching_result_in_ends_with_mode() -> Result<()>940     fn describes_itself_for_matching_result_in_ends_with_mode() -> Result<()> {
941         let matcher: StrMatcher<&str, _> = ends_with("A string");
942         verify_that!(
943             Matcher::describe(&matcher, MatcherResult::Match),
944             displays_as(eq("ends with suffix \"A string\""))
945         )
946     }
947 
948     #[test]
describes_itself_for_non_matching_result_in_ends_with_mode() -> Result<()>949     fn describes_itself_for_non_matching_result_in_ends_with_mode() -> Result<()> {
950         let matcher: StrMatcher<&str, _> = ends_with("A string");
951         verify_that!(
952             Matcher::describe(&matcher, MatcherResult::NoMatch),
953             displays_as(eq("does not end with \"A string\""))
954         )
955     }
956 
957     #[test]
match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()>958     fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()> {
959         let result = verify_that!(
960             indoc!(
961                 "
962                     First line
963                     Second line
964                     Third line
965                 "
966             ),
967             starts_with(indoc!(
968                 "
969                     First line
970                     Second lines
971                     Third line
972                 "
973             ))
974         );
975 
976         verify_that!(
977             result,
978             err(displays_as(contains_substring(
979                 "\
980    First line
981   -Second line
982   +Second lines
983    Third line"
984             )))
985         )
986     }
987 
988     #[test]
match_explanation_for_starts_with_ignores_trailing_lines_in_actual_string() -> Result<()>989     fn match_explanation_for_starts_with_ignores_trailing_lines_in_actual_string() -> Result<()> {
990         let result = verify_that!(
991             indoc!(
992                 "
993                     First line
994                     Second line
995                     Third line
996                     Fourth line
997                 "
998             ),
999             starts_with(indoc!(
1000                 "
1001                     First line
1002                     Second lines
1003                     Third line
1004                 "
1005             ))
1006         );
1007 
1008         verify_that!(
1009             result,
1010             err(displays_as(contains_substring(
1011                 "
1012    First line
1013   -Second line
1014   +Second lines
1015    Third line
1016    <---- remaining lines omitted ---->"
1017             )))
1018         )
1019     }
1020 
1021     #[test]
match_explanation_for_starts_with_includes_both_versions_of_differing_last_line() -> Result<()>1022     fn match_explanation_for_starts_with_includes_both_versions_of_differing_last_line()
1023     -> Result<()> {
1024         let result = verify_that!(
1025             indoc!(
1026                 "
1027                     First line
1028                     Second line
1029                     Third line
1030                 "
1031             ),
1032             starts_with(indoc!(
1033                 "
1034                     First line
1035                     Second lines
1036                 "
1037             ))
1038         );
1039 
1040         verify_that!(
1041             result,
1042             err(displays_as(contains_substring(
1043                 "\
1044    First line
1045   -Second line
1046   +Second lines
1047    <---- remaining lines omitted ---->"
1048             )))
1049         )
1050     }
1051 
1052     #[test]
match_explanation_for_ends_with_ignores_leading_lines_in_actual_string() -> Result<()>1053     fn match_explanation_for_ends_with_ignores_leading_lines_in_actual_string() -> Result<()> {
1054         let result = verify_that!(
1055             indoc!(
1056                 "
1057                     First line
1058                     Second line
1059                     Third line
1060                     Fourth line
1061                 "
1062             ),
1063             ends_with(indoc!(
1064                 "
1065                     Second line
1066                     Third lines
1067                     Fourth line
1068                 "
1069             ))
1070         );
1071 
1072         verify_that!(
1073             result,
1074             err(displays_as(contains_substring(
1075                 "
1076   Difference(-actual / +expected):
1077    <---- remaining lines omitted ---->
1078    Second line
1079   -Third line
1080   +Third lines
1081    Fourth line"
1082             )))
1083         )
1084     }
1085 
1086     #[test]
match_explanation_for_contains_substring_ignores_outer_lines_in_actual_string() -> Result<()>1087     fn match_explanation_for_contains_substring_ignores_outer_lines_in_actual_string() -> Result<()>
1088     {
1089         let result = verify_that!(
1090             indoc!(
1091                 "
1092                     First line
1093                     Second line
1094                     Third line
1095                     Fourth line
1096                     Fifth line
1097                 "
1098             ),
1099             contains_substring(indoc!(
1100                 "
1101                     Second line
1102                     Third lines
1103                     Fourth line
1104                 "
1105             ))
1106         );
1107 
1108         verify_that!(
1109             result,
1110             err(displays_as(contains_substring(
1111                 "
1112   Difference(-actual / +expected):
1113    <---- remaining lines omitted ---->
1114    Second line
1115   -Third line
1116   +Third lines
1117    Fourth line
1118    <---- remaining lines omitted ---->"
1119             )))
1120         )
1121     }
1122 
1123     #[test]
match_explanation_for_contains_substring_shows_diff_when_first_and_last_line_are_incomplete() -> Result<()>1124     fn match_explanation_for_contains_substring_shows_diff_when_first_and_last_line_are_incomplete()
1125     -> Result<()> {
1126         let result = verify_that!(
1127             indoc!(
1128                 "
1129                     First line
1130                     Second line
1131                     Third line
1132                     Fourth line
1133                     Fifth line
1134                 "
1135             ),
1136             contains_substring(indoc!(
1137                 "
1138                     line
1139                     Third line
1140                     Foorth line
1141                     Fifth"
1142             ))
1143         );
1144 
1145         verify_that!(
1146             result,
1147             err(displays_as(contains_substring(
1148                 "
1149   Difference(-actual / +expected):
1150    <---- remaining lines omitted ---->
1151   -Second line
1152   +line
1153    Third line
1154   -Fourth line
1155   +Foorth line
1156   -Fifth line
1157   +Fifth
1158    <---- remaining lines omitted ---->"
1159             )))
1160         )
1161     }
1162 
1163     #[test]
match_explanation_for_eq_does_not_ignore_trailing_lines_in_actual_string() -> Result<()>1164     fn match_explanation_for_eq_does_not_ignore_trailing_lines_in_actual_string() -> Result<()> {
1165         let result = verify_that!(
1166             indoc!(
1167                 "
1168                     First line
1169                     Second line
1170                     Third line
1171                     Fourth line
1172                 "
1173             ),
1174             eq(indoc!(
1175                 "
1176                     First line
1177                     Second lines
1178                     Third line
1179                 "
1180             ))
1181         );
1182 
1183         verify_that!(
1184             result,
1185             err(displays_as(contains_substring(
1186                 "\
1187    First line
1188   -Second line
1189   +Second lines
1190    Third line
1191   -Fourth line"
1192             )))
1193         )
1194     }
1195 
1196     #[test]
match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()>1197     fn match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()> {
1198         let result = verify_that!(
1199             "First line",
1200             starts_with(indoc!(
1201                 "
1202                     Second line
1203                     Third line
1204                 "
1205             ))
1206         );
1207 
1208         verify_that!(
1209             result,
1210             err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
1211         )
1212     }
1213 
1214     #[test]
match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()>1215     fn match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()> {
1216         let result = verify_that!(
1217             indoc!(
1218                 "
1219                     First line
1220                     Second line
1221                     Third line
1222                 "
1223             ),
1224             starts_with("Second line")
1225         );
1226 
1227         verify_that!(
1228             result,
1229             err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
1230         )
1231     }
1232 }
1233