Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parameterize Matcher::matches and Matcher::explain_match. #369

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions googletest/crate_docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,15 @@ 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. |
| [`gt`] | A [`PartialOrd`] value strictly greater than the given value. |
| [`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. |
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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<T> {
expected: T,
Expand All @@ -219,7 +223,10 @@ struct MyEqMatcher<T> {
impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
type ActualT = T;

fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
fn matches<ActualRefT: Deref<Target = Self::ActualT>>(
&self,
actual: ActualRefT,
) -> MatcherResult {
if self.expected == *actual {
MatcherResult::Match
} else {
Expand All @@ -244,7 +251,7 @@ impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {

```no_run
# use googletest::{description::Description, matcher::{Matcher, MatcherResult}};
# use std::fmt::Debug;
# use std::{fmt::Debug, ops::Deref};
#
# struct MyEqMatcher<T> {
# expected: T,
Expand All @@ -253,7 +260,10 @@ impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
# impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
# type ActualT = T;
#
# fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
# fn matches<ActualRefT: Deref<Target = Self::ActualT>>(
# &self,
# actual: ActualRefT,
# ) -> MatcherResult {
# if self.expected == *actual {
# MatcherResult::Match
# } else {
Expand Down Expand Up @@ -283,7 +293,7 @@ impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
```
# use googletest::prelude::*;
# use googletest::{description::Description, matcher::{Matcher, MatcherResult}};
# use std::fmt::Debug;
# use std::{fmt::Debug, ops::Deref};
#
# struct MyEqMatcher<T> {
# expected: T,
Expand All @@ -292,7 +302,10 @@ impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
# impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
# type ActualT = T;
#
# fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
# fn matches<ActualRefT: Deref<Target = Self::ActualT>>(
# &self,
# actual: ActualRefT,
# ) -> MatcherResult {
# if self.expected == *actual {
# MatcherResult::Match
# } else {
Expand Down
71 changes: 64 additions & 7 deletions googletest/src/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -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<ActualRefT: Deref<Target = Self::ActualT> + Clone>(
&self,
actual: ActualRefT,
) -> MatcherResult;

/// Returns a description of `self` or a negative description if
/// `matcher_result` is `DoesNotMatch`.
Expand Down Expand Up @@ -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<ActualRefT: Deref<Target = Self::ActualT> + Clone>(
&self,
actual: ActualRefT,
) -> Description {
format!("which {}", self.describe(self.matches(actual))).into()
}

Expand Down Expand Up @@ -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<MatcherT: Matcher> ObjectSafeMatcher for MatcherT {
type ActualT = <Self as Matcher>::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;
Expand Down Expand Up @@ -249,7 +296,11 @@ pub enum MatcherResult {

impl From<bool> for MatcherResult {
fn from(b: bool) -> Self {
if b { MatcherResult::Match } else { MatcherResult::NoMatch }
if b {
MatcherResult::Match
} else {
MatcherResult::NoMatch
}
}
}

Expand All @@ -276,16 +327,22 @@ impl MatcherResult {
impl<M: Matcher> Matcher for &M {
type ActualT = M::ActualT;

fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
(*self).matches(actual)
fn matches<ActualRefT: Deref<Target = Self::ActualT>>(
&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<ActualRefT: Deref<Target = Self::ActualT>>(
&self,
actual: ActualRefT,
) -> Description {
(*self).explain_match(actual.deref())
}
}

Expand Down
4 changes: 2 additions & 2 deletions googletest/src/matchers/anything_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -41,7 +41,7 @@ struct Anything<T: ?Sized>(PhantomData<T>);
impl<T: Debug + ?Sized> Matcher for Anything<T> {
type ActualT = T;

fn matches(&self, _: &T) -> MatcherResult {
fn matches<ActualRefT: Deref<Target = Self::ActualT>>(&self, _: ActualRefT) -> MatcherResult {
MatcherResult::Match
}

Expand Down
23 changes: 18 additions & 5 deletions googletest/src/matchers/char_count_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
///
Expand Down Expand Up @@ -70,7 +70,10 @@ struct CharLenMatcher<T: ?Sized, E> {
impl<T: Debug + ?Sized + AsRef<str>, E: Matcher<ActualT = usize>> Matcher for CharLenMatcher<T, E> {
type ActualT = T;

fn matches(&self, actual: &T) -> MatcherResult {
fn matches<ActualRefT: Deref<Target = Self::ActualT>>(
&self,
actual: ActualRefT,
) -> MatcherResult {
self.expected.matches(&actual.as_ref().chars().count())
}

Expand All @@ -89,7 +92,10 @@ impl<T: Debug + ?Sized + AsRef<str>, E: Matcher<ActualT = usize>> Matcher for Ch
}
}

fn explain_match(&self, actual: &T) -> Description {
fn explain_match<ActualRefT: Deref<Target = Self::ActualT>>(
&self,
actual: ActualRefT,
) -> Description {
let actual_size = actual.as_ref().chars().count();
format!(
"which has character count {}, {}",
Expand All @@ -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<()> {
Expand All @@ -134,15 +141,21 @@ mod tests {
impl<T: Debug> Matcher for TestMatcher<T> {
type ActualT = T;

fn matches(&self, _: &T) -> MatcherResult {
fn matches<ActualRefT: Deref<Target = Self::ActualT>>(
&self,
_: ActualRefT,
) -> MatcherResult {
false.into()
}

fn describe(&self, _: MatcherResult) -> Description {
"called described".into()
}

fn explain_match(&self, _: &T) -> Description {
fn explain_match<ActualRefT: Deref<Target = Self::ActualT>>(
&self,
_: ActualRefT,
) -> Description {
"called explain_match".into()
}
}
Expand Down
26 changes: 16 additions & 10 deletions googletest/src/matchers/conjunction_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!`].
///
Expand Down Expand Up @@ -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<ActualRefT: Deref<Target = Self::ActualT> + 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<ActualRefT: Deref<Target = Self::ActualT> + 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()
}
}
Expand Down
Loading
Loading