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