diff --git a/googletest/crate_docs.md b/googletest/crate_docs.md index 8c8b47cf..165ac226 100644 --- a/googletest/crate_docs.md +++ b/googletest/crate_docs.md @@ -127,6 +127,7 @@ The following matchers are provided in GoogleTest Rust: | [`ends_with`] | A string ending with the given suffix. | | [`eq`] | A value equal to the argument, in the sense of the [`PartialEq`] trait. | | [`eq_deref_of`] | A value equal to the dereferenced value of the argument. | +| [`eq_str`] | A string equal to the argument. | | [`err`] | A [`Result`][std::result::Result] containing an `Err` variant the argument matches. | | [`field!`] | A struct or enum with a given field whose value the argument matches. | | [`ge`] | A [`PartialOrd`] value greater than or equal to the given value. | @@ -134,6 +135,7 @@ The following matchers are provided in GoogleTest Rust: | [`has_entry`] | A [`HashMap`] containing a given key whose value the argument matches. | | [`is_contained_in!`] | A container each of whose elements is matched by some given matcher. | | [`is_nan`] | A floating point number which is NaN. | +| [`is_utf8_string`] | A byte sequence representing a UTF-8 encoded string which the argument matches. | | [`le`] | A [`PartialOrd`] value less than or equal to the given value. | | [`len`] | A container whose number of elements the argument matches. | | [`lt`] | A [`PartialOrd`] value strictly less than the given value. | @@ -169,6 +171,7 @@ The following matchers are provided in GoogleTest Rust: [`empty`]: matchers::empty [`ends_with`]: matchers::ends_with [`eq`]: matchers::eq +[`eq_str`]: matchers::eq_str [`eq_deref_of`]: matchers::eq_deref_of [`err`]: matchers::err [`field!`]: matchers::field @@ -177,6 +180,7 @@ The following matchers are provided in GoogleTest Rust: [`has_entry`]: matchers::has_entry [`is_contained_in!`]: matchers::is_contained_in [`is_nan`]: matchers::is_nan +[`is_utf8_string`]: matchers::is_utf8_string [`le`]: matchers::le [`len`]: matchers::len [`lt`]: matchers::lt @@ -210,7 +214,7 @@ a struct holding the matcher's data and have it implement the trait ```no_run use googletest::{description::Description, matcher::{Matcher, MatcherResult}}; -use std::fmt::Debug; +use std::{fmt::Debug, ops::Deref}; struct MyEqMatcher { expected: T, @@ -219,7 +223,10 @@ struct MyEqMatcher { impl Matcher for MyEqMatcher { type ActualT = T; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { if self.expected == *actual { MatcherResult::Match } else { @@ -244,7 +251,7 @@ impl Matcher for MyEqMatcher { ```no_run # use googletest::{description::Description, matcher::{Matcher, MatcherResult}}; - # use std::fmt::Debug; + # use std::{fmt::Debug, ops::Deref}; # # struct MyEqMatcher { # expected: T, @@ -253,7 +260,10 @@ impl Matcher for MyEqMatcher { # impl Matcher for MyEqMatcher { # type ActualT = T; # - # fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + # fn matches>( + # &self, + # actual: ActualRefT, + # ) -> MatcherResult { # if self.expected == *actual { # MatcherResult::Match # } else { @@ -283,7 +293,7 @@ impl Matcher for MyEqMatcher { ``` # use googletest::prelude::*; # use googletest::{description::Description, matcher::{Matcher, MatcherResult}}; -# use std::fmt::Debug; +# use std::{fmt::Debug, ops::Deref}; # # struct MyEqMatcher { # expected: T, @@ -292,7 +302,10 @@ impl Matcher for MyEqMatcher { # impl Matcher for MyEqMatcher { # type ActualT = T; # -# fn matches(&self, actual: &Self::ActualT) -> MatcherResult { +# fn matches>( +# &self, +# actual: ActualRefT, +# ) -> MatcherResult { # if self.expected == *actual { # MatcherResult::Match # } else { diff --git a/googletest/src/matcher.rs b/googletest/src/matcher.rs index 4edabbd5..bb32d73e 100644 --- a/googletest/src/matcher.rs +++ b/googletest/src/matcher.rs @@ -20,6 +20,7 @@ use crate::internal::test_outcome::TestAssertionFailure; use crate::matchers::__internal_unstable_do_not_depend_on_these::ConjunctionMatcher; use crate::matchers::__internal_unstable_do_not_depend_on_these::DisjunctionMatcher; use std::fmt::Debug; +use std::ops::Deref; /// An interface for checking an arbitrary condition on a datum. /// @@ -36,7 +37,10 @@ pub trait Matcher { /// matching condition is based on data stored in the matcher. For example, /// `eq` matches when its stored expected value is equal (in the sense of /// the `==` operator) to the value `actual`. - fn matches(&self, actual: &Self::ActualT) -> MatcherResult; + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult; /// Returns a description of `self` or a negative description if /// `matcher_result` is `DoesNotMatch`. @@ -137,7 +141,10 @@ pub trait Matcher { /// .nested(self.expected.explain_match(actual.deref())) /// } /// ``` - fn explain_match(&self, actual: &Self::ActualT) -> Description { + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { format!("which {}", self.describe(self.matches(actual))).into() } @@ -205,6 +212,46 @@ pub trait Matcher { } } +/// Functionality used by macros within this crate. +/// +/// For internal use only. API stablility is not guaranteed! +#[doc(hidden)] +pub mod __internal_unstable_do_not_depend_on_these { + use super::{Matcher, MatcherResult}; + use crate::description::Description; + use std::fmt::Debug; + + /// A variant of [`Matcher`] which is object-safe. + /// + /// This is used in contexts where a `dyn Matcher` is required. It supplies all methods of + /// [`Matcher`] but without any generics on the methods. + pub trait ObjectSafeMatcher { + type ActualT: Debug + ?Sized; + + fn obj_matches(&self, actual: &Self::ActualT) -> MatcherResult; + + fn obj_describe(&self, matcher_result: MatcherResult) -> Description; + + fn obj_explain_match(&self, actual: &Self::ActualT) -> Description; + } + + impl ObjectSafeMatcher for MatcherT { + type ActualT = ::ActualT; + + fn obj_matches(&self, actual: &Self::ActualT) -> MatcherResult { + Matcher::matches(self, actual) + } + + fn obj_describe(&self, matcher_result: MatcherResult) -> Description { + Matcher::describe(self, matcher_result) + } + + fn obj_explain_match(&self, actual: &Self::ActualT) -> Description { + Matcher::explain_match(self, actual) + } + } +} + /// Any actual value whose debug length is greater than this value will be /// pretty-printed. Otherwise, it will have normal debug output formatting. const PRETTY_PRINT_LENGTH_THRESHOLD: usize = 60; @@ -249,7 +296,11 @@ pub enum MatcherResult { impl From for MatcherResult { fn from(b: bool) -> Self { - if b { MatcherResult::Match } else { MatcherResult::NoMatch } + if b { + MatcherResult::Match + } else { + MatcherResult::NoMatch + } } } @@ -276,16 +327,22 @@ impl MatcherResult { impl Matcher for &M { type ActualT = M::ActualT; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { - (*self).matches(actual) + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + (*self).matches(actual.deref()) } fn describe(&self, matcher_result: MatcherResult) -> Description { (*self).describe(matcher_result) } - fn explain_match(&self, actual: &Self::ActualT) -> Description { - (*self).explain_match(actual) + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + (*self).explain_match(actual.deref()) } } diff --git a/googletest/src/matchers/anything_matcher.rs b/googletest/src/matchers/anything_matcher.rs index 36be478b..f423ea0c 100644 --- a/googletest/src/matchers/anything_matcher.rs +++ b/googletest/src/matchers/anything_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches anything. This matcher always succeeds. /// @@ -41,7 +41,7 @@ struct Anything(PhantomData); impl Matcher for Anything { type ActualT = T; - fn matches(&self, _: &T) -> MatcherResult { + fn matches>(&self, _: ActualRefT) -> MatcherResult { MatcherResult::Match } diff --git a/googletest/src/matchers/char_count_matcher.rs b/googletest/src/matchers/char_count_matcher.rs index 70977d74..219300cb 100644 --- a/googletest/src/matchers/char_count_matcher.rs +++ b/googletest/src/matchers/char_count_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a string whose number of Unicode scalars matches `expected`. /// @@ -70,7 +70,10 @@ struct CharLenMatcher { impl, E: Matcher> Matcher for CharLenMatcher { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { self.expected.matches(&actual.as_ref().chars().count()) } @@ -89,7 +92,10 @@ impl, E: Matcher> Matcher for Ch } } - fn explain_match(&self, actual: &T) -> Description { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { let actual_size = actual.as_ref().chars().count(); format!( "which has character count {}, {}", @@ -109,6 +115,7 @@ mod tests { use indoc::indoc; use std::fmt::Debug; use std::marker::PhantomData; + use std::ops::Deref; #[test] fn char_count_matches_string_slice() -> Result<()> { @@ -134,7 +141,10 @@ mod tests { impl Matcher for TestMatcher { type ActualT = T; - fn matches(&self, _: &T) -> MatcherResult { + fn matches>( + &self, + _: ActualRefT, + ) -> MatcherResult { false.into() } @@ -142,7 +152,10 @@ mod tests { "called described".into() } - fn explain_match(&self, _: &T) -> Description { + fn explain_match>( + &self, + _: ActualRefT, + ) -> Description { "called explain_match".into() } } diff --git a/googletest/src/matchers/conjunction_matcher.rs b/googletest/src/matchers/conjunction_matcher.rs index 0f3c606d..12e871b7 100644 --- a/googletest/src/matchers/conjunction_matcher.rs +++ b/googletest/src/matchers/conjunction_matcher.rs @@ -19,7 +19,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::fmt::Debug; +use std::{fmt::Debug, ops::Deref}; /// Matcher created by [`Matcher::and`] and [`all!`]. /// @@ -58,25 +58,31 @@ where { type ActualT = M1::ActualT; - fn matches(&self, actual: &M1::ActualT) -> MatcherResult { - match (self.m1.matches(actual), self.m2.matches(actual)) { + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + match (self.m1.matches(actual.clone()), self.m2.matches(actual.clone())) { (MatcherResult::Match, MatcherResult::Match) => MatcherResult::Match, _ => MatcherResult::NoMatch, } } - fn explain_match(&self, actual: &M1::ActualT) -> Description { - match (self.m1.matches(actual), self.m2.matches(actual)) { - (MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual), - (MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual), + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { + match (self.m1.matches(actual.clone()), self.m2.matches(actual.clone())) { + (MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual.clone()), + (MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual.clone()), (_, _) => { - let m1_description = self.m1.explain_match(actual); + let m1_description = self.m1.explain_match(actual.clone()); if m1_description.is_conjunction_description() { - m1_description.nested(self.m2.explain_match(actual)) + m1_description.nested(self.m2.explain_match(actual.clone())) } else { Description::new() .bullet_list() - .collect([m1_description, self.m2.explain_match(actual)]) + .collect([m1_description, self.m2.explain_match(actual.clone())]) .conjunction_description() } } diff --git a/googletest/src/matchers/container_eq_matcher.rs b/googletest/src/matchers/container_eq_matcher.rs index d4f872c7..01daf58a 100644 --- a/googletest/src/matchers/container_eq_matcher.rs +++ b/googletest/src/matchers/container_eq_matcher.rs @@ -16,6 +16,7 @@ use crate::description::Description; use crate::matcher::{Matcher, MatcherResult}; use std::fmt::Debug; use std::marker::PhantomData; +use std::ops::Deref; /// Matches a container equal (in the sense of `==`) to `expected`. /// @@ -116,12 +117,22 @@ where { type ActualT = ActualContainerT; - fn matches(&self, actual: &ActualContainerT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { (*actual == self.expected).into() } - fn explain_match(&self, actual: &ActualContainerT) -> Description { - build_explanation(self.get_missing_items(actual), self.get_unexpected_items(actual)).into() + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + build_explanation( + self.get_missing_items(actual.deref()), + self.get_unexpected_items(actual.deref()), + ) + .into() } fn describe(&self, matcher_result: MatcherResult) -> Description { @@ -271,15 +282,15 @@ mod tests { } #[test] - fn container_eq_matches_owned_vec_of_owned_strings_with_slice_of_string_references() - -> Result<()> { + fn container_eq_matches_owned_vec_of_owned_strings_with_slice_of_string_references( + ) -> Result<()> { let vector = vec!["A string".to_string(), "Another string".to_string()]; verify_that!(vector, container_eq(["A string", "Another string"])) } #[test] - fn container_eq_matches_owned_vec_of_owned_strings_with_shorter_slice_of_string_references() - -> Result<()> { + fn container_eq_matches_owned_vec_of_owned_strings_with_shorter_slice_of_string_references( + ) -> Result<()> { let actual = vec!["A string".to_string(), "Another string".to_string()]; let matcher = container_eq(["A string"]); diff --git a/googletest/src/matchers/contains_matcher.rs b/googletest/src/matchers/contains_matcher.rs index 1a27ce04..9108d097 100644 --- a/googletest/src/matchers/contains_matcher.rs +++ b/googletest/src/matchers/contains_matcher.rs @@ -14,9 +14,11 @@ use crate::{ description::Description, - matcher::{Matcher, MatcherResult}, + matcher::{ + Matcher, MatcherResult, __internal_unstable_do_not_depend_on_these::ObjectSafeMatcher, + }, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches an iterable type whose elements contain a value matched by `inner`. /// @@ -51,7 +53,7 @@ pub fn contains(inner: InnerMatcherT) -> ContainsMatcher { inner: InnerMatcherT, - count: Option>>, + count: Option>>, phantom: PhantomData, } @@ -91,9 +93,12 @@ where { type ActualT = ContainerT; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { if let Some(count) = &self.count { - count.matches(&self.count_matches(actual)) + count.obj_matches(&self.count_matches(actual.deref())) } else { for v in actual.into_iter() { if self.inner.matches(v).into() { @@ -104,8 +109,11 @@ where } } - fn explain_match(&self, actual: &Self::ActualT) -> Description { - let count = self.count_matches(actual); + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + let count = self.count_matches(actual.deref()); match (count, &self.count) { (_, Some(_)) => format!("which contains {} matching elements", count).into(), (0, None) => "which does not contain a matching element".into(), @@ -118,13 +126,13 @@ where (MatcherResult::Match, Some(count)) => format!( "contains n elements which {}\n where n {}", self.inner.describe(MatcherResult::Match), - count.describe(MatcherResult::Match) + count.obj_describe(MatcherResult::Match) ) .into(), (MatcherResult::NoMatch, Some(count)) => format!( "doesn't contain n elements which {}\n where n {}", self.inner.describe(MatcherResult::Match), - count.describe(MatcherResult::Match) + count.obj_describe(MatcherResult::Match) ) .into(), (MatcherResult::Match, None) => format!( diff --git a/googletest/src/matchers/contains_regex_matcher.rs b/googletest/src/matchers/contains_regex_matcher.rs index 6f681680..79021b5b 100644 --- a/googletest/src/matchers/contains_regex_matcher.rs +++ b/googletest/src/matchers/contains_regex_matcher.rs @@ -74,7 +74,10 @@ pub struct ContainsRegexMatcher { impl + Debug + ?Sized> Matcher for ContainsRegexMatcher { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { self.regex.is_match(actual.as_ref()).into() } diff --git a/googletest/src/matchers/disjunction_matcher.rs b/googletest/src/matchers/disjunction_matcher.rs index 5e0f130d..7964a45c 100644 --- a/googletest/src/matchers/disjunction_matcher.rs +++ b/googletest/src/matchers/disjunction_matcher.rs @@ -19,7 +19,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::fmt::Debug; +use std::{fmt::Debug, ops::Deref}; /// Matcher created by [`Matcher::or`] and [`any!`]. /// @@ -52,25 +52,31 @@ where { type ActualT = M1::ActualT; - fn matches(&self, actual: &M1::ActualT) -> MatcherResult { - match (self.m1.matches(actual), self.m2.matches(actual)) { + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + match (self.m1.matches(actual.clone()), self.m2.matches(actual.clone())) { (MatcherResult::NoMatch, MatcherResult::NoMatch) => MatcherResult::NoMatch, _ => MatcherResult::Match, } } - fn explain_match(&self, actual: &M1::ActualT) -> Description { - match (self.m1.matches(actual), self.m2.matches(actual)) { - (MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual), - (MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual), + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { + match (self.m1.matches(actual.clone()), self.m2.matches(actual.clone())) { + (MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual.clone()), + (MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual.clone()), (_, _) => { - let m1_description = self.m1.explain_match(actual); + let m1_description = self.m1.explain_match(actual.clone()); if m1_description.is_disjunction_description() { - m1_description.nested(self.m2.explain_match(actual)) + m1_description.nested(self.m2.explain_match(actual.clone())) } else { Description::new() .bullet_list() - .collect([m1_description, self.m2.explain_match(actual)]) + .collect([m1_description, self.m2.explain_match(actual.clone())]) .disjunction_description() } } diff --git a/googletest/src/matchers/display_matcher.rs b/googletest/src/matchers/display_matcher.rs index afc22a47..25d7d4cc 100644 --- a/googletest/src/matchers/display_matcher.rs +++ b/googletest/src/matchers/display_matcher.rs @@ -16,6 +16,7 @@ use crate::description::Description; use crate::matcher::{Matcher, MatcherResult}; use std::fmt::{Debug, Display}; use std::marker::PhantomData; +use std::ops::Deref; /// Matches the string representation of types that implement `Display`. /// @@ -39,15 +40,21 @@ impl> Matcher { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { - self.inner.matches(&format!("{actual}")) + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + self.inner.matches(&format!("{}", actual.deref())) } - fn explain_match(&self, actual: &T) -> Description { + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { format!( "which displays as {:?} {}", actual.to_string(), - self.inner.explain_match(&format!("{actual}")) + self.inner.explain_match(&format!("{}", actual.deref())) ) .into() } diff --git a/googletest/src/matchers/each_matcher.rs b/googletest/src/matchers/each_matcher.rs index 08a07426..8300cc8a 100644 --- a/googletest/src/matchers/each_matcher.rs +++ b/googletest/src/matchers/each_matcher.rs @@ -14,6 +14,7 @@ use crate::description::Description; use crate::matcher::{Matcher, MatcherResult}; +use std::ops::Deref; use std::{fmt::Debug, marker::PhantomData}; /// Matches a container all of whose elements are matched by the matcher @@ -83,8 +84,11 @@ where { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { - for element in actual { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + for element in actual.deref() { if self.inner.matches(element).is_no_match() { return MatcherResult::NoMatch; } @@ -92,7 +96,10 @@ where MatcherResult::Match } - fn explain_match(&self, actual: &ActualT) -> Description { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { let mut non_matching_elements = Vec::new(); for (index, element) in actual.into_iter().enumerate() { if self.inner.matches(element).is_no_match() { diff --git a/googletest/src/matchers/elements_are_matcher.rs b/googletest/src/matchers/elements_are_matcher.rs index 3a4b5b24..4853b110 100644 --- a/googletest/src/matchers/elements_are_matcher.rs +++ b/googletest/src/matchers/elements_are_matcher.rs @@ -93,8 +93,11 @@ macro_rules! __elements_are { #[doc(hidden)] pub mod internal { use crate::description::Description; - use crate::matcher::{Matcher, MatcherResult}; + use crate::matcher::{ + Matcher, MatcherResult, __internal_unstable_do_not_depend_on_these::ObjectSafeMatcher, + }; use crate::matcher_support::zipped_iterator::zip; + use std::ops::Deref; use std::{fmt::Debug, marker::PhantomData}; /// This struct is meant to be used only by the macro `elements_are!`. @@ -102,7 +105,7 @@ pub mod internal { /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] pub struct ElementsAre<'a, ContainerT: ?Sized, T: Debug> { - elements: Vec + 'a>>, + elements: Vec + 'a>>, phantom: PhantomData, } @@ -111,7 +114,7 @@ pub mod internal { /// /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] - pub fn new(elements: Vec + 'a>>) -> Self { + pub fn new(elements: Vec + 'a>>) -> Self { Self { elements, phantom: Default::default() } } } @@ -122,10 +125,13 @@ pub mod internal { { type ActualT = ContainerT; - fn matches(&self, actual: &ContainerT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { let mut zipped_iterator = zip(actual.into_iter(), self.elements.iter()); for (a, e) in zipped_iterator.by_ref() { - if e.matches(a).is_no_match() { + if e.obj_matches(a).is_no_match() { return MatcherResult::NoMatch; } } @@ -136,13 +142,16 @@ pub mod internal { } } - fn explain_match(&self, actual: &ContainerT) -> Description { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { let actual_iterator = actual.into_iter(); let mut zipped_iterator = zip(actual_iterator, self.elements.iter()); let mut mismatches = Vec::new(); for (idx, (a, e)) in zipped_iterator.by_ref().enumerate() { - if e.matches(a).is_no_match() { - mismatches.push(format!("element #{idx} is {a:?}, {}", e.explain_match(a))); + if e.obj_matches(a).is_no_match() { + mismatches.push(format!("element #{idx} is {a:?}, {}", e.obj_explain_match(a))); } } if mismatches.is_empty() { @@ -167,7 +176,7 @@ pub mod internal { &self .elements .iter() - .map(|matcher| matcher.describe(MatcherResult::Match)) + .map(|matcher| matcher.obj_describe(MatcherResult::Match)) .collect::() .enumerate() .indent() diff --git a/googletest/src/matchers/empty_matcher.rs b/googletest/src/matchers/empty_matcher.rs index 11cb6758..279509b1 100644 --- a/googletest/src/matchers/empty_matcher.rs +++ b/googletest/src/matchers/empty_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches an empty container. /// @@ -67,7 +67,10 @@ where { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { actual.into_iter().next().is_none().into() } diff --git a/googletest/src/matchers/eq_deref_of_matcher.rs b/googletest/src/matchers/eq_deref_of_matcher.rs index 3a6cfced..d51363fa 100644 --- a/googletest/src/matchers/eq_deref_of_matcher.rs +++ b/googletest/src/matchers/eq_deref_of_matcher.rs @@ -73,8 +73,11 @@ where { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { - (self.expected.deref() == actual).into() + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + (self.expected.deref() == actual.deref()).into() } fn describe(&self, matcher_result: MatcherResult) -> Description { @@ -84,12 +87,15 @@ where } } - fn explain_match(&self, actual: &ActualT) -> Description { + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { format!( "which {}{}", - &self.describe(self.matches(actual)), + &self.describe(self.matches(actual.clone())), create_diff( - &format!("{:#?}", actual), + &format!("{:#?}", actual.deref()), &format!("{:#?}", self.expected.deref()), edit_distance::Mode::Exact, ) diff --git a/googletest/src/matchers/eq_matcher.rs b/googletest/src/matchers/eq_matcher.rs index 26fea831..5c7966b8 100644 --- a/googletest/src/matchers/eq_matcher.rs +++ b/googletest/src/matchers/eq_matcher.rs @@ -17,6 +17,7 @@ use crate::matcher::{Matcher, MatcherResult}; use crate::matcher_support::edit_distance; use crate::matcher_support::summarize_diff::create_diff; +use std::ops::Deref; use std::{fmt::Debug, marker::PhantomData}; /// Matches a value equal (in the sense of `==`) to `expected`. @@ -86,7 +87,10 @@ pub struct EqMatcher { impl> Matcher for EqMatcher { type ActualT = A; - fn matches(&self, actual: &A) -> MatcherResult { + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult { (*actual == self.expected).into() } @@ -97,9 +101,12 @@ impl> Matcher for EqMatcher { } } - fn explain_match(&self, actual: &A) -> Description { + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { let expected_debug = format!("{:#?}", self.expected); - let actual_debug = format!("{:#?}", actual); + let actual_debug = format!("{:#?}", actual.deref()); let description = self.describe(self.matches(actual)); let diff = if is_multiline_string_debug(&actual_debug) diff --git a/googletest/src/matchers/err_matcher.rs b/googletest/src/matchers/err_matcher.rs index 3b10de4f..5c0a89b0 100644 --- a/googletest/src/matchers/err_matcher.rs +++ b/googletest/src/matchers/err_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a `Result` containing `Err` with a value matched by `inner`. /// @@ -55,12 +55,18 @@ impl> Matcher { type ActualT = std::result::Result; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { actual.as_ref().err().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch) } - fn explain_match(&self, actual: &Self::ActualT) -> Description { - match actual { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + match actual.deref() { Err(e) => { Description::new().text("which is an error").nested(self.inner.explain_match(e)) } diff --git a/googletest/src/matchers/field_matcher.rs b/googletest/src/matchers/field_matcher.rs index fc37ce66..912681cd 100644 --- a/googletest/src/matchers/field_matcher.rs +++ b/googletest/src/matchers/field_matcher.rs @@ -145,7 +145,7 @@ pub mod internal { description::Description, matcher::{Matcher, MatcherResult}, }; - use std::fmt::Debug; + use std::{fmt::Debug, ops::Deref}; /// Creates a matcher to verify a specific field of the actual struct using /// the provided inner matcher. @@ -171,16 +171,22 @@ pub mod internal { { type ActualT = OuterT; - fn matches(&self, actual: &OuterT) -> MatcherResult { - if let Some(value) = (self.field_accessor)(actual) { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + if let Some(value) = (self.field_accessor)(actual.deref()) { self.inner.matches(value) } else { MatcherResult::NoMatch } } - fn explain_match(&self, actual: &OuterT) -> Description { - if let Some(actual) = (self.field_accessor)(actual) { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + if let Some(actual) = (self.field_accessor)(actual.deref()) { format!( "which has field `{}`, {}", self.field_path, @@ -188,7 +194,7 @@ pub mod internal { ) .into() } else { - let formatted_actual_value = format!("{actual:?}"); + let formatted_actual_value = format!("{:?}", actual.deref()); let without_fields = formatted_actual_value.split('(').next().unwrap_or(""); let without_fields = without_fields.split('{').next().unwrap_or("").trim_end(); format!("which has the wrong enum variant `{without_fields}`").into() diff --git a/googletest/src/matchers/ge_matcher.rs b/googletest/src/matchers/ge_matcher.rs index cb2a91ff..422a75bd 100644 --- a/googletest/src/matchers/ge_matcher.rs +++ b/googletest/src/matchers/ge_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a value greater than or equal to (in the sense of `>=`) `expected`. /// @@ -90,7 +90,10 @@ impl, ExpectedT: Debug> Matcher { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { (*actual >= self.expected).into() } diff --git a/googletest/src/matchers/gt_matcher.rs b/googletest/src/matchers/gt_matcher.rs index b52c6e3d..d8f53ffe 100644 --- a/googletest/src/matchers/gt_matcher.rs +++ b/googletest/src/matchers/gt_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a value greater (in the sense of `>`) than `expected`. /// @@ -90,7 +90,10 @@ impl, ExpectedT: Debug> Matcher { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { (*actual > self.expected).into() } diff --git a/googletest/src/matchers/has_entry_matcher.rs b/googletest/src/matchers/has_entry_matcher.rs index 7f9d2add..1ac508ed 100644 --- a/googletest/src/matchers/has_entry_matcher.rs +++ b/googletest/src/matchers/has_entry_matcher.rs @@ -18,6 +18,7 @@ use std::collections::HashMap; use std::fmt::Debug; use std::hash::Hash; use std::marker::PhantomData; +use std::ops::Deref; /// Matches a HashMap containing the given `key` whose value is matched by the /// matcher `inner`. @@ -80,7 +81,10 @@ impl { type ActualT = HashMap; - fn matches(&self, actual: &HashMap) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { if let Some(value) = actual.get(&self.key) { self.inner.matches(value) } else { @@ -88,7 +92,10 @@ impl } } - fn explain_match(&self, actual: &HashMap) -> Description { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { if let Some(value) = actual.get(&self.key) { format!( "which contains key {:?}, but is mapped to value {:#?}, {}", diff --git a/googletest/src/matchers/is_encoded_string_matcher.rs b/googletest/src/matchers/is_encoded_string_matcher.rs index d2fb2596..c073453f 100644 --- a/googletest/src/matchers/is_encoded_string_matcher.rs +++ b/googletest/src/matchers/is_encoded_string_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a byte sequence which is a UTF-8 encoded string matched by `inner`. /// @@ -25,18 +25,21 @@ use std::{fmt::Debug, marker::PhantomData}; /// /// The input may be a slice `&[u8]` or a `Vec` of bytes. /// +/// When asserting the equality of the actual value with a given string, use +/// [`eq_str`] rather than [`eq`]. +/// /// ``` /// # use googletest::prelude::*; /// # fn should_pass() -> Result<()> { /// let bytes: &[u8] = "A string".as_bytes(); -/// verify_that!(bytes, is_utf8_string(eq("A string")))?; // Passes +/// verify_that!(bytes, is_utf8_string(eq_str("A string")))?; // Passes /// let bytes: Vec = "A string".as_bytes().to_vec(); -/// verify_that!(bytes, is_utf8_string(eq("A string")))?; // Passes +/// verify_that!(bytes, is_utf8_string(eq_str("A string")))?; // Passes /// # Ok(()) /// # } /// # fn should_fail_1() -> Result<()> { /// # let bytes: &[u8] = "A string".as_bytes(); -/// verify_that!(bytes, is_utf8_string(eq("Another string")))?; // Fails (inner matcher does not match) +/// verify_that!(bytes, is_utf8_string(eq_str("Another string")))?; // Fails (inner matcher does not match) /// # Ok(()) /// # } /// # fn should_fail_2() -> Result<()> { @@ -48,11 +51,14 @@ use std::{fmt::Debug, marker::PhantomData}; /// # should_fail_1().unwrap_err(); /// # should_fail_2().unwrap_err(); /// ``` -pub fn is_utf8_string<'a, ActualT: AsRef<[u8]> + Debug + 'a, InnerMatcherT>( +/// +/// [`eq`]: crate::matchers::eq +/// [`eq_str`]: crate::matchers::eq_str +pub fn is_utf8_string + Debug, InnerMatcherT>( inner: InnerMatcherT, ) -> impl Matcher where - InnerMatcherT: Matcher, + InnerMatcherT: Matcher, { IsEncodedStringMatcher { inner, phantom: Default::default() } } @@ -62,16 +68,19 @@ struct IsEncodedStringMatcher { phantom: PhantomData, } -impl<'a, ActualT: AsRef<[u8]> + Debug + 'a, InnerMatcherT> Matcher +impl + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher where - InnerMatcherT: Matcher, + InnerMatcherT: Matcher, { type ActualT = ActualT; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { - String::from_utf8(actual.as_ref().to_vec()) - .map(|s| self.inner.matches(&s)) + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + std::str::from_utf8(actual.as_ref()) + .map(|s| self.inner.matches(s)) .unwrap_or(MatcherResult::NoMatch) } @@ -90,10 +99,13 @@ where } } - fn explain_match(&self, actual: &Self::ActualT) -> Description { - match String::from_utf8(actual.as_ref().to_vec()) { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + match std::str::from_utf8(actual.as_ref()) { Ok(s) => { - format!("which is a UTF-8 encoded string {}", self.inner.explain_match(&s)).into() + format!("which is a UTF-8 encoded string {}", self.inner.explain_match(s)).into() } Err(e) => format!("which is not a UTF-8 encoded string: {e}").into(), } @@ -107,62 +119,62 @@ mod tests { #[test] fn matches_string_as_byte_slice() -> Result<()> { - verify_that!("A string".as_bytes(), is_utf8_string(eq("A string"))) + verify_that!("A string".as_bytes(), is_utf8_string(eq_str("A string"))) } #[test] fn matches_string_as_byte_vec() -> Result<()> { - verify_that!("A string".as_bytes().to_vec(), is_utf8_string(eq("A string"))) + verify_that!("A string".as_bytes().to_vec(), is_utf8_string(eq_str("A string"))) } #[test] fn matches_string_with_utf_8_encoded_sequences() -> Result<()> { - verify_that!("äöüÄÖÜ".as_bytes().to_vec(), is_utf8_string(eq("äöüÄÖÜ"))) + verify_that!("äöüÄÖÜ".as_bytes().to_vec(), is_utf8_string(eq_str("äöüÄÖÜ"))) } #[test] fn does_not_match_non_equal_string() -> Result<()> { - verify_that!("äöüÄÖÜ".as_bytes().to_vec(), not(is_utf8_string(eq("A string")))) + verify_that!("äöüÄÖÜ".as_bytes().to_vec(), not(is_utf8_string(eq_str("A string")))) } #[test] fn does_not_match_non_utf_8_encoded_byte_sequence() -> Result<()> { - verify_that!(&[192, 64, 255, 32], not(is_utf8_string(eq("A string")))) + verify_that!(&[192, 64, 255, 32], not(is_utf8_string(eq_str("A string")))) } #[test] fn has_correct_description_in_matched_case() -> Result<()> { - let matcher = is_utf8_string::<&[u8], _>(eq("A string")); + let matcher = is_utf8_string::<&[u8], _>(eq_str("A string")); verify_that!( matcher.describe(MatcherResult::Match), - displays_as(eq("is a UTF-8 encoded string which is equal to \"A string\"")) + displays_as(eq_str("is a UTF-8 encoded string which is equal to \"A string\"")) ) } #[test] fn has_correct_description_in_not_matched_case() -> Result<()> { - let matcher = is_utf8_string::<&[u8], _>(eq("A string")); + let matcher = is_utf8_string::<&[u8], _>(eq_str("A string")); verify_that!( matcher.describe(MatcherResult::NoMatch), - displays_as(eq("is not a UTF-8 encoded string which is equal to \"A string\"")) + displays_as(eq_str("is not a UTF-8 encoded string which is equal to \"A string\"")) ) } #[test] fn has_correct_explanation_in_matched_case() -> Result<()> { - let explanation = is_utf8_string(eq("A string")).explain_match(&"A string".as_bytes()); + let explanation = is_utf8_string(eq_str("A string")).explain_match(&"A string".as_bytes()); verify_that!( explanation, - displays_as(eq("which is a UTF-8 encoded string which is equal to \"A string\"")) + displays_as(eq_str("which is a UTF-8 encoded string which is equal to \"A string\"")) ) } #[test] fn has_correct_explanation_when_byte_array_is_not_utf8_encoded() -> Result<()> { - let explanation = is_utf8_string(eq("A string")).explain_match(&&[192, 128, 0, 64]); + let explanation = is_utf8_string(eq_str("A string")).explain_match(&&[192, 128, 0, 64]); verify_that!(explanation, displays_as(starts_with("which is not a UTF-8 encoded string: "))) } @@ -170,7 +182,7 @@ mod tests { #[test] fn has_correct_explanation_when_inner_matcher_does_not_match() -> Result<()> { let explanation = - is_utf8_string(eq("A string")).explain_match(&"Another string".as_bytes()); + is_utf8_string(eq_str("A string")).explain_match(&"Another string".as_bytes()); verify_that!( explanation, diff --git a/googletest/src/matchers/is_matcher.rs b/googletest/src/matchers/is_matcher.rs index 336ce536..fdaa4348 100644 --- a/googletest/src/matchers/is_matcher.rs +++ b/googletest/src/matchers/is_matcher.rs @@ -18,7 +18,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches precisely values matched by `inner`. /// @@ -43,7 +43,10 @@ impl<'a, ActualT: Debug, InnerMatcherT: Matcher> Matcher { type ActualT = ActualT; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult { self.inner.matches(actual) } @@ -64,7 +67,10 @@ impl<'a, ActualT: Debug, InnerMatcherT: Matcher> Matcher } } - fn explain_match(&self, actual: &Self::ActualT) -> Description { + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { self.inner.explain_match(actual) } } diff --git a/googletest/src/matchers/is_nan_matcher.rs b/googletest/src/matchers/is_nan_matcher.rs index 0af4338d..12a16e6e 100644 --- a/googletest/src/matchers/is_nan_matcher.rs +++ b/googletest/src/matchers/is_nan_matcher.rs @@ -17,7 +17,7 @@ use crate::{ matcher::{Matcher, MatcherResult}, }; use num_traits::float::Float; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a floating point value which is NaN. pub fn is_nan() -> impl Matcher { @@ -29,7 +29,10 @@ struct IsNanMatcher(PhantomData); impl Matcher for IsNanMatcher { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { actual.is_nan().into() } diff --git a/googletest/src/matchers/le_matcher.rs b/googletest/src/matchers/le_matcher.rs index cfc57818..6646f817 100644 --- a/googletest/src/matchers/le_matcher.rs +++ b/googletest/src/matchers/le_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a value less than or equal to (in the sense of `<=`) `expected`. /// @@ -90,7 +90,10 @@ impl, ExpectedT: Debug> Matcher { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { (*actual <= self.expected).into() } diff --git a/googletest/src/matchers/len_matcher.rs b/googletest/src/matchers/len_matcher.rs index be903c99..588f66ce 100644 --- a/googletest/src/matchers/len_matcher.rs +++ b/googletest/src/matchers/len_matcher.rs @@ -15,6 +15,7 @@ use crate::description::Description; use crate::matcher::{Matcher, MatcherResult}; use crate::matcher_support::count_elements::count_elements; +use std::ops::Deref; use std::{fmt::Debug, marker::PhantomData}; /// Matches a container whose number of elements matches `expected`. @@ -67,8 +68,11 @@ where { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { - self.expected.matches(&count_elements(actual)) + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + self.expected.matches(&count_elements(actual.deref())) } fn describe(&self, matcher_result: MatcherResult) -> Description { @@ -83,8 +87,11 @@ where } } - fn explain_match(&self, actual: &T) -> Description { - let actual_size = count_elements(actual); + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + let actual_size = count_elements(actual.deref()); format!("which has length {}, {}", actual_size, self.expected.explain_match(&actual_size)) .into() } @@ -102,6 +109,7 @@ mod tests { }; use std::fmt::Debug; use std::marker::PhantomData; + use std::ops::Deref; #[test] fn len_matcher_match_vec() -> Result<()> { @@ -182,7 +190,10 @@ mod tests { impl Matcher for TestMatcher { type ActualT = T; - fn matches(&self, _: &T) -> MatcherResult { + fn matches>( + &self, + _: ActualRefT, + ) -> MatcherResult { false.into() } @@ -190,7 +201,10 @@ mod tests { "called described".into() } - fn explain_match(&self, _: &T) -> Description { + fn explain_match>( + &self, + _: ActualRefT, + ) -> Description { "called explain_match".into() } } diff --git a/googletest/src/matchers/lt_matcher.rs b/googletest/src/matchers/lt_matcher.rs index 08bc563a..310901cd 100644 --- a/googletest/src/matchers/lt_matcher.rs +++ b/googletest/src/matchers/lt_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a value less (in the sense of `<`) than `expected`. /// @@ -90,7 +90,10 @@ impl, ExpectedT: Debug> Matcher { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { (*actual < self.expected).into() } diff --git a/googletest/src/matchers/matches_regex_matcher.rs b/googletest/src/matchers/matches_regex_matcher.rs index 32b053b4..437214e9 100644 --- a/googletest/src/matchers/matches_regex_matcher.rs +++ b/googletest/src/matchers/matches_regex_matcher.rs @@ -91,7 +91,10 @@ where { type ActualT = ActualT; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { self.regex.is_match(actual.as_ref()).into() } diff --git a/googletest/src/matchers/mod.rs b/googletest/src/matchers/mod.rs index 147c8ab1..e4faf555 100644 --- a/googletest/src/matchers/mod.rs +++ b/googletest/src/matchers/mod.rs @@ -85,7 +85,7 @@ pub use points_to_matcher::points_to; pub use predicate_matcher::{predicate, PredicateMatcher}; pub use some_matcher::some; pub use str_matcher::{ - contains_substring, ends_with, starts_with, StrMatcher, StrMatcherConfigurator, + contains_substring, ends_with, eq_str, starts_with, StrMatcher, StrMatcherConfigurator, }; pub use subset_of_matcher::subset_of; pub use superset_of_matcher::superset_of; diff --git a/googletest/src/matchers/near_matcher.rs b/googletest/src/matchers/near_matcher.rs index ca7cbdf6..cc37e853 100644 --- a/googletest/src/matchers/near_matcher.rs +++ b/googletest/src/matchers/near_matcher.rs @@ -17,7 +17,7 @@ use crate::{ matcher::{Matcher, MatcherResult}, }; use num_traits::{Float, FloatConst}; -use std::fmt::Debug; +use std::{fmt::Debug, ops::Deref}; /// Matches a value equal within `max_abs_error` of `expected`. /// @@ -169,7 +169,10 @@ impl NearMatcher { impl Matcher for NearMatcher { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { if self.nans_are_equal && self.expected.is_nan() && actual.is_nan() { return MatcherResult::Match; } diff --git a/googletest/src/matchers/none_matcher.rs b/googletest/src/matchers/none_matcher.rs index af289327..bfbf6e4b 100644 --- a/googletest/src/matchers/none_matcher.rs +++ b/googletest/src/matchers/none_matcher.rs @@ -16,6 +16,7 @@ use crate::description::Description; use crate::matcher::{Matcher, MatcherResult}; use std::fmt::Debug; use std::marker::PhantomData; +use std::ops::Deref; /// Matches an `Option` containing `None`. /// @@ -43,7 +44,10 @@ struct NoneMatcher { impl Matcher for NoneMatcher { type ActualT = Option; - fn matches(&self, actual: &Option) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { (actual.is_none()).into() } diff --git a/googletest/src/matchers/not_matcher.rs b/googletest/src/matchers/not_matcher.rs index f03d4cec..0d22b689 100644 --- a/googletest/src/matchers/not_matcher.rs +++ b/googletest/src/matchers/not_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches the actual value exactly when the inner matcher does _not_ match. /// @@ -47,14 +47,20 @@ struct NotMatcher { impl> Matcher for NotMatcher { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult { match self.inner.matches(actual) { MatcherResult::Match => MatcherResult::NoMatch, MatcherResult::NoMatch => MatcherResult::Match, } } - fn explain_match(&self, actual: &T) -> Description { + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { self.inner.explain_match(actual) } diff --git a/googletest/src/matchers/ok_matcher.rs b/googletest/src/matchers/ok_matcher.rs index 8425b93a..ab13d82c 100644 --- a/googletest/src/matchers/ok_matcher.rs +++ b/googletest/src/matchers/ok_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a `Result` containing `Ok` with a value matched by `inner`. /// @@ -55,12 +55,18 @@ impl> Matcher { type ActualT = std::result::Result; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch) } - fn explain_match(&self, actual: &Self::ActualT) -> Description { - match actual { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + match actual.deref() { Ok(o) => { Description::new().text("which is a success").nested(self.inner.explain_match(o)) } diff --git a/googletest/src/matchers/points_to_matcher.rs b/googletest/src/matchers/points_to_matcher.rs index 2c516d0c..1ff34cef 100644 --- a/googletest/src/matchers/points_to_matcher.rs +++ b/googletest/src/matchers/points_to_matcher.rs @@ -36,7 +36,7 @@ pub fn points_to( expected: MatcherT, ) -> impl Matcher where - ExpectedT: Debug, + ExpectedT: Debug + ?Sized, MatcherT: Matcher, ActualT: Deref + Debug + ?Sized, { @@ -48,20 +48,26 @@ struct PointsToMatcher { phantom: PhantomData, } -impl Matcher for PointsToMatcher +impl Matcher for PointsToMatcher where - ExpectedT: Debug, - MatcherT: Matcher, - ActualT: Deref + Debug + ?Sized, + ActualDerefT: Debug + ?Sized, + MatcherT: Matcher, + ActualT: Deref + Debug + ?Sized, { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { - self.expected.matches(actual.deref()) + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + self.expected.matches(actual.deref().deref()) } - fn explain_match(&self, actual: &ActualT) -> Description { - self.expected.explain_match(actual.deref()) + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { + self.expected.explain_match(actual.deref().deref()) } fn describe(&self, matcher_result: MatcherResult) -> Description { diff --git a/googletest/src/matchers/pointwise_matcher.rs b/googletest/src/matchers/pointwise_matcher.rs index 01e70c0d..5cf67448 100644 --- a/googletest/src/matchers/pointwise_matcher.rs +++ b/googletest/src/matchers/pointwise_matcher.rs @@ -154,6 +154,7 @@ pub mod internal { use crate::description::Description; use crate::matcher::{Matcher, MatcherResult}; use crate::matcher_support::zipped_iterator::zip; + use std::ops::Deref; use std::{fmt::Debug, marker::PhantomData}; /// This struct is meant to be used only through the `pointwise` macro. @@ -178,7 +179,10 @@ pub mod internal { { type ActualT = ContainerT; - fn matches(&self, actual: &ContainerT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { let mut zipped_iterator = zip(actual.into_iter(), self.matchers.iter()); for (element, matcher) in zipped_iterator.by_ref() { if matcher.matches(element).is_no_match() { @@ -192,7 +196,10 @@ pub mod internal { } } - fn explain_match(&self, actual: &ContainerT) -> Description { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { // TODO(b/260819741) This code duplicates elements_are_matcher.rs. Consider // extract as a separate library. (or implement pointwise! with // elements_are) diff --git a/googletest/src/matchers/predicate_matcher.rs b/googletest/src/matchers/predicate_matcher.rs index 5bc067d7..85073278 100644 --- a/googletest/src/matchers/predicate_matcher.rs +++ b/googletest/src/matchers/predicate_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Creates a matcher based on the predicate provided. /// @@ -130,8 +130,11 @@ where { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { - (self.predicate)(actual).into() + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + (self.predicate)(actual.deref()).into() } fn describe(&self, result: MatcherResult) -> Description { @@ -149,8 +152,11 @@ where { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { - (self.predicate)(actual).into() + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + (self.predicate)(actual.deref()).into() } fn describe(&self, result: MatcherResult) -> Description { diff --git a/googletest/src/matchers/property_matcher.rs b/googletest/src/matchers/property_matcher.rs index 19b48627..744cece2 100644 --- a/googletest/src/matchers/property_matcher.rs +++ b/googletest/src/matchers/property_matcher.rs @@ -78,10 +78,10 @@ /// # .unwrap(); /// ``` /// -/// Unfortunately, this matcher does *not* work with methods returning string -/// slices: +/// When the property returns a string slice and you wish to assert its +/// equality, use [`eq_str`] instead of [`eq`]: /// -/// ```compile_fail +/// ``` /// # use googletest::prelude::*; /// #[derive(Debug)] /// pub struct MyStruct { @@ -92,7 +92,7 @@ /// } /// /// let value = MyStruct { a_string: "A string".into() }; -/// verify_that!(value, property!(*MyStruct.get_a_string(), eq("A string"))) // Does not compile +/// verify_that!(value, property!(*MyStruct.get_a_string(), eq_str("A string"))) /// # .unwrap(); /// ``` /// @@ -101,6 +101,9 @@ /// rather than accessing a field. /// /// The list of arguments may optionally have a trailing comma. +/// +/// [`eq`]: crate::matchers::eq +/// [`eq_str`]: crate::matchers::eq_str #[macro_export] #[doc(hidden)] macro_rules! __property { @@ -138,7 +141,7 @@ pub mod internal { description::Description, matcher::{Matcher, MatcherResult}, }; - use std::{fmt::Debug, marker::PhantomData}; + use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] @@ -166,8 +169,11 @@ pub mod internal { { type ActualT = OuterT; - fn matches(&self, actual: &OuterT) -> MatcherResult { - self.inner.matches(&(self.extractor)(actual)) + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + self.inner.matches(&(self.extractor)(actual.deref())) } fn describe(&self, matcher_result: MatcherResult) -> Description { @@ -179,8 +185,11 @@ pub mod internal { .into() } - fn explain_match(&self, actual: &OuterT) -> Description { - let actual_inner = (self.extractor)(actual); + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + let actual_inner = (self.extractor)(actual.deref()); format!( "whose property `{}` is `{:#?}`, {}", self.property_desc, @@ -217,8 +226,11 @@ pub mod internal { { type ActualT = OuterT; - fn matches(&self, actual: &OuterT) -> MatcherResult { - self.inner.matches((self.extractor)(actual)) + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + self.inner.matches((self.extractor)(actual.deref())) } fn describe(&self, matcher_result: MatcherResult) -> Description { @@ -230,8 +242,11 @@ pub mod internal { .into() } - fn explain_match(&self, actual: &OuterT) -> Description { - let actual_inner = (self.extractor)(actual); + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + let actual_inner = (self.extractor)(actual.deref()); format!( "whose property `{}` is `{:#?}`, {}", self.property_desc, diff --git a/googletest/src/matchers/some_matcher.rs b/googletest/src/matchers/some_matcher.rs index 905aa17d..1b6257fd 100644 --- a/googletest/src/matchers/some_matcher.rs +++ b/googletest/src/matchers/some_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches an `Option` containing a value matched by `inner`. /// @@ -50,12 +50,18 @@ struct SomeMatcher { impl> Matcher for SomeMatcher { type ActualT = Option; - fn matches(&self, actual: &Option) -> MatcherResult { + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult { actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch) } - fn explain_match(&self, actual: &Option) -> Description { - match (self.matches(actual), actual) { + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { + match (self.matches(actual.clone()), actual.deref()) { (_, Some(t)) => { Description::new().text("which has a value").nested(self.inner.explain_match(t)) } diff --git a/googletest/src/matchers/str_matcher.rs b/googletest/src/matchers/str_matcher.rs index f4f158e5..fecc3171 100644 --- a/googletest/src/matchers/str_matcher.rs +++ b/googletest/src/matchers/str_matcher.rs @@ -14,7 +14,9 @@ use crate::{ description::Description, - matcher::{Matcher, MatcherResult}, + matcher::{ + Matcher, MatcherResult, __internal_unstable_do_not_depend_on_these::ObjectSafeMatcher, + }, matcher_support::{ edit_distance, summarize_diff::{create_diff, create_diff_reversed}, @@ -26,6 +28,59 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::ops::Deref; +/// Matches a string equal to the given string. +/// +/// This has the same effect as [`eq`]. Unlike [`eq`], [`eq_str`] can be used +/// in cases where the actual type being matched is `str`. In paticular, one +/// can use [`eq_str`] together with: +/// +/// * [`property!`] where the property returns a string slice, and +/// * [`is_utf8_string`]. +/// +/// ``` +/// # use googletest::prelude::*; +/// # fn should_pass_1() -> Result<()> { +/// verify_that!("Some value", eq_str("Some value"))?; // Passes +/// verify_that!("Some value".to_string(), eq_str("Some value"))?; // Passes +/// # Ok(()) +/// # } +/// # fn should_fail() -> Result<()> { +/// verify_that!("Another value", eq_str("Some value"))?; // Fails +/// # Ok(()) +/// # } +/// # fn should_pass_2() -> Result<()> { +/// let value_as_bytes = "Some value".as_bytes(); +/// verify_that!(value_as_bytes, is_utf8_string(eq_str("Some value")))?; // Passes +/// # Ok(()) +/// # } +/// # should_pass_1().unwrap(); +/// # should_fail().unwrap_err(); +/// # should_pass_2().unwrap(); +/// +/// #[derive(Debug)] +/// pub struct MyStruct { +/// a_string: String, +/// } +/// impl MyStruct { +/// pub fn get_a_string(&self) -> &str { &self.a_string } +/// } +/// +/// let value = MyStruct { a_string: "A string".into() }; +/// verify_that!(value, property!(*MyStruct.get_a_string(), eq_str("A string"))) // Passes +/// # .unwrap(); +/// ``` +/// +/// [`eq`]: crate::matchers::eq +/// [`is_utf8_string`]: crate::matchers::is_utf8_string +/// [`property!`]: crate::matchers::property +pub fn eq_str(expected: T) -> StrMatcher { + StrMatcher { + configuration: Configuration { mode: MatchMode::Equals, ..Default::default() }, + expected, + phantom: Default::default(), + } +} + /// Matches a string containing a given substring. /// /// Both the actual value and the expected substring may be either a `String` or @@ -300,7 +355,10 @@ where { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { self.configuration.do_strings_match(self.expected.deref(), actual.as_ref()).into() } @@ -308,7 +366,10 @@ where self.configuration.describe(matcher_result, self.expected.deref()) } - fn explain_match(&self, actual: &ActualT) -> Description { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { self.configuration.explain_match(self.expected.deref(), actual.as_ref()) } } @@ -387,7 +448,7 @@ struct Configuration { ignore_leading_whitespace: bool, ignore_trailing_whitespace: bool, case_policy: CasePolicy, - times: Option>>, + times: Option>>, } #[derive(Clone)] @@ -461,7 +522,7 @@ impl Configuration { // 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) + matches!(times.obj_matches(&(actual.split(expected).count() - 1)), MatcherResult::Match) } else { actual.contains(expected) } @@ -481,7 +542,7 @@ impl Configuration { 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()); + addenda.push(format!("count {}", times.obj_describe(matcher_result)).into()); } let extra = if !addenda.is_empty() { format!(" ({})", addenda.join(", ")) } else { "".into() }; @@ -729,8 +790,8 @@ mod tests { } #[test] - fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case() - -> Result<()> { + fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case( + ) -> Result<()> { verify_that!("Some string", contains_substring("STR").ignoring_ascii_case()) } @@ -879,8 +940,8 @@ mod tests { } #[test] - fn describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace() - -> Result<()> { + 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(); @@ -1021,8 +1082,8 @@ mod tests { } #[test] - fn match_explanation_for_starts_with_includes_both_versions_of_differing_last_line() - -> Result<()> { + fn match_explanation_for_starts_with_includes_both_versions_of_differing_last_line( + ) -> Result<()> { let result = verify_that!( indoc!( " @@ -1123,8 +1184,8 @@ mod tests { } #[test] - fn match_explanation_for_contains_substring_shows_diff_when_first_and_last_line_are_incomplete() - -> Result<()> { + fn match_explanation_for_contains_substring_shows_diff_when_first_and_last_line_are_incomplete( + ) -> Result<()> { let result = verify_that!( indoc!( " diff --git a/googletest/src/matchers/subset_of_matcher.rs b/googletest/src/matchers/subset_of_matcher.rs index 24c00d80..e9cad31c 100644 --- a/googletest/src/matchers/subset_of_matcher.rs +++ b/googletest/src/matchers/subset_of_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a container all of whose items are in the given container /// `superset`. @@ -106,8 +106,11 @@ where { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { - for actual_item in actual { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + for actual_item in actual.deref() { if self.expected_is_missing(actual_item) { return MatcherResult::NoMatch; } @@ -115,7 +118,10 @@ where MatcherResult::Match } - fn explain_match(&self, actual: &ActualT) -> Description { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { let unexpected_elements = actual .into_iter() .enumerate() diff --git a/googletest/src/matchers/superset_of_matcher.rs b/googletest/src/matchers/superset_of_matcher.rs index d1e9d72a..3927bfd9 100644 --- a/googletest/src/matchers/superset_of_matcher.rs +++ b/googletest/src/matchers/superset_of_matcher.rs @@ -16,7 +16,7 @@ use crate::{ description::Description, matcher::{Matcher, MatcherResult}, }; -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; /// Matches a container containing all of the items in the given container /// `subset`. @@ -107,20 +107,26 @@ where { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { for expected_item in &self.subset { - if actual_is_missing(actual, expected_item) { + if actual_is_missing(actual.deref(), expected_item) { return MatcherResult::NoMatch; } } MatcherResult::Match } - fn explain_match(&self, actual: &ActualT) -> Description { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { let missing_items: Vec<_> = self .subset .into_iter() - .filter(|expected_item| actual_is_missing(actual, expected_item)) + .filter(|expected_item| actual_is_missing(actual.deref(), expected_item)) .map(|expected_item| format!("{expected_item:#?}")) .collect(); match missing_items.len() { diff --git a/googletest/src/matchers/tuple_matcher.rs b/googletest/src/matchers/tuple_matcher.rs index af55cbf6..a8450688 100644 --- a/googletest/src/matchers/tuple_matcher.rs +++ b/googletest/src/matchers/tuple_matcher.rs @@ -25,14 +25,17 @@ pub mod internal { description::Description, matcher::{Matcher, MatcherResult}, }; - use std::fmt::Debug; + use std::{fmt::Debug, ops::Deref}; // This implementation is provided for completeness, but is completely trivial. // The only actual value which can be supplied is (), which must match. impl Matcher for () { type ActualT = (); - fn matches(&self, _: &Self::ActualT) -> MatcherResult { + fn matches>( + &self, + _: ActualRefT, + ) -> MatcherResult { MatcherResult::Match } @@ -55,7 +58,10 @@ pub mod internal { { type ActualT = ($($field_type,)*); - fn matches(&self, actual: &($($field_type,)*)) -> MatcherResult { + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult { $(match self.$field_number.matches(&actual.$field_number) { MatcherResult::Match => {}, MatcherResult::NoMatch => { @@ -65,9 +71,12 @@ pub mod internal { MatcherResult::Match } - fn explain_match(&self, actual: &($($field_type,)*)) -> Description { - let mut explanation = Description::new().text("which").nested(self.describe(self.matches(actual))); - $(match self.$field_number.matches(&actual.$field_number) { + fn explain_match + Clone>( + &self, + actual: ActualRefT, + ) -> Description { + let mut explanation = Description::new().text("which").nested(self.describe(self.matches(actual.clone()))); + $(match self.$field_number.matches(&actual.clone().$field_number) { MatcherResult::Match => {}, MatcherResult::NoMatch => { explanation = explanation diff --git a/googletest/src/matchers/unordered_elements_are_matcher.rs b/googletest/src/matchers/unordered_elements_are_matcher.rs index f4585a43..7dc4972a 100644 --- a/googletest/src/matchers/unordered_elements_are_matcher.rs +++ b/googletest/src/matchers/unordered_elements_are_matcher.rs @@ -366,11 +366,14 @@ macro_rules! __is_contained_in { #[doc(hidden)] pub mod internal { use crate::description::Description; - use crate::matcher::{Matcher, MatcherResult}; + use crate::matcher::{ + Matcher, MatcherResult, __internal_unstable_do_not_depend_on_these::ObjectSafeMatcher, + }; use crate::matcher_support::count_elements::count_elements; use std::collections::HashSet; use std::fmt::{Debug, Display}; use std::marker::PhantomData; + use std::ops::Deref; /// This struct is meant to be used only through the /// `unordered_elements_are![...]` macro. @@ -378,7 +381,7 @@ pub mod internal { /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] pub struct UnorderedElementsAreMatcher<'a, ContainerT: ?Sized, T: Debug, const N: usize> { - elements: [Box + 'a>; N], + elements: [Box + 'a>; N], requirements: Requirements, phantom: PhantomData, } @@ -387,7 +390,7 @@ pub mod internal { UnorderedElementsAreMatcher<'a, ContainerT, T, N> { pub fn new( - elements: [Box + 'a>; N], + elements: [Box + 'a>; N], requirements: Requirements, ) -> Self { Self { elements, requirements, phantom: Default::default() } @@ -410,19 +413,25 @@ pub mod internal { { type ActualT = ContainerT; - fn matches(&self, actual: &ContainerT) -> MatcherResult { - let match_matrix = MatchMatrix::generate(actual, &self.elements); + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + let match_matrix = MatchMatrix::generate(actual.deref(), &self.elements); match_matrix.is_match_for(self.requirements).into() } - fn explain_match(&self, actual: &ContainerT) -> Description { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { if let Some(size_mismatch_explanation) = - self.requirements.explain_size_mismatch(actual, N) + self.requirements.explain_size_mismatch(actual.deref(), N) { return size_mismatch_explanation; } - let match_matrix = MatchMatrix::generate(actual, &self.elements); + let match_matrix = MatchMatrix::generate(actual.deref(), &self.elements); if let Some(unmatchable_explanation) = match_matrix.explain_unmatchable(self.requirements) { @@ -431,7 +440,7 @@ pub mod internal { let best_match = match_matrix.find_best_match(); best_match - .get_explanation(actual, &self.elements, self.requirements) + .get_explanation(actual.deref(), &self.elements, self.requirements) .unwrap_or("whose elements all match".into()) } @@ -441,7 +450,7 @@ pub mod internal { if matcher_result.into() { "contains" } else { "doesn't contain" }, self.elements .iter() - .map(|matcher| matcher.describe(MatcherResult::Match)) + .map(|matcher| matcher.obj_describe(MatcherResult::Match)) .collect::() .enumerate() .indent() @@ -450,8 +459,10 @@ pub mod internal { } } - type KeyValueMatcher<'a, KeyT, ValueT> = - (Box + 'a>, Box + 'a>); + type KeyValueMatcher<'a, KeyT, ValueT> = ( + Box + 'a>, + Box + 'a>, + ); /// This is the analogue to [UnorderedElementsAreMatcher] for maps and /// map-like collections. @@ -487,19 +498,25 @@ pub mod internal { { type ActualT = ContainerT; - fn matches(&self, actual: &ContainerT) -> MatcherResult { - let match_matrix = MatchMatrix::generate_for_map(actual, &self.elements); + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + let match_matrix = MatchMatrix::generate_for_map(actual.deref(), &self.elements); match_matrix.is_match_for(self.requirements).into() } - fn explain_match(&self, actual: &ContainerT) -> Description { + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { if let Some(size_mismatch_explanation) = - self.requirements.explain_size_mismatch(actual, N) + self.requirements.explain_size_mismatch(actual.deref(), N) { return size_mismatch_explanation; } - let match_matrix = MatchMatrix::generate_for_map(actual, &self.elements); + let match_matrix = MatchMatrix::generate_for_map(actual.deref(), &self.elements); if let Some(unmatchable_explanation) = match_matrix.explain_unmatchable(self.requirements) { @@ -509,7 +526,7 @@ pub mod internal { let best_match = match_matrix.find_best_match(); best_match - .get_explanation_for_map(actual, &self.elements, self.requirements) + .get_explanation_for_map(actual.deref(), &self.elements, self.requirements) .unwrap_or("whose elements all match".into()) } @@ -521,8 +538,8 @@ pub mod internal { .iter() .map(|(key_matcher, value_matcher)| format!( "{} => {}", - key_matcher.describe(MatcherResult::Match), - value_matcher.describe(MatcherResult::Match) + key_matcher.obj_describe(MatcherResult::Match), + value_matcher.obj_describe(MatcherResult::Match) )) .collect::() .indent() @@ -603,7 +620,7 @@ pub mod internal { impl MatchMatrix { fn generate<'a, T: Debug + 'a, ContainerT: Debug + ?Sized>( actual: &ContainerT, - expected: &[Box + 'a>; N], + expected: &[Box + 'a>; N], ) -> Self where for<'b> &'b ContainerT: IntoIterator, @@ -611,7 +628,7 @@ pub mod internal { let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; count_elements(actual)]); for (actual_idx, actual) in actual.into_iter().enumerate() { for (expected_idx, expected) in expected.iter().enumerate() { - matrix.0[actual_idx][expected_idx] = expected.matches(actual); + matrix.0[actual_idx][expected_idx] = expected.obj_matches(actual); } } matrix @@ -627,9 +644,10 @@ pub mod internal { let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; count_elements(actual)]); for (actual_idx, (actual_key, actual_value)) in actual.into_iter().enumerate() { for (expected_idx, (expected_key, expected_value)) in expected.iter().enumerate() { - matrix.0[actual_idx][expected_idx] = (expected_key.matches(actual_key).into() - && expected_value.matches(actual_value).into()) - .into(); + matrix.0[actual_idx][expected_idx] = + (expected_key.obj_matches(actual_key).into() + && expected_value.obj_matches(actual_value).into()) + .into(); } } matrix @@ -962,7 +980,7 @@ pub mod internal { fn get_explanation<'a, T: Debug, ContainerT: Debug + ?Sized>( &self, actual: &ContainerT, - expected: &[Box + 'a>; N], + expected: &[Box + 'a>; N], requirements: Requirements, ) -> Option where @@ -981,7 +999,7 @@ pub mod internal { format!( "Actual element {:?} at index {actual_idx} matched expected element `{}` at index {expected_idx}.", actual[actual_idx], - expected[expected_idx].describe(MatcherResult::Match), + expected[expected_idx].obj_describe(MatcherResult::Match), )}); let unmatched_actual = self.get_unmatched_actual().map(|actual_idx| { @@ -993,7 +1011,7 @@ pub mod internal { let unmatched_expected = self.get_unmatched_expected().into_iter().map(|expected_idx|{format!( "Expected element `{}` at index {expected_idx} did not match any remaining actual element.", - expected[expected_idx].describe(MatcherResult::Match) + expected[expected_idx].obj_describe(MatcherResult::Match) )}); let best_match = matches @@ -1030,8 +1048,8 @@ pub mod internal { "Actual element {:?} => {:?} at index {actual_idx} matched expected element `{}` => `{}` at index {expected_idx}.", actual[actual_idx].0, actual[actual_idx].1, - expected[expected_idx].0.describe(MatcherResult::Match), - expected[expected_idx].1.describe(MatcherResult::Match), + expected[expected_idx].0.obj_describe(MatcherResult::Match), + expected[expected_idx].1.obj_describe(MatcherResult::Match), ) }); @@ -1049,8 +1067,8 @@ pub mod internal { .map(|expected_idx| { format!( "Expected element `{}` => `{}` at index {expected_idx} did not match any remaining actual element.", - expected[expected_idx].0.describe(MatcherResult::Match), - expected[expected_idx].1.describe(MatcherResult::Match), + expected[expected_idx].0.obj_describe(MatcherResult::Match), + expected[expected_idx].1.obj_describe(MatcherResult::Match), ) }); @@ -1083,9 +1101,9 @@ mod tests { // aren't dropped too early. let matchers = ((eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))); let matcher: UnorderedElementsOfMapAreMatcher, _, _, 3> = unordered_elements_are![ - (matchers.0.0, matchers.0.1), - (matchers.1.0, matchers.1.1), - (matchers.2.0, matchers.2.1) + (matchers.0 .0, matchers.0 .1), + (matchers.1 .0, matchers.1 .1), + (matchers.2 .0, matchers.2 .1) ]; verify_that!( Matcher::describe(&matcher, MatcherResult::Match), @@ -1108,9 +1126,9 @@ mod tests { // aren't dropped too early. let matchers = ((anything(), eq(1)), (anything(), eq(2)), (anything(), eq(2))); let matcher: UnorderedElementsOfMapAreMatcher, _, _, 3> = unordered_elements_are![ - (matchers.0.0, matchers.0.1), - (matchers.1.0, matchers.1.1), - (matchers.2.0, matchers.2.1), + (matchers.0 .0, matchers.0 .1), + (matchers.1 .0, matchers.1 .1), + (matchers.2 .0, matchers.2 .1), ]; let value: HashMap = HashMap::from_iter([(0, 1), (1, 1), (2, 2)]); verify_that!( diff --git a/googletest/tests/arena_test.rs b/googletest/tests/arena_test.rs new file mode 100644 index 00000000..02646251 --- /dev/null +++ b/googletest/tests/arena_test.rs @@ -0,0 +1,149 @@ +use googletest::{description::Description, matcher::MatcherResult, prelude::*}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; + +#[derive(Debug)] +struct ViewProxy<'a, T: ?Sized> { + value: &'a T, +} + +impl<'a, T> Clone for ViewProxy<'a, T> { + fn clone(&self) -> Self { + Self { value: self.value } + } +} + +#[derive(Debug, PartialEq)] +struct Strukt { + a_field: i32, + a_string: String, +} + +type StruktView<'a> = ViewProxy<'a, Strukt>; + +impl<'a> ViewProxy<'a, Strukt> { + fn get_a_field(&self) -> ViewProxy<'_, i32> { + ViewProxy { value: &self.value.a_field } + } + + #[allow(unused)] + fn get_a_string(&self) -> ViewProxy<'_, str> { + ViewProxy { value: &self.value.a_string } + } +} + +impl<'data, T: ?Sized> Deref for ViewProxy<'data, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value + } +} + +impl<'data, T> AsRef for ViewProxy<'data, T> { + fn as_ref(&self) -> &T { + self.value + } +} + +#[allow(unused)] +fn property_matcher< + OuterT: Debug + Clone, + InnerT: Debug + Clone, + InnerRefT: Deref + Clone, + MatcherT: Matcher, +>( + extractor: impl Fn(OuterT) -> InnerRefT, + property_desc: &'static str, + inner: MatcherT, +) -> impl Matcher { + PropertyMatcher { extractor, property_desc, inner, phantom: Default::default() } +} + +struct PropertyMatcher { + extractor: ExtractorT, + property_desc: &'static str, + inner: MatcherT, + phantom: PhantomData, +} + +impl Matcher + for PropertyMatcher +where + InnerT: Debug + Clone, + InnerRefT: Deref + Clone, + OuterT: Debug + Clone, + ExtractorT: Fn(OuterT) -> InnerRefT, + MatcherT: Matcher, +{ + type ActualT = OuterT; + + fn matches>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + self.inner.matches((self.extractor)(actual.deref().clone())) + } + + fn describe(&self, matcher_result: MatcherResult) -> Description { + format!( + "has property `{}`, which {}", + self.property_desc, + self.inner.describe(matcher_result) + ) + .into() + } + + fn explain_match>( + &self, + actual: ActualRefT, + ) -> Description { + let actual_inner = (self.extractor)(actual.deref().clone()); + format!( + "whose property `{}` is `{:#?}`, {}", + self.property_desc, + actual_inner.clone().deref(), + self.inner.explain_match(actual_inner) + ) + .into() + } +} + +fn has_a_field<'actual>( + inner: impl Matcher, +) -> impl Matcher> { + struct HasAFieldMatcher<'actual, InnerMatcherT: Matcher> { + inner: InnerMatcherT, + phantom: PhantomData<&'actual ()>, + } + + impl<'actual, InnerMatcherT: Matcher> Matcher + for HasAFieldMatcher<'actual, InnerMatcherT> + { + type ActualT = StruktView<'actual>; + + fn matches + Clone>( + &self, + actual: ActualRefT, + ) -> MatcherResult { + self.inner.matches(actual.get_a_field()) + } + + fn describe(&self, _: MatcherResult) -> Description { + todo!() + } + } + + HasAFieldMatcher { inner, phantom: Default::default() } +} + +#[test] +fn check() -> Result<()> { + let arena = vec![Strukt { a_field: 33, a_string: "something".to_string() }]; + let holder = ViewProxy { value: &arena[0] }; + + // Problem: This does not compile because get_a_field() constraints the lifetime of its + // returned ViewProxy to that of the parameter v, which is local to the closure. + // verify_that!(holder, property_matcher(|v: StruktView| v.get_a_field(), "a_field", eq(33)))?; + verify_that!(holder, has_a_field(eq(33)))?; + Ok(()) +} diff --git a/googletest/tests/lib.rs b/googletest/tests/lib.rs index fb243caa..3e0bbfcf 100644 --- a/googletest/tests/lib.rs +++ b/googletest/tests/lib.rs @@ -14,6 +14,7 @@ mod all_matcher_test; mod any_matcher_test; +mod arena_test; mod colorized_diff_test; mod composition_test; mod elements_are_matcher_test; diff --git a/googletest/tests/property_matcher_test.rs b/googletest/tests/property_matcher_test.rs index 7092446b..5c560cfa 100644 --- a/googletest/tests/property_matcher_test.rs +++ b/googletest/tests/property_matcher_test.rs @@ -85,6 +85,21 @@ fn matches_struct_with_matching_string_reference_property() -> Result<()> { verify_that!(value, property!(*StructWithString.get_property_ref(), eq("Something"))) } +#[test] +fn matches_struct_with_matching_string_slice_property() -> Result<()> { + #[derive(Debug)] + struct StructWithString { + property: String, + } + impl StructWithString { + fn get_property_ref(&self) -> &str { + &self.property + } + } + let value = StructWithString { property: "Something".into() }; + verify_that!(value, property!(*StructWithString.get_property_ref(), eq_str("Something"))) +} + #[test] fn matches_struct_with_matching_slice_property() -> Result<()> { #[derive(Debug)]