// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // There are no visible documentation elements in this module; the declarative // macro is documented in the matcher module. #![doc(hidden)] /// Matches a value which all of the given matchers match. /// /// Each argument is a [`Matcher`][crate::matcher::Matcher] which matches /// against the actual value. /// /// For example: /// /// ``` /// # use googletest::prelude::*; /// # fn should_pass() -> Result<()> { /// verify_that!("A string", all!(starts_with("A"), ends_with("string")))?; // Passes /// # Ok(()) /// # } /// # fn should_fail() -> Result<()> { /// verify_that!("A string", all!(starts_with("A"), ends_with("not a string")))?; // Fails /// # Ok(()) /// # } /// # should_pass().unwrap(); /// # should_fail().unwrap_err(); /// ``` /// /// Using this macro is equivalent to using the /// [`and`][crate::matcher::Matcher::and] method: /// /// ``` /// # use googletest::prelude::*; /// # fn should_pass() -> Result<()> { /// verify_that!(10, gt(9).and(lt(11)))?; // Also passes /// # Ok(()) /// # } /// # should_pass().unwrap(); /// ``` /// /// Assertion failure messages are not guaranteed to be identical, however. #[macro_export] #[doc(hidden)] macro_rules! __all { ($($matcher:expr),* $(,)?) => {{ use $crate::matchers::__internal_unstable_do_not_depend_on_these::AllMatcher; AllMatcher::new([$(Box::new($matcher)),*]) }} } /// Functionality needed by the [`all`] macro. /// /// For internal use only. API stablility is not guaranteed! #[doc(hidden)] pub mod internal { use crate::description::Description; use crate::matcher::{Matcher, MatcherResult}; use crate::matchers::anything; use std::fmt::Debug; /// A matcher which matches an input value matched by all matchers in the /// array `components`. /// /// For internal use only. API stablility is not guaranteed! #[doc(hidden)] pub struct AllMatcher<'a, T: Debug + ?Sized, const N: usize> { components: [Box + 'a>; N], } impl<'a, T: Debug + ?Sized, const N: usize> AllMatcher<'a, T, N> { /// Constructs an [`AllMatcher`] with the given component matchers. /// /// Intended for use only by the [`all`] macro. pub fn new(components: [Box + 'a>; N]) -> Self { Self { components } } } impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AllMatcher<'a, T, N> { type ActualT = T; fn matches(&self, actual: &Self::ActualT) -> MatcherResult { for component in &self.components { match component.matches(actual) { MatcherResult::NoMatch => { return MatcherResult::NoMatch; } MatcherResult::Match => {} } } MatcherResult::Match } fn explain_match(&self, actual: &Self::ActualT) -> Description { match N { 0 => anything::().explain_match(actual), 1 => self.components[0].explain_match(actual), _ => { let failures = self .components .iter() .filter(|component| component.matches(actual).is_no_match()) .collect::>(); if failures.len() == 1 { failures[0].explain_match(actual) } else { Description::new() .collect( failures .into_iter() .map(|component| component.explain_match(actual)), ) .bullet_list() } } } } fn describe(&self, matcher_result: MatcherResult) -> Description { match N { 0 => anything::().describe(matcher_result), 1 => self.components[0].describe(matcher_result), _ => { let header = if matcher_result.into() { "has all the following properties:" } else { "has at least one of the following properties:" }; Description::new().text(header).nested( Description::new() .bullet_list() .collect(self.components.iter().map(|m| m.describe(matcher_result))), ) } } } } } #[cfg(test)] mod tests { use super::internal; use crate::matcher::{Matcher, MatcherResult}; use crate::prelude::*; use indoc::indoc; #[test] fn description_shows_more_than_one_matcher() -> Result<()> { let first_matcher = starts_with("A"); let second_matcher = ends_with("string"); let matcher: internal::AllMatcher = all!(first_matcher, second_matcher); verify_that!( matcher.describe(MatcherResult::Match), displays_as(eq(indoc!( " has all the following properties: * starts with prefix \"A\" * ends with suffix \"string\"" ))) ) } #[test] fn description_shows_one_matcher_directly() -> Result<()> { let first_matcher = starts_with("A"); let matcher: internal::AllMatcher = all!(first_matcher); verify_that!( matcher.describe(MatcherResult::Match), displays_as(eq("starts with prefix \"A\"")) ) } #[test] fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent() -> Result<()> { let first_matcher = starts_with("Another"); let second_matcher = ends_with("string"); let matcher: internal::AllMatcher = all!(first_matcher, second_matcher); verify_that!( matcher.explain_match("A string"), displays_as(eq("which does not start with \"Another\"")) ) } #[test] fn mismatch_description_is_simple_when_only_one_consistuent() -> Result<()> { let first_matcher = starts_with("Another"); let matcher: internal::AllMatcher = all!(first_matcher); verify_that!( matcher.explain_match("A string"), displays_as(eq("which does not start with \"Another\"")) ) } }