1 // Copyright 2023 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 matchers module. 17 #![doc(hidden)] 18 19 /// Matches a value which at least one 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", any!(starts_with("A"), ends_with("string")))?; // Passes 30 /// verify_that!("A string", any!(starts_with("A"), starts_with("string")))?; // Passes 31 /// verify_that!("A string", any!(ends_with("A"), ends_with("string")))?; // Passes 32 /// # Ok(()) 33 /// # } 34 /// # fn should_fail() -> Result<()> { 35 /// verify_that!("A string", any!(starts_with("An"), ends_with("not a string")))?; // Fails 36 /// # Ok(()) 37 /// # } 38 /// # should_pass().unwrap(); 39 /// # should_fail().unwrap_err(); 40 /// ``` 41 /// 42 /// Using this macro is equivalent to using the 43 /// [`or`][crate::matcher::Matcher::or] method: 44 /// 45 /// ``` 46 /// # use googletest::prelude::*; 47 /// # fn should_pass() -> Result<()> { 48 /// verify_that!(10, gt(9).or(lt(8)))?; // Also passes 49 /// # Ok(()) 50 /// # } 51 /// # should_pass().unwrap(); 52 /// ``` 53 /// 54 /// Assertion failure messages are not guaranteed to be identical, however. 55 #[macro_export] 56 #[doc(hidden)] 57 macro_rules! __any { 58 ($($matcher:expr),* $(,)?) => {{ 59 use $crate::matchers::__internal_unstable_do_not_depend_on_these::AnyMatcher; 60 AnyMatcher::new([$(Box::new($matcher)),*]) 61 }} 62 } 63 64 /// Functionality needed by the [`any`] macro. 65 /// 66 /// For internal use only. API stablility is not guaranteed! 67 #[doc(hidden)] 68 pub mod internal { 69 use crate::description::Description; 70 use crate::matcher::{Matcher, MatcherResult}; 71 use crate::matchers::anything; 72 use std::fmt::Debug; 73 74 /// A matcher which matches an input value matched by all matchers in the 75 /// array `components`. 76 /// 77 /// For internal use only. API stablility is not guaranteed! 78 #[doc(hidden)] 79 pub struct AnyMatcher<'a, T: Debug + ?Sized, const N: usize> { 80 components: [Box<dyn Matcher<ActualT = T> + 'a>; N], 81 } 82 83 impl<'a, T: Debug + ?Sized, const N: usize> AnyMatcher<'a, T, N> { 84 /// Constructs an [`AnyMatcher`] with the given component matchers. 85 /// 86 /// Intended for use only by the [`all`] macro. new(components: [Box<dyn Matcher<ActualT = T> + 'a>; N]) -> Self87 pub fn new(components: [Box<dyn Matcher<ActualT = T> + 'a>; N]) -> Self { 88 Self { components } 89 } 90 } 91 92 impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AnyMatcher<'a, T, N> { 93 type ActualT = T; 94 matches(&self, actual: &Self::ActualT) -> MatcherResult95 fn matches(&self, actual: &Self::ActualT) -> MatcherResult { 96 MatcherResult::from(self.components.iter().any(|c| c.matches(actual).is_match())) 97 } 98 explain_match(&self, actual: &Self::ActualT) -> Description99 fn explain_match(&self, actual: &Self::ActualT) -> Description { 100 match N { 101 0 => format!("which {}", anything::<T>().describe(MatcherResult::NoMatch)).into(), 102 1 => self.components[0].explain_match(actual), 103 _ => { 104 let failures = self 105 .components 106 .iter() 107 .filter(|component| component.matches(actual).is_no_match()) 108 .collect::<Vec<_>>(); 109 110 if failures.len() == 1 { 111 failures[0].explain_match(actual) 112 } else { 113 Description::new() 114 .collect( 115 failures 116 .into_iter() 117 .map(|component| component.explain_match(actual)), 118 ) 119 .bullet_list() 120 } 121 } 122 } 123 } 124 describe(&self, matcher_result: MatcherResult) -> Description125 fn describe(&self, matcher_result: MatcherResult) -> Description { 126 match N { 127 0 => anything::<T>().describe(matcher_result), 128 1 => self.components[0].describe(matcher_result), 129 _ => { 130 let properties = self 131 .components 132 .iter() 133 .map(|m| m.describe(matcher_result)) 134 .collect::<Description>() 135 .bullet_list() 136 .indent(); 137 format!( 138 "{}:\n{properties}", 139 if matcher_result.into() { 140 "has at least one of the following properties" 141 } else { 142 "has none of the following properties" 143 } 144 ) 145 .into() 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::AnyMatcher<String, 2> = any!(first_matcher, second_matcher); 164 165 verify_that!( 166 matcher.describe(MatcherResult::Match), 167 displays_as(eq(indoc!( 168 " 169 has at least one of 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::AnyMatcher<String, 1> = any!(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::AnyMatcher<str, 2> = any!(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_constituent() -> Result<()>201 fn mismatch_description_is_simple_when_only_one_constituent() -> Result<()> { 202 let first_matcher = starts_with("Another"); 203 let matcher: internal::AnyMatcher<str, 1> = any!(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