use std::fmt;
use crate::description::Description;
use crate::matcher::{MatchResult, Matcher, Mismatch};
struct NotMatcher<M> {
inner: M,
}
impl<T, M> Matcher<T> for NotMatcher<M>
where
T: ?Sized + fmt::Debug,
M: Matcher<T>,
{
fn check(&self, actual: &T) -> MatchResult {
if self.inner.check(actual).matched {
MatchResult::fail(Mismatch::new(self.description(), format!("{actual:?}")))
} else {
MatchResult::pass()
}
}
fn description(&self) -> Description {
!self.inner.description()
}
}
#[must_use]
pub fn not<T, M>(matcher: M) -> impl Matcher<T>
where
T: ?Sized + fmt::Debug,
M: Matcher<T>,
{
NotMatcher { inner: matcher }
}
pub trait MatcherTuple<T: ?Sized> {
fn check_all(&self, actual: &T) -> MatchResult;
fn check_any(&self, actual: &T) -> MatchResult;
fn describe_all(&self) -> Description;
fn describe_any(&self) -> Description;
}
macro_rules! impl_matcher_tuple {
($first:ident, $($rest:ident),+) => {
#[allow(non_snake_case)]
impl<T, $first, $($rest,)+> MatcherTuple<T> for ($first, $($rest,)+)
where
T: ?Sized,
$first: Matcher<T>,
$($rest: Matcher<T>,)+
{
fn check_all(&self, actual: &T) -> MatchResult {
let ($first, $($rest,)+) = self;
if let Some(mismatch) = $first.check(actual).failure {
return MatchResult::fail(mismatch);
}
$(
if let Some(mismatch) = $rest.check(actual).failure {
return MatchResult::fail(mismatch);
}
)+
MatchResult::pass()
}
fn check_any(&self, actual: &T) -> MatchResult {
let ($first, $($rest,)+) = self;
let first_actual = match $first.check(actual).failure {
None => return MatchResult::pass(),
Some(mismatch) => mismatch.actual,
};
$(
if $rest.check(actual).matched {
return MatchResult::pass();
}
)+
MatchResult::fail(Mismatch::new(self.describe_any(), first_actual))
}
fn describe_all(&self) -> Description {
let ($first, $($rest,)+) = self;
let desc = $first.description();
$( let desc = desc.and($rest.description()); )+
desc
}
fn describe_any(&self) -> Description {
let ($first, $($rest,)+) = self;
let desc = $first.description();
$( let desc = desc.or($rest.description()); )+
desc
}
}
};
}
impl_matcher_tuple!(M1, M2);
impl_matcher_tuple!(M1, M2, M3);
impl_matcher_tuple!(M1, M2, M3, M4);
impl_matcher_tuple!(M1, M2, M3, M4, M5);
impl_matcher_tuple!(M1, M2, M3, M4, M5, M6);
impl_matcher_tuple!(M1, M2, M3, M4, M5, M6, M7);
impl_matcher_tuple!(M1, M2, M3, M4, M5, M6, M7, M8);
struct AllOfMatcher<Tup> {
matchers: Tup,
}
impl<T, Tup> Matcher<T> for AllOfMatcher<Tup>
where
T: ?Sized,
Tup: MatcherTuple<T>,
{
fn check(&self, actual: &T) -> MatchResult {
self.matchers.check_all(actual)
}
fn description(&self) -> Description {
self.matchers.describe_all()
}
}
#[must_use]
pub fn all_of<T, Tup>(matchers: Tup) -> impl Matcher<T>
where
T: ?Sized,
Tup: MatcherTuple<T>,
{
AllOfMatcher { matchers }
}
struct AnyOfMatcher<Tup> {
matchers: Tup,
}
impl<T, Tup> Matcher<T> for AnyOfMatcher<Tup>
where
T: ?Sized,
Tup: MatcherTuple<T>,
{
fn check(&self, actual: &T) -> MatchResult {
self.matchers.check_any(actual)
}
fn description(&self) -> Description {
self.matchers.describe_any()
}
}
#[must_use]
pub fn any_of<T, Tup>(matchers: Tup) -> impl Matcher<T>
where
T: ?Sized,
Tup: MatcherTuple<T>,
{
AnyOfMatcher { matchers }
}
#[cfg(test)]
mod tests {
use test_better_core::{OrFail, TestResult};
use super::*;
use crate::{check, eq, gt, is_false, is_true, lt};
#[test]
fn not_inverts_the_inner_matcher() -> TestResult {
check!(not(eq(4)).check(&5).matched).satisfies(is_true())?;
check!(not(eq(4)).check(&4).matched).satisfies(is_false())?;
Ok(())
}
#[test]
fn not_failure_negates_the_description_and_renders_the_actual() -> TestResult {
let failure = not(eq(4))
.check(&4)
.failure
.or_fail_with("4 does match eq(4), so not(eq(4)) fails")?;
check!(failure.expected.to_string()).satisfies(eq("not equal to 4".to_string()))?;
check!(failure.actual).satisfies(eq("4".to_string()))?;
Ok(())
}
#[test]
fn all_of_passes_when_every_matcher_matches() -> TestResult {
check!(all_of((gt(0), lt(100))).check(&50).matched).satisfies(is_true())?;
Ok(())
}
#[test]
fn all_of_fails_with_the_first_failing_sub_matcher() -> TestResult {
let failure = all_of((gt(0), lt(100)))
.check(&150)
.failure
.or_fail_with("150 is not less than 100")?;
check!(failure.expected.to_string()).satisfies(eq("less than 100".to_string()))?;
check!(failure.actual).satisfies(eq("150".to_string()))?;
Ok(())
}
#[test]
fn all_of_describes_itself_as_a_conjunction() -> TestResult {
let description = all_of((gt(0), lt(100))).description();
check!(description.to_string())
.satisfies(eq("greater than 0 and less than 100".to_string()))?;
Ok(())
}
#[test]
fn any_of_passes_when_at_least_one_matcher_matches() -> TestResult {
check!(any_of((eq(7), eq(8), eq(9))).check(&8).matched).satisfies(is_true())?;
Ok(())
}
#[test]
fn any_of_fails_when_no_matcher_matches() -> TestResult {
let failure = any_of((eq(7), eq(8), eq(9)))
.check(&1)
.failure
.or_fail_with("1 is none of 7, 8, 9")?;
check!(failure.expected.to_string())
.satisfies(eq("equal to 7 or equal to 8 or equal to 9".to_string()))?;
check!(failure.actual).satisfies(eq("1".to_string()))?;
Ok(())
}
#[test]
fn combinators_nest() -> TestResult {
check!(all_of((not(eq(0)), lt(100))).check(&50).matched).satisfies(is_true())?;
check!(all_of((not(eq(0)), lt(100))).check(&0).matched).satisfies(is_false())?;
Ok(())
}
#[test]
fn all_of_supports_arity_eight() -> TestResult {
let matcher = all_of((gt(0), lt(100), gt(1), lt(99), gt(2), lt(98), gt(3), lt(97)));
check!(matcher.check(&50).matched).satisfies(is_true())?;
Ok(())
}
}