1 // Copyright 2022 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // There are no visible documentation elements in this module; the declarative 16 // macro is documented in the matcher module. 17 #![doc(hidden)] 18 19 /// Matches a value which all of the given matchers match. 20 /// 21 /// Each argument is a [`Matcher`][crate::matcher::Matcher] which matches 22 /// against the actual value. 23 /// 24 /// For example: 25 /// 26 /// ``` 27 /// # use googletest::prelude::*; 28 /// # fn should_pass() -> Result<()> { 29 /// verify_that!("A string", all!(starts_with("A"), ends_with("string")))?; // Passes 30 /// # Ok(()) 31 /// # } 32 /// # fn should_fail() -> Result<()> { 33 /// verify_that!("A string", all!(starts_with("A"), ends_with("not a string")))?; // Fails 34 /// # Ok(()) 35 /// # } 36 /// # should_pass().unwrap(); 37 /// # should_fail().unwrap_err(); 38 /// ``` 39 /// 40 /// Using this macro is equivalent to using the 41 /// [`and`][crate::matcher::Matcher::and] method: 42 /// 43 /// ``` 44 /// # use googletest::prelude::*; 45 /// # fn should_pass() -> Result<()> { 46 /// verify_that!(10, gt(9).and(lt(11)))?; // Also passes 47 /// # Ok(()) 48 /// # } 49 /// # should_pass().unwrap(); 50 /// ``` 51 /// 52 /// Assertion failure messages are not guaranteed to be identical, however. 53 #[macro_export] 54 #[doc(hidden)] 55 macro_rules! __all { 56 ($($matcher:expr),* $(,)?) => {{ 57 use $crate::matchers::__internal_unstable_do_not_depend_on_these::AllMatcher; 58 AllMatcher::new([$(Box::new($matcher)),*]) 59 }} 60 } 61 62 /// Functionality needed by the [`all`] macro. 63 /// 64 /// For internal use only. API stablility is not guaranteed! 65 #[doc(hidden)] 66 pub mod internal { 67 use crate::description::Description; 68 use crate::matcher::{Matcher, MatcherResult}; 69 use crate::matchers::anything; 70 use std::fmt::Debug; 71 72 /// A matcher which matches an input value matched by all matchers in the 73 /// array `components`. 74 /// 75 /// For internal use only. API stablility is not guaranteed! 76 #[doc(hidden)] 77 pub struct AllMatcher<'a, T: Debug + ?Sized, const N: usize> { 78 components: [Box<dyn Matcher<ActualT = T> + 'a>; N], 79 } 80 81 impl<'a, T: Debug + ?Sized, const N: usize> AllMatcher<'a, T, N> { 82 /// Constructs an [`AllMatcher`] with the given component matchers. 83 /// 84 /// Intended for use only by the [`all`] macro. new(components: [Box<dyn Matcher<ActualT = T> + 'a>; N]) -> Self85 pub fn new(components: [Box<dyn Matcher<ActualT = T> + 'a>; N]) -> Self { 86 Self { components } 87 } 88 } 89 90 impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AllMatcher<'a, T, N> { 91 type ActualT = T; 92 matches(&self, actual: &Self::ActualT) -> MatcherResult93 fn matches(&self, actual: &Self::ActualT) -> MatcherResult { 94 for component in &self.components { 95 match component.matches(actual) { 96 MatcherResult::NoMatch => { 97 return MatcherResult::NoMatch; 98 } 99 MatcherResult::Match => {} 100 } 101 } 102 MatcherResult::Match 103 } 104 explain_match(&self, actual: &Self::ActualT) -> Description105 fn explain_match(&self, actual: &Self::ActualT) -> Description { 106 match N { 107 0 => anything::<T>().explain_match(actual), 108 1 => self.components[0].explain_match(actual), 109 _ => { 110 let failures = self 111 .components 112 .iter() 113 .filter(|component| component.matches(actual).is_no_match()) 114 .collect::<Vec<_>>(); 115 116 if failures.len() == 1 { 117 failures[0].explain_match(actual) 118 } else { 119 Description::new() 120 .collect( 121 failures 122 .into_iter() 123 .map(|component| component.explain_match(actual)), 124 ) 125 .bullet_list() 126 } 127 } 128 } 129 } 130 describe(&self, matcher_result: MatcherResult) -> Description131 fn describe(&self, matcher_result: MatcherResult) -> Description { 132 match N { 133 0 => anything::<T>().describe(matcher_result), 134 1 => self.components[0].describe(matcher_result), 135 _ => { 136 let header = if matcher_result.into() { 137 "has all the following properties:" 138 } else { 139 "has at least one of the following properties:" 140 }; 141 Description::new().text(header).nested( 142 Description::new() 143 .bullet_list() 144 .collect(self.components.iter().map(|m| m.describe(matcher_result))), 145 ) 146 } 147 } 148 } 149 } 150 } 151 152 #[cfg(test)] 153 mod tests { 154 use super::internal; 155 use crate::matcher::{Matcher, MatcherResult}; 156 use crate::prelude::*; 157 use indoc::indoc; 158 159 #[test] description_shows_more_than_one_matcher() -> Result<()>160 fn description_shows_more_than_one_matcher() -> Result<()> { 161 let first_matcher = starts_with("A"); 162 let second_matcher = ends_with("string"); 163 let matcher: internal::AllMatcher<String, 2> = all!(first_matcher, second_matcher); 164 165 verify_that!( 166 matcher.describe(MatcherResult::Match), 167 displays_as(eq(indoc!( 168 " 169 has all the following properties: 170 * starts with prefix \"A\" 171 * ends with suffix \"string\"" 172 ))) 173 ) 174 } 175 176 #[test] description_shows_one_matcher_directly() -> Result<()>177 fn description_shows_one_matcher_directly() -> Result<()> { 178 let first_matcher = starts_with("A"); 179 let matcher: internal::AllMatcher<String, 1> = all!(first_matcher); 180 181 verify_that!( 182 matcher.describe(MatcherResult::Match), 183 displays_as(eq("starts with prefix \"A\"")) 184 ) 185 } 186 187 #[test] mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent() -> Result<()>188 fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent() -> Result<()> 189 { 190 let first_matcher = starts_with("Another"); 191 let second_matcher = ends_with("string"); 192 let matcher: internal::AllMatcher<str, 2> = all!(first_matcher, second_matcher); 193 194 verify_that!( 195 matcher.explain_match("A string"), 196 displays_as(eq("which does not start with \"Another\"")) 197 ) 198 } 199 200 #[test] mismatch_description_is_simple_when_only_one_consistuent() -> Result<()>201 fn mismatch_description_is_simple_when_only_one_consistuent() -> Result<()> { 202 let first_matcher = starts_with("Another"); 203 let matcher: internal::AllMatcher<str, 1> = all!(first_matcher); 204 205 verify_that!( 206 matcher.explain_match("A string"), 207 displays_as(eq("which does not start with \"Another\"")) 208 ) 209 } 210 } 211