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