Skip to main content

googletest/matchers/
str_matcher.rs

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