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