1323 lines
45 KiB
Rust
1323 lines
45 KiB
Rust
// Copyright 2023 Google LLC
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
use crate::{
|
||
description::Description,
|
||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||
matcher_support::{
|
||
edit_distance,
|
||
summarize_diff::{create_diff, create_diff_reversed},
|
||
},
|
||
matchers::eq_matcher::EqMatcher,
|
||
};
|
||
use std::borrow::Cow;
|
||
use std::fmt::Debug;
|
||
use std::ops::Deref;
|
||
|
||
/// Matches a string containing a given substring.
|
||
///
|
||
/// Both the actual value and the expected substring may be either a `String` or
|
||
/// a string reference.
|
||
///
|
||
/// ```
|
||
/// # use googletest::prelude::*;
|
||
/// # fn should_pass_1() -> Result<()> {
|
||
/// verify_that!("Some value", contains_substring("Some"))?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_fail() -> Result<()> {
|
||
/// verify_that!("Another value", contains_substring("Some"))?; // Fails
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_pass_2() -> Result<()> {
|
||
/// verify_that!("Some value".to_string(), contains_substring("value"))?; // Passes
|
||
/// verify_that!("Some value", contains_substring("value".to_string()))?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # should_pass_1().unwrap();
|
||
/// # should_fail().unwrap_err();
|
||
/// # should_pass_2().unwrap();
|
||
/// ```
|
||
///
|
||
/// See the [`StrMatcherConfigurator`] extension trait for more options on how
|
||
/// the string is matched.
|
||
///
|
||
/// > Note on memory use: In most cases, this matcher does not allocate memory
|
||
/// > when matching strings. However, it must allocate copies of both the actual
|
||
/// > and expected values when matching strings while
|
||
/// > [`ignoring_ascii_case`][StrMatcherConfigurator::ignoring_ascii_case] or
|
||
/// > [`ignoring_unicode_case`][StrMatcherConfigurator::ignoring_unicode_case]
|
||
/// > are set.
|
||
pub fn contains_substring<T>(expected: T) -> StrMatcher<T> {
|
||
StrMatcher {
|
||
configuration: Configuration { mode: MatchMode::Contains, ..Default::default() },
|
||
expected,
|
||
}
|
||
}
|
||
|
||
/// Matches a string which starts with the given prefix.
|
||
///
|
||
/// Both the actual value and the expected prefix may be either a `String` or
|
||
/// a string reference.
|
||
///
|
||
/// ```
|
||
/// # use googletest::prelude::*;
|
||
/// # fn should_pass_1() -> Result<()> {
|
||
/// verify_that!("Some value", starts_with("Some"))?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_fail_1() -> Result<()> {
|
||
/// verify_that!("Another value", starts_with("Some"))?; // Fails
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_fail_2() -> Result<()> {
|
||
/// verify_that!("Some value", starts_with("value"))?; // Fails
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_pass_2() -> Result<()> {
|
||
/// verify_that!("Some value".to_string(), starts_with("Some"))?; // Passes
|
||
/// verify_that!("Some value", starts_with("Some".to_string()))?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # should_pass_1().unwrap();
|
||
/// # should_fail_1().unwrap_err();
|
||
/// # should_fail_2().unwrap_err();
|
||
/// # should_pass_2().unwrap();
|
||
/// ```
|
||
///
|
||
/// See the [`StrMatcherConfigurator`] extension trait for more options on how
|
||
/// the string is matched.
|
||
pub fn starts_with<T>(expected: T) -> StrMatcher<T> {
|
||
StrMatcher {
|
||
configuration: Configuration { mode: MatchMode::StartsWith, ..Default::default() },
|
||
expected,
|
||
}
|
||
}
|
||
|
||
/// Matches a string which ends with the given suffix.
|
||
///
|
||
/// Both the actual value and the expected suffix may be either a `String` or
|
||
/// a string reference.
|
||
///
|
||
/// ```
|
||
/// # use googletest::prelude::*;
|
||
/// # fn should_pass_1() -> Result<()> {
|
||
/// verify_that!("Some value", ends_with("value"))?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_fail_1() -> Result<()> {
|
||
/// verify_that!("Some value", ends_with("other value"))?; // Fails
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_fail_2() -> Result<()> {
|
||
/// verify_that!("Some value", ends_with("Some"))?; // Fails
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_pass_2() -> Result<()> {
|
||
/// verify_that!("Some value".to_string(), ends_with("value"))?; // Passes
|
||
/// verify_that!("Some value", ends_with("value".to_string()))?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # should_pass_1().unwrap();
|
||
/// # should_fail_1().unwrap_err();
|
||
/// # should_fail_2().unwrap_err();
|
||
/// # should_pass_2().unwrap();
|
||
/// ```
|
||
///
|
||
/// See the [`StrMatcherConfigurator`] extension trait for more options on how
|
||
/// the string is matched.
|
||
pub fn ends_with<T>(expected: T) -> StrMatcher<T> {
|
||
StrMatcher {
|
||
configuration: Configuration { mode: MatchMode::EndsWith, ..Default::default() },
|
||
expected,
|
||
}
|
||
}
|
||
|
||
/// Extension trait to configure [`StrMatcher`].
|
||
///
|
||
/// Matchers which match against string values and, through configuration,
|
||
/// specialise to [`StrMatcher`] implement this trait. That includes
|
||
/// [`EqMatcher`] and [`StrMatcher`].
|
||
pub trait StrMatcherConfigurator<ExpectedT> {
|
||
/// Configures the matcher to ignore any leading whitespace in either the
|
||
/// actual or the expected value.
|
||
///
|
||
/// Whitespace is defined as in [`str::trim_start`].
|
||
///
|
||
/// ```
|
||
/// # use googletest::prelude::*;
|
||
/// # fn should_pass() -> Result<()> {
|
||
/// verify_that!("A string", eq(" A string").ignoring_leading_whitespace())?; // Passes
|
||
/// verify_that!(" A string", eq("A string").ignoring_leading_whitespace())?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # should_pass().unwrap();
|
||
/// ```
|
||
///
|
||
/// When all other configuration options are left as the defaults, this is
|
||
/// equivalent to invoking [`str::trim_start`] on both the expected and
|
||
/// actual value.
|
||
fn ignoring_leading_whitespace(self) -> StrMatcher<ExpectedT>;
|
||
|
||
/// Configures the matcher to ignore any trailing whitespace in either the
|
||
/// actual or the expected value.
|
||
///
|
||
/// Whitespace is defined as in [`str::trim_end`].
|
||
///
|
||
/// ```
|
||
/// # use googletest::prelude::*;
|
||
/// # fn should_pass() -> Result<()> {
|
||
/// verify_that!("A string", eq("A string ").ignoring_trailing_whitespace())?; // Passes
|
||
/// verify_that!("A string ", eq("A string").ignoring_trailing_whitespace())?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # should_pass().unwrap();
|
||
/// ```
|
||
///
|
||
/// When all other configuration options are left as the defaults, this is
|
||
/// equivalent to invoking [`str::trim_end`] on both the expected and
|
||
/// actual value.
|
||
fn ignoring_trailing_whitespace(self) -> StrMatcher<ExpectedT>;
|
||
|
||
/// Configures the matcher to ignore both leading and trailing whitespace in
|
||
/// either the actual or the expected value.
|
||
///
|
||
/// Whitespace is defined as in [`str::trim`].
|
||
///
|
||
/// ```
|
||
/// # use googletest::prelude::*;
|
||
/// # fn should_pass() -> Result<()> {
|
||
/// verify_that!("A string", eq(" A string ").ignoring_outer_whitespace())?; // Passes
|
||
/// verify_that!(" A string ", eq("A string").ignoring_outer_whitespace())?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # should_pass().unwrap();
|
||
/// ```
|
||
///
|
||
/// This is equivalent to invoking both
|
||
/// [`ignoring_leading_whitespace`][StrMatcherConfigurator::ignoring_leading_whitespace] and
|
||
/// [`ignoring_trailing_whitespace`][StrMatcherConfigurator::ignoring_trailing_whitespace].
|
||
///
|
||
/// When all other configuration options are left as the defaults, this is
|
||
/// equivalent to invoking [`str::trim`] on both the expected and actual
|
||
/// value.
|
||
fn ignoring_outer_whitespace(self) -> StrMatcher<ExpectedT>;
|
||
|
||
/// Configures the matcher to ignore ASCII case when comparing values.
|
||
///
|
||
/// This uses the same rules for case as [`str::eq_ignore_ascii_case`].
|
||
///
|
||
/// ```
|
||
/// # use googletest::prelude::*;
|
||
/// # fn should_pass() -> Result<()> {
|
||
/// verify_that!("Some value", eq("SOME VALUE").ignoring_ascii_case())?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_fail() -> Result<()> {
|
||
/// verify_that!("Another value", eq("Some value").ignoring_ascii_case())?; // Fails
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # should_pass().unwrap();
|
||
/// # should_fail().unwrap_err();
|
||
/// ```
|
||
///
|
||
/// This is **not guaranteed** to match strings with differing upper/lower
|
||
/// case characters outside of the codepoints 0-127 covered by ASCII.
|
||
fn ignoring_ascii_case(self) -> StrMatcher<ExpectedT>;
|
||
|
||
/// Configures the matcher to ignore Unicode case when comparing values.
|
||
///
|
||
/// This uses the same rules for case as [`str::to_lowercase`].
|
||
///
|
||
/// ```
|
||
/// # use googletest::prelude::*;
|
||
/// # fn should_pass() -> Result<()> {
|
||
/// verify_that!("ὈΔΥΣΣΕΎΣ", eq("ὀδυσσεύς").ignoring_unicode_case())?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_fail() -> Result<()> {
|
||
/// verify_that!("secret", eq("비밀").ignoring_unicode_case())?; // Fails
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # should_pass().unwrap();
|
||
/// # should_fail().unwrap_err();
|
||
/// ```
|
||
fn ignoring_unicode_case(self) -> StrMatcher<ExpectedT>;
|
||
|
||
/// Configures the matcher to match only strings which otherwise satisfy the
|
||
/// conditions a number times matched by the matcher `times`.
|
||
///
|
||
/// ```
|
||
/// # use googletest::prelude::*;
|
||
/// # fn should_pass() -> Result<()> {
|
||
/// verify_that!("Some value\nSome value", contains_substring("value").times(eq(2)))?; // Passes
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # fn should_fail() -> Result<()> {
|
||
/// verify_that!("Some value", contains_substring("value").times(eq(2)))?; // Fails
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # should_pass().unwrap();
|
||
/// # should_fail().unwrap_err();
|
||
/// ```
|
||
///
|
||
/// The matched substrings must be disjoint from one another to be counted.
|
||
/// For example:
|
||
///
|
||
/// ```
|
||
/// # use googletest::prelude::*;
|
||
/// # fn should_fail() -> Result<()> {
|
||
/// // Fails: substrings distinct but not disjoint!
|
||
/// verify_that!("ababab", contains_substring("abab").times(eq(2)))?;
|
||
/// # Ok(())
|
||
/// # }
|
||
/// # should_fail().unwrap_err();
|
||
/// ```
|
||
///
|
||
/// This is only meaningful when the matcher was constructed with
|
||
/// [`contains_substring`]. This method will panic when it is used with any
|
||
/// other matcher construction.
|
||
fn times(self, times: impl Matcher<usize> + 'static) -> StrMatcher<ExpectedT>;
|
||
}
|
||
|
||
/// A matcher which matches equality or containment of a string-like value in a
|
||
/// configurable way.
|
||
///
|
||
/// The following matcher methods instantiate this:
|
||
///
|
||
/// * [`eq`][crate::matchers::eq_matcher::eq],
|
||
/// * [`contains_substring`],
|
||
/// * [`starts_with`],
|
||
/// * [`ends_with`].
|
||
#[derive(MatcherBase)]
|
||
pub struct StrMatcher<ExpectedT> {
|
||
expected: ExpectedT,
|
||
configuration: Configuration,
|
||
}
|
||
|
||
impl<ExpectedT, ActualT> Matcher<ActualT> for StrMatcher<ExpectedT>
|
||
where
|
||
ExpectedT: Deref<Target = str> + Debug,
|
||
ActualT: AsRef<str> + Debug + Copy,
|
||
{
|
||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||
self.configuration.do_strings_match(self.expected.deref(), actual.as_ref()).into()
|
||
}
|
||
|
||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||
self.configuration.describe(matcher_result, self.expected.deref())
|
||
}
|
||
|
||
fn explain_match(&self, actual: ActualT) -> Description {
|
||
self.configuration.explain_match(self.expected.deref(), actual.as_ref())
|
||
}
|
||
}
|
||
|
||
impl<ExpectedT, MatcherT: Into<StrMatcher<ExpectedT>>> StrMatcherConfigurator<ExpectedT>
|
||
for MatcherT
|
||
{
|
||
fn ignoring_leading_whitespace(self) -> StrMatcher<ExpectedT> {
|
||
let existing = self.into();
|
||
StrMatcher {
|
||
configuration: existing.configuration.ignoring_leading_whitespace(),
|
||
..existing
|
||
}
|
||
}
|
||
|
||
fn ignoring_trailing_whitespace(self) -> StrMatcher<ExpectedT> {
|
||
let existing = self.into();
|
||
StrMatcher {
|
||
configuration: existing.configuration.ignoring_trailing_whitespace(),
|
||
..existing
|
||
}
|
||
}
|
||
|
||
fn ignoring_outer_whitespace(self) -> StrMatcher<ExpectedT> {
|
||
let existing = self.into();
|
||
StrMatcher { configuration: existing.configuration.ignoring_outer_whitespace(), ..existing }
|
||
}
|
||
|
||
fn ignoring_ascii_case(self) -> StrMatcher<ExpectedT> {
|
||
let existing = self.into();
|
||
StrMatcher { configuration: existing.configuration.ignoring_ascii_case(), ..existing }
|
||
}
|
||
|
||
fn ignoring_unicode_case(self) -> StrMatcher<ExpectedT> {
|
||
let existing = self.into();
|
||
StrMatcher { configuration: existing.configuration.ignoring_unicode_case(), ..existing }
|
||
}
|
||
|
||
fn times(self, times: impl Matcher<usize> + 'static) -> StrMatcher<ExpectedT> {
|
||
let existing = self.into();
|
||
if !matches!(existing.configuration.mode, MatchMode::Contains) {
|
||
panic!("The times() configurator is only meaningful with contains_substring().");
|
||
}
|
||
StrMatcher { configuration: existing.configuration.times(times), ..existing }
|
||
}
|
||
}
|
||
|
||
impl<T: Deref<Target = str>> From<EqMatcher<T>> for StrMatcher<T> {
|
||
fn from(value: EqMatcher<T>) -> Self {
|
||
Self::with_default_config(value.expected)
|
||
}
|
||
}
|
||
|
||
impl<T> StrMatcher<T> {
|
||
/// Returns a [`StrMatcher`] with a default configuration to match against
|
||
/// the given expected value.
|
||
///
|
||
/// This default configuration is sensitive to whitespace and case.
|
||
fn with_default_config(expected: T) -> Self {
|
||
Self { expected, configuration: Default::default() }
|
||
}
|
||
}
|
||
|
||
// Holds all the information on how the expected and actual strings are to be
|
||
// compared. Its associated functions perform the actual matching operations
|
||
// on string references. The struct and comparison methods therefore need not be
|
||
// parameterised, saving compilation time and binary size on monomorphisation.
|
||
//
|
||
// The default value represents exact equality of the strings.
|
||
struct Configuration {
|
||
mode: MatchMode,
|
||
ignore_leading_whitespace: bool,
|
||
ignore_trailing_whitespace: bool,
|
||
case_policy: CasePolicy,
|
||
times: Option<Box<dyn Matcher<usize>>>,
|
||
}
|
||
|
||
#[derive(Clone)]
|
||
enum MatchMode {
|
||
Equals,
|
||
Contains,
|
||
StartsWith,
|
||
EndsWith,
|
||
}
|
||
|
||
impl MatchMode {
|
||
fn to_diff_mode(&self) -> edit_distance::Mode {
|
||
match self {
|
||
MatchMode::StartsWith | MatchMode::EndsWith => edit_distance::Mode::Prefix,
|
||
MatchMode::Contains => edit_distance::Mode::Contains,
|
||
MatchMode::Equals => edit_distance::Mode::Exact,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone)]
|
||
enum CasePolicy {
|
||
Respect,
|
||
IgnoreAscii,
|
||
IgnoreUnicode,
|
||
}
|
||
|
||
impl Configuration {
|
||
// The entry point for all string matching. StrMatcher::matches redirects
|
||
// immediately to this function.
|
||
fn do_strings_match(&self, expected: &str, actual: &str) -> bool {
|
||
let (expected, actual) =
|
||
match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) {
|
||
(true, true) => (expected.trim(), actual.trim()),
|
||
(true, false) => (expected.trim_start(), actual.trim_start()),
|
||
(false, true) => (expected.trim_end(), actual.trim_end()),
|
||
(false, false) => (expected, actual),
|
||
};
|
||
match self.mode {
|
||
MatchMode::Equals => match self.case_policy {
|
||
CasePolicy::Respect => expected == actual,
|
||
CasePolicy::IgnoreAscii => expected.eq_ignore_ascii_case(actual),
|
||
CasePolicy::IgnoreUnicode => expected.to_lowercase() == actual.to_lowercase(),
|
||
},
|
||
MatchMode::Contains => match self.case_policy {
|
||
CasePolicy::Respect => self.does_containment_match(actual, expected),
|
||
CasePolicy::IgnoreAscii => self.does_containment_match(
|
||
actual.to_ascii_lowercase().as_str(),
|
||
expected.to_ascii_lowercase().as_str(),
|
||
),
|
||
CasePolicy::IgnoreUnicode => self.does_containment_match(
|
||
actual.to_lowercase().as_str(),
|
||
expected.to_lowercase().as_str(),
|
||
),
|
||
},
|
||
MatchMode::StartsWith => match self.case_policy {
|
||
CasePolicy::Respect => actual.starts_with(expected),
|
||
CasePolicy::IgnoreAscii => {
|
||
actual.len() >= expected.len()
|
||
&& actual[..expected.len()].eq_ignore_ascii_case(expected)
|
||
}
|
||
CasePolicy::IgnoreUnicode => {
|
||
actual.to_lowercase().starts_with(&expected.to_lowercase())
|
||
}
|
||
},
|
||
MatchMode::EndsWith => match self.case_policy {
|
||
CasePolicy::Respect => actual.ends_with(expected),
|
||
CasePolicy::IgnoreAscii => {
|
||
actual.len() >= expected.len()
|
||
&& actual[actual.len() - expected.len()..].eq_ignore_ascii_case(expected)
|
||
}
|
||
CasePolicy::IgnoreUnicode => {
|
||
actual.to_lowercase().ends_with(&expected.to_lowercase())
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
// Returns whether actual contains expected a number of times matched by the
|
||
// matcher self.times. Does not take other configuration into account.
|
||
fn does_containment_match(&self, actual: &str, expected: &str) -> bool {
|
||
if let Some(times) = self.times.as_ref() {
|
||
// Split returns an iterator over the "boundaries" left and right of the
|
||
// substring to be matched, of which there is one more than the number of
|
||
// substrings.
|
||
matches!(times.matches(actual.split(expected).count() - 1), MatcherResult::Match)
|
||
} else {
|
||
actual.contains(expected)
|
||
}
|
||
}
|
||
|
||
// StrMatcher::<str>::describe redirects immediately to this function.
|
||
fn describe(&self, matcher_result: MatcherResult, expected: &str) -> Description {
|
||
let mut addenda: Vec<Cow<'static, str>> = Vec::with_capacity(3);
|
||
match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) {
|
||
(true, true) => addenda.push("ignoring leading and trailing whitespace".into()),
|
||
(true, false) => addenda.push("ignoring leading whitespace".into()),
|
||
(false, true) => addenda.push("ignoring trailing whitespace".into()),
|
||
(false, false) => {}
|
||
}
|
||
match self.case_policy {
|
||
CasePolicy::Respect => {}
|
||
CasePolicy::IgnoreAscii => addenda.push("ignoring ASCII case".into()),
|
||
CasePolicy::IgnoreUnicode => addenda.push("ignoring Unicode case".into()),
|
||
}
|
||
if let Some(times) = self.times.as_ref() {
|
||
addenda.push(format!("count {}", times.describe(matcher_result)).into());
|
||
}
|
||
let extra =
|
||
if !addenda.is_empty() { format!(" ({})", addenda.join(", ")) } else { "".into() };
|
||
let match_mode_description = match self.mode {
|
||
MatchMode::Equals => match matcher_result {
|
||
MatcherResult::Match => "is equal to",
|
||
MatcherResult::NoMatch => "isn't equal to",
|
||
},
|
||
MatchMode::Contains => match matcher_result {
|
||
MatcherResult::Match => "contains a substring",
|
||
MatcherResult::NoMatch => "does not contain a substring",
|
||
},
|
||
MatchMode::StartsWith => match matcher_result {
|
||
MatcherResult::Match => "starts with prefix",
|
||
MatcherResult::NoMatch => "does not start with",
|
||
},
|
||
MatchMode::EndsWith => match matcher_result {
|
||
MatcherResult::Match => "ends with suffix",
|
||
MatcherResult::NoMatch => "does not end with",
|
||
},
|
||
};
|
||
format!("{match_mode_description} {expected:?}{extra}").into()
|
||
}
|
||
|
||
fn explain_match(&self, expected: &str, actual: &str) -> Description {
|
||
let default_explanation = format!(
|
||
"which {}",
|
||
self.describe(self.do_strings_match(expected, actual).into(), expected)
|
||
)
|
||
.into();
|
||
if !expected.contains('\n') || !actual.contains('\n') {
|
||
return default_explanation;
|
||
}
|
||
|
||
if self.ignore_leading_whitespace {
|
||
// TODO - b/283448414 : Support StrMatcher with ignore_leading_whitespace.
|
||
return default_explanation;
|
||
}
|
||
|
||
if self.ignore_trailing_whitespace {
|
||
// TODO - b/283448414 : Support StrMatcher with ignore_trailing_whitespace.
|
||
return default_explanation;
|
||
}
|
||
|
||
if self.times.is_some() {
|
||
// TODO - b/283448414 : Support StrMatcher with times.
|
||
return default_explanation;
|
||
}
|
||
if matches!(self.case_policy, CasePolicy::IgnoreAscii) {
|
||
// TODO - b/283448414 : Support StrMatcher with ignore ascii case policy.
|
||
return default_explanation;
|
||
}
|
||
if matches!(self.case_policy, CasePolicy::IgnoreUnicode) {
|
||
// TODO - b/283448414 : Support StrMatcher with ignore unicode case policy.
|
||
return default_explanation;
|
||
}
|
||
if self.do_strings_match(expected, actual) {
|
||
// TODO - b/283448414 : Consider supporting debug difference if the
|
||
// strings match. This can be useful when a small contains is found
|
||
// in a long string.
|
||
return default_explanation;
|
||
}
|
||
|
||
let diff = match self.mode {
|
||
MatchMode::Equals | MatchMode::StartsWith | MatchMode::Contains => {
|
||
// TODO(b/287632452): Also consider improving the output in MatchMode::Contains
|
||
// when the substring begins or ends in the middle of a line of the actual
|
||
// value.
|
||
create_diff(actual, expected, self.mode.to_diff_mode())
|
||
}
|
||
MatchMode::EndsWith => create_diff_reversed(actual, expected, self.mode.to_diff_mode()),
|
||
};
|
||
|
||
if diff.is_empty() {
|
||
format!("{default_explanation}").into()
|
||
} else {
|
||
format!("{default_explanation}\n\n{diff}").into()
|
||
}
|
||
}
|
||
|
||
fn ignoring_leading_whitespace(self) -> Self {
|
||
Self { ignore_leading_whitespace: true, ..self }
|
||
}
|
||
|
||
fn ignoring_trailing_whitespace(self) -> Self {
|
||
Self { ignore_trailing_whitespace: true, ..self }
|
||
}
|
||
|
||
fn ignoring_outer_whitespace(self) -> Self {
|
||
Self { ignore_leading_whitespace: true, ignore_trailing_whitespace: true, ..self }
|
||
}
|
||
|
||
fn ignoring_ascii_case(self) -> Self {
|
||
Self { case_policy: CasePolicy::IgnoreAscii, ..self }
|
||
}
|
||
|
||
fn ignoring_unicode_case(self) -> Self {
|
||
Self { case_policy: CasePolicy::IgnoreUnicode, ..self }
|
||
}
|
||
|
||
fn times(self, times: impl Matcher<usize> + 'static) -> Self {
|
||
Self { times: Some(Box::new(times)), ..self }
|
||
}
|
||
}
|
||
|
||
impl Default for Configuration {
|
||
fn default() -> Self {
|
||
Self {
|
||
mode: MatchMode::Equals,
|
||
ignore_leading_whitespace: false,
|
||
ignore_trailing_whitespace: false,
|
||
case_policy: CasePolicy::Respect,
|
||
times: None,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use crate as googletest;
|
||
use crate::matcher::MatcherResult;
|
||
use crate::prelude::*;
|
||
use indoc::indoc;
|
||
|
||
#[test]
|
||
fn matches_string_reference_with_equal_string_reference() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string");
|
||
verify_that!("A string", matcher)
|
||
}
|
||
|
||
#[test]
|
||
fn does_not_match_string_reference_with_non_equal_string_reference() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("Another string");
|
||
verify_that!("A string", not(matcher))
|
||
}
|
||
|
||
#[test]
|
||
fn matches_owned_string_with_string_reference() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string");
|
||
let value = "A string".to_string();
|
||
verify_that!(value, matcher)
|
||
}
|
||
|
||
#[test]
|
||
fn matches_owned_string_reference_with_string_reference() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string");
|
||
let value = "A string".to_string();
|
||
verify_that!(&value, matcher)
|
||
}
|
||
|
||
#[test]
|
||
fn ignores_leading_whitespace_in_expected_when_requested() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config(" \n\tA string");
|
||
verify_that!("A string", matcher.ignoring_leading_whitespace())
|
||
}
|
||
|
||
#[test]
|
||
fn ignores_leading_whitespace_in_actual_when_requested() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string");
|
||
verify_that!(" \n\tA string", matcher.ignoring_leading_whitespace())
|
||
}
|
||
|
||
#[test]
|
||
fn does_not_match_unequal_remaining_string_when_ignoring_leading_whitespace(
|
||
) -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config(" \n\tAnother string");
|
||
verify_that!("A string", not(matcher.ignoring_leading_whitespace()))
|
||
}
|
||
|
||
#[test]
|
||
fn remains_sensitive_to_trailing_whitespace_when_ignoring_leading_whitespace(
|
||
) -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string \n\t");
|
||
verify_that!("A string", not(matcher.ignoring_leading_whitespace()))
|
||
}
|
||
|
||
#[test]
|
||
fn ignores_trailing_whitespace_in_expected_when_requested() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string \n\t");
|
||
verify_that!("A string", matcher.ignoring_trailing_whitespace())
|
||
}
|
||
|
||
#[test]
|
||
fn ignores_trailing_whitespace_in_actual_when_requested() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string");
|
||
verify_that!("A string \n\t", matcher.ignoring_trailing_whitespace())
|
||
}
|
||
|
||
#[test]
|
||
fn does_not_match_unequal_remaining_string_when_ignoring_trailing_whitespace(
|
||
) -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("Another string \n\t");
|
||
verify_that!("A string", not(matcher.ignoring_trailing_whitespace()))
|
||
}
|
||
|
||
#[test]
|
||
fn remains_sensitive_to_leading_whitespace_when_ignoring_trailing_whitespace(
|
||
) -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config(" \n\tA string");
|
||
verify_that!("A string", not(matcher.ignoring_trailing_whitespace()))
|
||
}
|
||
|
||
#[test]
|
||
fn ignores_leading_and_trailing_whitespace_in_expected_when_requested() -> googletest::Result<()>
|
||
{
|
||
let matcher = StrMatcher::with_default_config(" \n\tA string \n\t");
|
||
verify_that!("A string", matcher.ignoring_outer_whitespace())
|
||
}
|
||
|
||
#[test]
|
||
fn ignores_leading_and_trailing_whitespace_in_actual_when_requested() -> googletest::Result<()>
|
||
{
|
||
let matcher = StrMatcher::with_default_config("A string");
|
||
verify_that!(" \n\tA string \n\t", matcher.ignoring_outer_whitespace())
|
||
}
|
||
|
||
#[test]
|
||
fn respects_ascii_case_by_default() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string");
|
||
verify_that!("A STRING", not(matcher))
|
||
}
|
||
|
||
#[test]
|
||
fn ignores_ascii_case_when_requested() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string");
|
||
verify_that!("A STRING", matcher.ignoring_ascii_case())
|
||
}
|
||
|
||
#[test]
|
||
fn ignores_unicode_case_when_requested() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("ὈΔΥΣΣΕΎΣ");
|
||
verify_that!("ὀδυσσεύς", matcher.ignoring_unicode_case())
|
||
}
|
||
|
||
#[test]
|
||
fn allows_ignoring_leading_whitespace_from_eq() -> googletest::Result<()> {
|
||
verify_that!("A string", eq(" \n\tA string").ignoring_leading_whitespace())
|
||
}
|
||
|
||
#[test]
|
||
fn allows_ignoring_trailing_whitespace_from_eq() -> googletest::Result<()> {
|
||
verify_that!("A string", eq("A string \n\t").ignoring_trailing_whitespace())
|
||
}
|
||
|
||
#[test]
|
||
fn allows_ignoring_outer_whitespace_from_eq() -> googletest::Result<()> {
|
||
verify_that!("A string", eq(" \n\tA string \n\t").ignoring_outer_whitespace())
|
||
}
|
||
|
||
#[test]
|
||
fn allows_ignoring_ascii_case_from_eq() -> googletest::Result<()> {
|
||
verify_that!("A string", eq("A STRING").ignoring_ascii_case())
|
||
}
|
||
|
||
#[test]
|
||
fn allows_ignoring_unicode_case_from_eq() -> googletest::Result<()> {
|
||
verify_that!("ὈΔΥΣΣΕΎΣ", eq("ὀδυσσεύς").ignoring_unicode_case())
|
||
}
|
||
|
||
#[test]
|
||
fn unicode_case_sensitive_from_eq() -> googletest::Result<()> {
|
||
verify_that!("ὈΔΥΣΣΕΎΣ", not(eq("ὀδυσσεύς")))
|
||
}
|
||
|
||
#[test]
|
||
fn matches_string_containing_expected_value_in_contains_mode() -> googletest::Result<()> {
|
||
verify_that!("Some string", contains_substring("str"))
|
||
}
|
||
|
||
#[test]
|
||
fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case(
|
||
) -> googletest::Result<()> {
|
||
verify_that!("Some string", contains_substring("STR").ignoring_ascii_case())
|
||
}
|
||
|
||
#[test]
|
||
fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_unicode_case(
|
||
) -> googletest::Result<()> {
|
||
verify_that!("Some σpsilon", contains_substring("Σps").ignoring_unicode_case())
|
||
}
|
||
|
||
#[test]
|
||
fn contains_substring_matches_correct_number_of_substrings() -> googletest::Result<()> {
|
||
verify_that!("Some string", contains_substring("str").times(eq(1)))
|
||
}
|
||
|
||
#[test]
|
||
fn contains_substring_does_not_match_incorrect_number_of_substrings() -> googletest::Result<()>
|
||
{
|
||
verify_that!("Some string\nSome string", not(contains_substring("string").times(eq(1))))
|
||
}
|
||
|
||
#[test]
|
||
fn contains_substring_does_not_match_when_substrings_overlap() -> googletest::Result<()> {
|
||
verify_that!("ababab", not(contains_substring("abab").times(eq(2))))
|
||
}
|
||
|
||
#[test]
|
||
fn starts_with_matches_string_reference_with_prefix() -> googletest::Result<()> {
|
||
verify_that!("Some value", starts_with("Some"))
|
||
}
|
||
|
||
#[test]
|
||
fn starts_with_matches_string_reference_with_prefix_ignoring_ascii_case(
|
||
) -> googletest::Result<()> {
|
||
verify_that!("Some value", starts_with("SOME").ignoring_ascii_case())
|
||
}
|
||
|
||
#[test]
|
||
fn starts_with_does_not_match_wrong_prefix_ignoring_ascii_case() -> googletest::Result<()> {
|
||
verify_that!("Some value", not(starts_with("OTHER").ignoring_ascii_case()))
|
||
}
|
||
|
||
#[test]
|
||
fn starts_with_does_not_match_short_string_ignoring_ascii_case() -> googletest::Result<()> {
|
||
verify_that!("Some", not(starts_with("OTHER").ignoring_ascii_case()))
|
||
}
|
||
|
||
#[test]
|
||
fn starts_with_matches_string_reference_with_prefix_ignoring_unicode_case(
|
||
) -> googletest::Result<()> {
|
||
verify_that!("비밀 santa", starts_with("비밀").ignoring_unicode_case())
|
||
}
|
||
|
||
#[test]
|
||
fn starts_with_does_not_match_wrong_prefix_ignoring_unicode_case() -> googletest::Result<()> {
|
||
verify_that!("secret santa", not(starts_with("비밀").ignoring_unicode_case()))
|
||
}
|
||
|
||
#[test]
|
||
fn starts_with_does_not_match_short_string_ignoring_unicode_case() -> googletest::Result<()> {
|
||
verify_that!("비밀", not(starts_with("秘密").ignoring_unicode_case()))
|
||
}
|
||
|
||
#[test]
|
||
fn starts_with_does_not_match_string_without_prefix() -> googletest::Result<()> {
|
||
verify_that!("Some value", not(starts_with("Another")))
|
||
}
|
||
|
||
#[test]
|
||
fn starts_with_does_not_match_string_with_substring_not_at_beginning() -> googletest::Result<()>
|
||
{
|
||
verify_that!("Some value", not(starts_with("value")))
|
||
}
|
||
|
||
#[test]
|
||
fn ends_with_matches_string_reference_with_suffix() -> googletest::Result<()> {
|
||
verify_that!("Some value", ends_with("value"))
|
||
}
|
||
|
||
#[test]
|
||
fn ends_with_matches_string_reference_with_suffix_ignoring_ascii_case() -> googletest::Result<()>
|
||
{
|
||
verify_that!("Some value", ends_with("VALUE").ignoring_ascii_case())
|
||
}
|
||
|
||
#[test]
|
||
fn ends_with_does_not_match_wrong_suffix_ignoring_ascii_case() -> googletest::Result<()> {
|
||
verify_that!("Some value", not(ends_with("OTHER").ignoring_ascii_case()))
|
||
}
|
||
|
||
#[test]
|
||
fn ends_with_does_not_match_too_short_string_ignoring_ascii_case() -> googletest::Result<()> {
|
||
verify_that!("Some", not(ends_with("OTHER").ignoring_ascii_case()))
|
||
}
|
||
|
||
#[test]
|
||
fn ends_with_does_not_match_string_without_suffix() -> googletest::Result<()> {
|
||
verify_that!("Some value", not(ends_with("other value")))
|
||
}
|
||
|
||
#[test]
|
||
fn ends_with_does_not_match_string_with_substring_not_at_end() -> googletest::Result<()> {
|
||
verify_that!("Some value", not(ends_with("Some")))
|
||
}
|
||
|
||
#[test]
|
||
fn ends_with_matches_string_reference_with_suffix_ignoring_unicode_case(
|
||
) -> googletest::Result<()> {
|
||
verify_that!("santa 비밀", ends_with("비밀").ignoring_unicode_case())
|
||
}
|
||
|
||
#[test]
|
||
fn ends_with_does_not_match_wrong_suffix_ignoring_unicode_case() -> googletest::Result<()> {
|
||
verify_that!("secret santa", not(ends_with("비밀").ignoring_unicode_case()))
|
||
}
|
||
|
||
#[test]
|
||
fn ends_with_does_not_match_short_string_ignoring_unicode_case() -> googletest::Result<()> {
|
||
verify_that!("비밀", not(ends_with("秘密").ignoring_unicode_case()))
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_matching_result() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string");
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||
displays_as(eq("is equal to \"A string\""))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_non_matching_result() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string");
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::NoMatch),
|
||
displays_as(eq("isn't equal to \"A string\""))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_matching_result_ignoring_leading_whitespace() -> googletest::Result<()>
|
||
{
|
||
let matcher = StrMatcher::with_default_config("A string").ignoring_leading_whitespace();
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||
displays_as(eq("is equal to \"A string\" (ignoring leading whitespace)"))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_non_matching_result_ignoring_leading_whitespace(
|
||
) -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string").ignoring_leading_whitespace();
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::NoMatch),
|
||
displays_as(eq("isn't equal to \"A string\" (ignoring leading whitespace)"))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_matching_result_ignoring_trailing_whitespace() -> googletest::Result<()>
|
||
{
|
||
let matcher = StrMatcher::with_default_config("A string").ignoring_trailing_whitespace();
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||
displays_as(eq("is equal to \"A string\" (ignoring trailing whitespace)"))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_matching_result_ignoring_leading_and_trailing_whitespace(
|
||
) -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string").ignoring_outer_whitespace();
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||
displays_as(eq("is equal to \"A string\" (ignoring leading and trailing whitespace)"))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_matching_result_ignoring_ascii_case() -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string").ignoring_ascii_case();
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||
displays_as(eq("is equal to \"A string\" (ignoring ASCII case)"))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace(
|
||
) -> googletest::Result<()> {
|
||
let matcher = StrMatcher::with_default_config("A string")
|
||
.ignoring_leading_whitespace()
|
||
.ignoring_ascii_case();
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||
displays_as(eq(
|
||
"is equal to \"A string\" (ignoring leading whitespace, ignoring ASCII case)"
|
||
))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_matching_result_in_contains_mode() -> googletest::Result<()> {
|
||
let matcher = contains_substring("A string");
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||
displays_as(eq("contains a substring \"A string\""))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_non_matching_result_in_contains_mode() -> googletest::Result<()> {
|
||
let matcher = contains_substring("A string");
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::NoMatch),
|
||
displays_as(eq("does not contain a substring \"A string\""))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_with_count_number() -> googletest::Result<()> {
|
||
let matcher = contains_substring("A string").times(gt(2));
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||
displays_as(eq("contains a substring \"A string\" (count is greater than 2)"))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_matching_result_in_starts_with_mode() -> googletest::Result<()> {
|
||
let matcher = starts_with("A string");
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||
displays_as(eq("starts with prefix \"A string\""))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_non_matching_result_in_starts_with_mode() -> googletest::Result<()> {
|
||
let matcher = starts_with("A string");
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::NoMatch),
|
||
displays_as(eq("does not start with \"A string\""))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_matching_result_in_ends_with_mode() -> googletest::Result<()> {
|
||
let matcher = ends_with("A string");
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||
displays_as(eq("ends with suffix \"A string\""))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn describes_itself_for_non_matching_result_in_ends_with_mode() -> googletest::Result<()> {
|
||
let matcher = ends_with("A string");
|
||
verify_that!(
|
||
Matcher::<&str>::describe(&matcher, MatcherResult::NoMatch),
|
||
displays_as(eq("does not end with \"A string\""))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> googletest::Result<()>
|
||
{
|
||
let result = verify_that!(
|
||
indoc!(
|
||
"
|
||
First line
|
||
Second line
|
||
Third line
|
||
"
|
||
),
|
||
starts_with(indoc!(
|
||
"
|
||
First line
|
||
Second lines
|
||
Third line
|
||
"
|
||
))
|
||
);
|
||
|
||
verify_that!(
|
||
result,
|
||
err(displays_as(contains_substring(
|
||
"\
|
||
First line
|
||
-Second line
|
||
+Second lines
|
||
Third line"
|
||
)))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn match_explanation_for_starts_with_ignores_trailing_lines_in_actual_string(
|
||
) -> googletest::Result<()> {
|
||
let result = verify_that!(
|
||
indoc!(
|
||
"
|
||
First line
|
||
Second line
|
||
Third line
|
||
Fourth line
|
||
"
|
||
),
|
||
starts_with(indoc!(
|
||
"
|
||
First line
|
||
Second lines
|
||
Third line
|
||
"
|
||
))
|
||
);
|
||
|
||
verify_that!(
|
||
result,
|
||
err(displays_as(contains_substring(
|
||
"
|
||
First line
|
||
-Second line
|
||
+Second lines
|
||
Third line
|
||
<---- remaining lines omitted ---->"
|
||
)))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn match_explanation_for_starts_with_includes_both_versions_of_differing_last_line(
|
||
) -> googletest::Result<()> {
|
||
let result = verify_that!(
|
||
indoc!(
|
||
"
|
||
First line
|
||
Second line
|
||
Third line
|
||
"
|
||
),
|
||
starts_with(indoc!(
|
||
"
|
||
First line
|
||
Second lines
|
||
"
|
||
))
|
||
);
|
||
|
||
verify_that!(
|
||
result,
|
||
err(displays_as(contains_substring(
|
||
"\
|
||
First line
|
||
-Second line
|
||
+Second lines
|
||
<---- remaining lines omitted ---->"
|
||
)))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn match_explanation_for_ends_with_ignores_leading_lines_in_actual_string(
|
||
) -> googletest::Result<()> {
|
||
let result = verify_that!(
|
||
indoc!(
|
||
"
|
||
First line
|
||
Second line
|
||
Third line
|
||
Fourth line
|
||
"
|
||
),
|
||
ends_with(indoc!(
|
||
"
|
||
Second line
|
||
Third lines
|
||
Fourth line
|
||
"
|
||
))
|
||
);
|
||
|
||
verify_that!(
|
||
result,
|
||
err(displays_as(contains_substring(
|
||
"
|
||
Difference(-actual / +expected):
|
||
<---- remaining lines omitted ---->
|
||
Second line
|
||
-Third line
|
||
+Third lines
|
||
Fourth line"
|
||
)))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn match_explanation_for_contains_substring_ignores_outer_lines_in_actual_string(
|
||
) -> googletest::Result<()> {
|
||
let result = verify_that!(
|
||
indoc!(
|
||
"
|
||
First line
|
||
Second line
|
||
Third line
|
||
Fourth line
|
||
Fifth line
|
||
"
|
||
),
|
||
contains_substring(indoc!(
|
||
"
|
||
Second line
|
||
Third lines
|
||
Fourth line
|
||
"
|
||
))
|
||
);
|
||
|
||
verify_that!(
|
||
result,
|
||
err(displays_as(contains_substring(
|
||
"
|
||
Difference(-actual / +expected):
|
||
<---- remaining lines omitted ---->
|
||
Second line
|
||
-Third line
|
||
+Third lines
|
||
Fourth line
|
||
<---- remaining lines omitted ---->"
|
||
)))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn match_explanation_for_contains_substring_shows_diff_when_first_and_last_line_are_incomplete(
|
||
) -> googletest::Result<()> {
|
||
let result = verify_that!(
|
||
indoc!(
|
||
"
|
||
First line
|
||
Second line
|
||
Third line
|
||
Fourth line
|
||
Fifth line
|
||
"
|
||
),
|
||
contains_substring(indoc!(
|
||
"
|
||
line
|
||
Third line
|
||
Foorth line
|
||
Fifth"
|
||
))
|
||
);
|
||
|
||
verify_that!(
|
||
result,
|
||
err(displays_as(contains_substring(
|
||
"
|
||
Difference(-actual / +expected):
|
||
<---- remaining lines omitted ---->
|
||
-Second line
|
||
+line
|
||
Third line
|
||
-Fourth line
|
||
+Foorth line
|
||
-Fifth line
|
||
+Fifth
|
||
<---- remaining lines omitted ---->"
|
||
)))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn match_explanation_for_eq_does_not_ignore_trailing_lines_in_actual_string(
|
||
) -> googletest::Result<()> {
|
||
let result = verify_that!(
|
||
indoc!(
|
||
"
|
||
First line
|
||
Second line
|
||
Third line
|
||
Fourth line
|
||
"
|
||
),
|
||
eq(indoc!(
|
||
"
|
||
First line
|
||
Second lines
|
||
Third line
|
||
"
|
||
))
|
||
);
|
||
|
||
verify_that!(
|
||
result,
|
||
err(displays_as(contains_substring(
|
||
"\
|
||
First line
|
||
-Second line
|
||
+Second lines
|
||
Third line
|
||
-Fourth line"
|
||
)))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn match_explanation_does_not_show_diff_if_actual_value_is_single_line(
|
||
) -> googletest::Result<()> {
|
||
let result = verify_that!(
|
||
"First line",
|
||
starts_with(indoc!(
|
||
"
|
||
Second line
|
||
Third line
|
||
"
|
||
))
|
||
);
|
||
|
||
verify_that!(
|
||
result,
|
||
err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn match_explanation_does_not_show_diff_if_expected_value_is_single_line(
|
||
) -> googletest::Result<()> {
|
||
let result = verify_that!(
|
||
indoc!(
|
||
"
|
||
First line
|
||
Second line
|
||
Third line
|
||
"
|
||
),
|
||
starts_with("Second line")
|
||
);
|
||
|
||
verify_that!(
|
||
result,
|
||
err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
|
||
)
|
||
}
|
||
}
|