diff --git a/googletest/src/matchers/contains_matcher.rs b/googletest/src/matchers/contains_matcher.rs index 1a27ce04..34585e57 100644 --- a/googletest/src/matchers/contains_matcher.rs +++ b/googletest/src/matchers/contains_matcher.rs @@ -43,19 +43,26 @@ use std::{fmt::Debug, marker::PhantomData}; /// # should_fail_1().unwrap_err(); /// # should_fail_2().unwrap_err(); /// ``` -pub fn contains(inner: InnerMatcherT) -> ContainsMatcher { - ContainsMatcher { inner, count: None, phantom: Default::default() } +pub fn contains( + inner: InnerMatcherT, +) -> ContainsMatcher { + ContainsMatcher { inner, count: NoCountMatcher, phantom: Default::default() } } /// A matcher which matches a container containing one or more elements a given /// inner [`Matcher`] matches. -pub struct ContainsMatcher { +pub struct ContainsMatcher { inner: InnerMatcherT, - count: Option>>, + count: CountMatcher, phantom: PhantomData, } -impl ContainsMatcher { +// Sentinel type to tag a `ContainsMatcher` as without a `CountMatcher`. +#[doc(hidden)] + +pub struct NoCountMatcher; + +impl ContainsMatcher { /// Configures this instance to match containers which contain a number of /// matching items matched by `count`. /// @@ -68,79 +75,91 @@ impl ContainsMatcher { /// /// One can also use `times(eq(0))` to test for the *absence* of an item /// matching the expected value. - pub fn times(mut self, count: impl Matcher + 'static) -> Self { - self.count = Some(Box::new(count)); - self + pub fn times>( + self, + count: CountMatcher, + ) -> ContainsMatcher { + ContainsMatcher { inner: self.inner, count, phantom: self.phantom } } } -// TODO(hovinen): Revisit the trait bounds to see whether this can be made more -// flexible. Namely, the following doesn't compile currently: -// -// let matcher = contains(eq(&42)); -// let val = 42; -// let _ = matcher.matches(&vec![&val]); -// -// because val is dropped before matcher but the trait bound requires that -// the argument to matches outlive the matcher. It works fine if one defines -// val before matcher. impl, ContainerT: Debug> Matcher - for ContainsMatcher + for ContainsMatcher where for<'a> &'a ContainerT: IntoIterator, { type ActualT = ContainerT; fn matches(&self, actual: &Self::ActualT) -> MatcherResult { - if let Some(count) = &self.count { - count.matches(&self.count_matches(actual)) - } else { - for v in actual.into_iter() { - if self.inner.matches(v).into() { - return MatcherResult::Match; - } + for v in actual.into_iter() { + if self.inner.matches(v).into() { + return MatcherResult::Match; } - MatcherResult::NoMatch } + MatcherResult::NoMatch } + // TODO use the inner matcher to produce a better error message. fn explain_match(&self, actual: &Self::ActualT) -> Description { - let count = self.count_matches(actual); - match (count, &self.count) { - (_, Some(_)) => format!("which contains {} matching elements", count).into(), - (0, None) => "which does not contain a matching element".into(), - (_, None) => "which contains a matching element".into(), + match self.count_matches(actual) { + 0 => "which does not contain a matching element".into(), + _ => "which contains a matching element".into(), } } fn describe(&self, matcher_result: MatcherResult) -> Description { - match (matcher_result, &self.count) { - (MatcherResult::Match, Some(count)) => format!( + match matcher_result { + MatcherResult::Match => format!( + "contains at least one element which {}", + self.inner.describe(MatcherResult::Match) + ) + .into(), + MatcherResult::NoMatch => { + format!("contains no element which {}", self.inner.describe(MatcherResult::Match)) + .into() + } + } + } +} + +impl< + T: Debug, + InnerMatcherT: Matcher, + ContainerT: Debug, + CountMatcher: Matcher, +> Matcher for ContainsMatcher +where + for<'a> &'a ContainerT: IntoIterator, +{ + type ActualT = ContainerT; + + fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + self.count.matches(&self.count_matches(actual)) + } + + fn explain_match(&self, actual: &Self::ActualT) -> Description { + format!("which contains {} matching elements", self.count_matches(actual)).into() + } + + fn describe(&self, matcher_result: MatcherResult) -> Description { + match matcher_result { + MatcherResult::Match => format!( "contains n elements which {}\n where n {}", self.inner.describe(MatcherResult::Match), - count.describe(MatcherResult::Match) + self.count.describe(MatcherResult::Match) ) .into(), - (MatcherResult::NoMatch, Some(count)) => format!( + MatcherResult::NoMatch => format!( "doesn't contain n elements which {}\n where n {}", self.inner.describe(MatcherResult::Match), - count.describe(MatcherResult::Match) - ) - .into(), - (MatcherResult::Match, None) => format!( - "contains at least one element which {}", - self.inner.describe(MatcherResult::Match) + self.count.describe(MatcherResult::Match) ) .into(), - (MatcherResult::NoMatch, None) => { - format!("contains no element which {}", self.inner.describe(MatcherResult::Match)) - .into() - } } } } -impl ContainsMatcher { +impl ContainsMatcher { fn count_matches(&self, actual: &ContainerT) -> usize where for<'b> &'b ContainerT: IntoIterator, @@ -236,7 +255,7 @@ mod tests { #[test] fn contains_formats_without_multiplicity_by_default() -> Result<()> { - let matcher: ContainsMatcher, _> = contains(eq(1)); + let matcher: ContainsMatcher, _, _> = contains(eq(1)); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), @@ -246,7 +265,7 @@ mod tests { #[test] fn contains_formats_with_multiplicity_when_specified() -> Result<()> { - let matcher: ContainsMatcher, _> = contains(eq(1)).times(eq(2)); + let matcher: ContainsMatcher, _, _> = contains(eq(1)).times(eq(2)); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), @@ -277,4 +296,12 @@ mod tests { displays_as(eq("which does not contain a matching element")) ) } + + #[test] + fn fix_todo() -> Result<()> { + let matcher = contains(eq(&42)); + let val = 42; + let result = matcher.matches(&vec![&val]); + verify_that!(result, eq(MatcherResult::Match)) + } }