| // 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::{ |
| matcher::{Matcher, MatcherResult}, |
| matcher_support::{ |
| edit_distance, |
| summarize_diff::{create_diff, create_diff_reversed}, |
| }, |
| matchers::{eq_deref_of_matcher::EqDerefOfMatcher, eq_matcher::EqMatcher}, |
| }; |
| use std::borrow::Cow; |
| use std::fmt::Debug; |
| use std::marker::PhantomData; |
| 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] is |
| /// > set. |
| pub fn contains_substring<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> { |
| StrMatcher { |
| configuration: Configuration { mode: MatchMode::Contains, ..Default::default() }, |
| expected, |
| phantom: Default::default(), |
| } |
| } |
| |
| /// 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<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> { |
| StrMatcher { |
| configuration: Configuration { mode: MatchMode::StartsWith, ..Default::default() }, |
| expected, |
| phantom: Default::default(), |
| } |
| } |
| |
| /// 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<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> { |
| StrMatcher { |
| configuration: Configuration { mode: MatchMode::EndsWith, ..Default::default() }, |
| expected, |
| phantom: Default::default(), |
| } |
| } |
| |
| /// 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<ActualT: ?Sized, 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<ActualT, 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<ActualT, 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<ActualT, 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<ActualT, 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<ActualT = usize> + 'static, |
| ) -> StrMatcher<ActualT, 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`]. |
| pub struct StrMatcher<ActualT: ?Sized, ExpectedT> { |
| expected: ExpectedT, |
| configuration: Configuration, |
| phantom: PhantomData<ActualT>, |
| } |
| |
| impl<ExpectedT, ActualT> Matcher for StrMatcher<ActualT, ExpectedT> |
| where |
| ExpectedT: Deref<Target = str> + Debug, |
| ActualT: AsRef<str> + Debug + ?Sized, |
| { |
| type ActualT = ActualT; |
| |
| fn matches(&self, actual: &ActualT) -> MatcherResult { |
| self.configuration.do_strings_match(self.expected.deref(), actual.as_ref()).into() |
| } |
| |
| fn describe(&self, matcher_result: MatcherResult) -> String { |
| self.configuration.describe(matcher_result, self.expected.deref()) |
| } |
| |
| fn explain_match(&self, actual: &ActualT) -> String { |
| self.configuration.explain_match(self.expected.deref(), actual.as_ref()) |
| } |
| } |
| |
| impl<ActualT: ?Sized, ExpectedT, MatcherT: Into<StrMatcher<ActualT, ExpectedT>>> |
| StrMatcherConfigurator<ActualT, ExpectedT> for MatcherT |
| { |
| fn ignoring_leading_whitespace(self) -> StrMatcher<ActualT, ExpectedT> { |
| let existing = self.into(); |
| StrMatcher { |
| configuration: existing.configuration.ignoring_leading_whitespace(), |
| ..existing |
| } |
| } |
| |
| fn ignoring_trailing_whitespace(self) -> StrMatcher<ActualT, ExpectedT> { |
| let existing = self.into(); |
| StrMatcher { |
| configuration: existing.configuration.ignoring_trailing_whitespace(), |
| ..existing |
| } |
| } |
| |
| fn ignoring_outer_whitespace(self) -> StrMatcher<ActualT, ExpectedT> { |
| let existing = self.into(); |
| StrMatcher { configuration: existing.configuration.ignoring_outer_whitespace(), ..existing } |
| } |
| |
| fn ignoring_ascii_case(self) -> StrMatcher<ActualT, ExpectedT> { |
| let existing = self.into(); |
| StrMatcher { configuration: existing.configuration.ignoring_ascii_case(), ..existing } |
| } |
| |
| fn times( |
| self, |
| times: impl Matcher<ActualT = usize> + 'static, |
| ) -> StrMatcher<ActualT, 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<A: ?Sized, T: Deref<Target = str>> From<EqMatcher<A, T>> for StrMatcher<A, T> { |
| fn from(value: EqMatcher<A, T>) -> Self { |
| Self::with_default_config(value.expected) |
| } |
| } |
| |
| impl<A: ?Sized, T: Deref<Target = str>> From<EqDerefOfMatcher<A, T>> for StrMatcher<A, T> { |
| fn from(value: EqDerefOfMatcher<A, T>) -> Self { |
| Self::with_default_config(value.expected) |
| } |
| } |
| |
| impl<A: ?Sized, T> StrMatcher<A, 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(), phantom: 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<ActualT = 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, |
| } |
| |
| 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), |
| }, |
| 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(), |
| ), |
| }, |
| 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) |
| } |
| }, |
| 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) |
| } |
| }, |
| } |
| } |
| |
| // 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::describe redirects immediately to this function. |
| fn describe(&self, matcher_result: MatcherResult, expected: &str) -> String { |
| 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()), |
| } |
| 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}") |
| } |
| |
| fn explain_match(&self, expected: &str, actual: &str) -> String { |
| let default_explanation = format!( |
| "which {}", |
| self.describe(self.do_strings_match(expected, actual).into(), expected) |
| ); |
| 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 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()), |
| }; |
| |
| format!("{default_explanation}\n{diff}",) |
| } |
| |
| 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 times(self, times: impl Matcher<ActualT = 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 super::{contains_substring, ends_with, starts_with, StrMatcher, StrMatcherConfigurator}; |
| use crate::matcher::{Matcher, MatcherResult}; |
| use crate::prelude::*; |
| use indoc::indoc; |
| |
| #[test] |
| fn matches_string_reference_with_equal_string_reference() -> 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() -> Result<()> { |
| let matcher = StrMatcher::with_default_config("Another string"); |
| verify_that!("A string", not(matcher)) |
| } |
| |
| #[test] |
| fn matches_owned_string_with_string_reference() -> 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() -> 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() -> 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() -> 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() -> 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() -> 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() -> 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() -> 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() -> 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() -> 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() -> 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() -> 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() -> Result<()> { |
| let matcher = StrMatcher::with_default_config("A string"); |
| verify_that!("A STRING", not(matcher)) |
| } |
| |
| #[test] |
| fn ignores_ascii_case_when_requested() -> Result<()> { |
| let matcher = StrMatcher::with_default_config("A string"); |
| verify_that!("A STRING", matcher.ignoring_ascii_case()) |
| } |
| |
| #[test] |
| fn allows_ignoring_leading_whitespace_from_eq() -> Result<()> { |
| verify_that!("A string", eq(" \n\tA string").ignoring_leading_whitespace()) |
| } |
| |
| #[test] |
| fn allows_ignoring_trailing_whitespace_from_eq() -> Result<()> { |
| verify_that!("A string", eq("A string \n\t").ignoring_trailing_whitespace()) |
| } |
| |
| #[test] |
| fn allows_ignoring_outer_whitespace_from_eq() -> Result<()> { |
| verify_that!("A string", eq(" \n\tA string \n\t").ignoring_outer_whitespace()) |
| } |
| |
| #[test] |
| fn allows_ignoring_ascii_case_from_eq() -> Result<()> { |
| verify_that!("A string", eq("A STRING").ignoring_ascii_case()) |
| } |
| |
| #[test] |
| fn allows_ignoring_ascii_case_from_eq_deref_of_str_slice() -> Result<()> { |
| verify_that!("A string", eq_deref_of("A STRING").ignoring_ascii_case()) |
| } |
| |
| #[test] |
| fn allows_ignoring_ascii_case_from_eq_deref_of_owned_string() -> Result<()> { |
| verify_that!("A string", eq_deref_of("A STRING".to_string()).ignoring_ascii_case()) |
| } |
| |
| #[test] |
| fn matches_string_containing_expected_value_in_contains_mode() -> Result<()> { |
| verify_that!("Some string", contains_substring("str")) |
| } |
| |
| #[test] |
| fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case() |
| -> Result<()> { |
| verify_that!("Some string", contains_substring("STR").ignoring_ascii_case()) |
| } |
| |
| #[test] |
| fn contains_substring_matches_correct_number_of_substrings() -> Result<()> { |
| verify_that!("Some string", contains_substring("str").times(eq(1))) |
| } |
| |
| #[test] |
| fn contains_substring_does_not_match_incorrect_number_of_substrings() -> Result<()> { |
| verify_that!("Some string\nSome string", not(contains_substring("string").times(eq(1)))) |
| } |
| |
| #[test] |
| fn contains_substring_does_not_match_when_substrings_overlap() -> Result<()> { |
| verify_that!("ababab", not(contains_substring("abab").times(eq(2)))) |
| } |
| |
| #[test] |
| fn starts_with_matches_string_reference_with_prefix() -> Result<()> { |
| verify_that!("Some value", starts_with("Some")) |
| } |
| |
| #[test] |
| fn starts_with_matches_string_reference_with_prefix_ignoring_ascii_case() -> Result<()> { |
| verify_that!("Some value", starts_with("SOME").ignoring_ascii_case()) |
| } |
| |
| #[test] |
| fn starts_with_does_not_match_wrong_prefix_ignoring_ascii_case() -> Result<()> { |
| verify_that!("Some value", not(starts_with("OTHER").ignoring_ascii_case())) |
| } |
| |
| #[test] |
| fn ends_with_does_not_match_short_string_ignoring_ascii_case() -> Result<()> { |
| verify_that!("Some", not(starts_with("OTHER").ignoring_ascii_case())) |
| } |
| |
| #[test] |
| fn starts_with_does_not_match_string_without_prefix() -> Result<()> { |
| verify_that!("Some value", not(starts_with("Another"))) |
| } |
| |
| #[test] |
| fn starts_with_does_not_match_string_with_substring_not_at_beginning() -> Result<()> { |
| verify_that!("Some value", not(starts_with("value"))) |
| } |
| |
| #[test] |
| fn ends_with_matches_string_reference_with_suffix() -> Result<()> { |
| verify_that!("Some value", ends_with("value")) |
| } |
| |
| #[test] |
| fn ends_with_matches_string_reference_with_suffix_ignoring_ascii_case() -> Result<()> { |
| verify_that!("Some value", ends_with("VALUE").ignoring_ascii_case()) |
| } |
| |
| #[test] |
| fn ends_with_does_not_match_wrong_suffix_ignoring_ascii_case() -> 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() -> Result<()> { |
| verify_that!("Some", not(ends_with("OTHER").ignoring_ascii_case())) |
| } |
| |
| #[test] |
| fn ends_with_does_not_match_string_without_suffix() -> Result<()> { |
| verify_that!("Some value", not(ends_with("other value"))) |
| } |
| |
| #[test] |
| fn ends_with_does_not_match_string_with_substring_not_at_end() -> Result<()> { |
| verify_that!("Some value", not(ends_with("Some"))) |
| } |
| |
| #[test] |
| fn describes_itself_for_matching_result() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string"); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::Match), |
| eq("is equal to \"A string\"") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_non_matching_result() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string"); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::NoMatch), |
| eq("isn't equal to \"A string\"") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_matching_result_ignoring_leading_whitespace() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = |
| StrMatcher::with_default_config("A string").ignoring_leading_whitespace(); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::Match), |
| eq("is equal to \"A string\" (ignoring leading whitespace)") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_non_matching_result_ignoring_leading_whitespace() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = |
| StrMatcher::with_default_config("A string").ignoring_leading_whitespace(); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::NoMatch), |
| eq("isn't equal to \"A string\" (ignoring leading whitespace)") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_matching_result_ignoring_trailing_whitespace() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = |
| StrMatcher::with_default_config("A string").ignoring_trailing_whitespace(); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::Match), |
| eq("is equal to \"A string\" (ignoring trailing whitespace)") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_matching_result_ignoring_leading_and_trailing_whitespace() -> Result<()> |
| { |
| let matcher: StrMatcher<&str, _> = |
| StrMatcher::with_default_config("A string").ignoring_outer_whitespace(); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::Match), |
| eq("is equal to \"A string\" (ignoring leading and trailing whitespace)") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_matching_result_ignoring_ascii_case() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = |
| StrMatcher::with_default_config("A string").ignoring_ascii_case(); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::Match), |
| eq("is equal to \"A string\" (ignoring ASCII case)") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace() |
| -> Result<()> { |
| let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string") |
| .ignoring_leading_whitespace() |
| .ignoring_ascii_case(); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::Match), |
| eq("is equal to \"A string\" (ignoring leading whitespace, ignoring ASCII case)") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_matching_result_in_contains_mode() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = contains_substring("A string"); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::Match), |
| eq("contains a substring \"A string\"") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_non_matching_result_in_contains_mode() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = contains_substring("A string"); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::NoMatch), |
| eq("does not contain a substring \"A string\"") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_with_count_number() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = contains_substring("A string").times(gt(2)); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::Match), |
| eq("contains a substring \"A string\" (count is greater than 2)") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_matching_result_in_starts_with_mode() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = starts_with("A string"); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::Match), |
| eq("starts with prefix \"A string\"") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_non_matching_result_in_starts_with_mode() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = starts_with("A string"); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::NoMatch), |
| eq("does not start with \"A string\"") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_matching_result_in_ends_with_mode() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = ends_with("A string"); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::Match), |
| eq("ends with suffix \"A string\"") |
| ) |
| } |
| |
| #[test] |
| fn describes_itself_for_non_matching_result_in_ends_with_mode() -> Result<()> { |
| let matcher: StrMatcher<&str, _> = ends_with("A string"); |
| verify_that!( |
| Matcher::describe(&matcher, MatcherResult::NoMatch), |
| eq("does not end with \"A string\"") |
| ) |
| } |
| |
| #[test] |
| fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> 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(indoc!( |
| " |
| First line |
| -Second line |
| +Second lines |
| Third line |
| " |
| )))) |
| ) |
| } |
| |
| #[test] |
| fn match_explanation_for_starts_with_ignores_trailing_lines_in_actual_string() -> 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(indoc!( |
| " |
| 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() |
| -> 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(indoc!( |
| " |
| First line |
| -Second line |
| +Second lines |
| <---- remaining lines omitted ----> |
| " |
| )))) |
| ) |
| } |
| |
| #[test] |
| fn match_explanation_for_ends_with_ignores_leading_lines_in_actual_string() -> 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(indoc!( |
| " |
| Difference(-actual / +expected): |
| <---- remaining lines omitted ----> |
| Second line |
| +Third lines |
| -Third line |
| Fourth line |
| " |
| )))) |
| ) |
| } |
| |
| #[test] |
| fn match_explanation_for_contains_substring_ignores_outer_lines_in_actual_string() -> 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(indoc!( |
| " |
| Difference(-actual / +expected): |
| <---- remaining lines omitted ----> |
| Second line |
| +Third lines |
| -Third line |
| Fourth line |
| <---- remaining lines omitted ---->" |
| )))) |
| ) |
| } |
| |
| #[test] |
| fn match_explanation_for_contains_substring_shows_diff_when_first_and_last_line_are_incomplete() |
| -> 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(indoc!( |
| " |
| Difference(-actual / +expected): |
| <---- remaining lines omitted ----> |
| +line |
| -Second line |
| Third line |
| +Foorth line |
| -Fourth line |
| +Fifth |
| -Fifth line |
| <---- remaining lines omitted ----> |
| " |
| )))) |
| ) |
| } |
| |
| #[test] |
| fn match_explanation_for_eq_does_not_ignore_trailing_lines_in_actual_string() -> 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(indoc!( |
| " |
| First line |
| -Second line |
| +Second lines |
| Third line |
| -Fourth line |
| " |
| )))) |
| ) |
| } |
| |
| #[test] |
| fn match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> 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() -> 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):")))) |
| ) |
| } |
| } |