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 use crate::{
16     description::Description,
17     matcher::{Matcher, MatcherResult},
18 };
19 use std::{fmt::Debug, marker::PhantomData};
20 
21 /// Creates a matcher based on the predicate provided.
22 ///
23 /// ```
24 /// # use googletest::prelude::*;
25 /// # fn should_pass() -> Result<()> {
26 /// verify_that!(3, predicate(|x: &i32| x % 2 == 1))?;  // Passes
27 /// #     Ok(())
28 /// # }
29 /// # should_pass().unwrap();
30 /// ```
31 ///
32 /// The predicate should take the subject type by reference and return a
33 /// boolean.
34 ///
35 /// Note: even if the Rust compiler should be able to infer the type of
36 /// the closure argument, it is likely that it won't.
37 /// See <https://github.com/rust-lang/rust/issues/12679> for update on this issue.
38 /// This is easily fixed by explicitly declaring the type of the argument
predicate<T: Debug + ?Sized, P>( predicate: P, ) -> PredicateMatcher<T, P, NoDescription, NoDescription> where for<'a> P: Fn(&'a T) -> bool,39 pub fn predicate<T: Debug + ?Sized, P>(
40     predicate: P,
41 ) -> PredicateMatcher<T, P, NoDescription, NoDescription>
42 where
43     for<'a> P: Fn(&'a T) -> bool,
44 {
45     PredicateMatcher {
46         predicate,
47         positive_description: NoDescription,
48         negative_description: NoDescription,
49         phantom: Default::default(),
50     }
51 }
52 
53 impl<T, P> PredicateMatcher<T, P, NoDescription, NoDescription> {
54     /// Configures this instance to provide a more meaningful description.
55     ///
56     /// For example, to make sure the error message is more useful
57     ///
58     /// ```
59     /// # use googletest::matchers::{predicate, PredicateMatcher};
60     /// # let _ =
61     /// predicate(|x: &i32| x % 2 == 1)
62     ///     .with_description("is odd", "is even")
63     /// # ;
64     /// ```
65     ///
66     /// This is optional as it only provides value when the test fails.
67     ///
68     /// Description can be passed by `&str`, `String` or `Fn() -> Into<String>`.
with_description<D1: PredicateDescription, D2: PredicateDescription>( self, positive_description: D1, negative_description: D2, ) -> PredicateMatcher<T, P, D1, D2>69     pub fn with_description<D1: PredicateDescription, D2: PredicateDescription>(
70         self,
71         positive_description: D1,
72         negative_description: D2,
73     ) -> PredicateMatcher<T, P, D1, D2> {
74         PredicateMatcher {
75             predicate: self.predicate,
76             positive_description,
77             negative_description,
78             phantom: Default::default(),
79         }
80     }
81 }
82 
83 /// A matcher which applies `predicate` on the value.
84 ///
85 /// See [`predicate`].
86 pub struct PredicateMatcher<T: ?Sized, P, D1, D2> {
87     predicate: P,
88     positive_description: D1,
89     negative_description: D2,
90     phantom: PhantomData<T>,
91 }
92 
93 /// A trait to allow [`PredicateMatcher::with_description`] to accept multiple
94 /// types.
95 ///
96 /// See [`PredicateMatcher::with_description`]
97 pub trait PredicateDescription {
to_description(&self) -> Description98     fn to_description(&self) -> Description;
99 }
100 
101 impl PredicateDescription for &str {
to_description(&self) -> Description102     fn to_description(&self) -> Description {
103         self.to_string().into()
104     }
105 }
106 
107 impl PredicateDescription for String {
to_description(&self) -> Description108     fn to_description(&self) -> Description {
109         self.to_string().into()
110     }
111 }
112 
113 impl<T, S> PredicateDescription for T
114 where
115     T: Fn() -> S,
116     S: Into<String>,
117 {
to_description(&self) -> Description118     fn to_description(&self) -> Description {
119         self().into().into()
120     }
121 }
122 
123 // Sentinel type to tag a MatcherBuilder as without a description.
124 #[doc(hidden)]
125 pub struct NoDescription;
126 
127 impl<T: Debug, P> Matcher for PredicateMatcher<T, P, NoDescription, NoDescription>
128 where
129     for<'a> P: Fn(&'a T) -> bool,
130 {
131     type ActualT = T;
132 
matches(&self, actual: &T) -> MatcherResult133     fn matches(&self, actual: &T) -> MatcherResult {
134         (self.predicate)(actual).into()
135     }
136 
describe(&self, result: MatcherResult) -> Description137     fn describe(&self, result: MatcherResult) -> Description {
138         match result {
139             MatcherResult::Match => "matches".into(),
140             MatcherResult::NoMatch => "does not match".into(),
141         }
142     }
143 }
144 
145 impl<T: Debug, P, D1: PredicateDescription, D2: PredicateDescription> Matcher
146     for PredicateMatcher<T, P, D1, D2>
147 where
148     for<'a> P: Fn(&'a T) -> bool,
149 {
150     type ActualT = T;
151 
matches(&self, actual: &T) -> MatcherResult152     fn matches(&self, actual: &T) -> MatcherResult {
153         (self.predicate)(actual).into()
154     }
155 
describe(&self, result: MatcherResult) -> Description156     fn describe(&self, result: MatcherResult) -> Description {
157         match result {
158             MatcherResult::Match => self.positive_description.to_description(),
159             MatcherResult::NoMatch => self.negative_description.to_description(),
160         }
161     }
162 }
163 
164 #[cfg(test)]
165 mod tests {
166     use super::predicate;
167     use crate::matcher::Matcher;
168     use crate::prelude::*;
169 
170     // Simple matcher with a description
is_odd() -> impl Matcher<ActualT = i32>171     fn is_odd() -> impl Matcher<ActualT = i32> {
172         predicate(|x| x % 2 == 1).with_description("is odd", "is even")
173     }
174 
175     #[test]
predicate_matcher_odd() -> Result<()>176     fn predicate_matcher_odd() -> Result<()> {
177         verify_that!(1, is_odd())
178     }
179 
180     #[test]
predicate_matcher_odd_explain_match_matches() -> Result<()>181     fn predicate_matcher_odd_explain_match_matches() -> Result<()> {
182         verify_that!(is_odd().explain_match(&1), displays_as(eq("which is odd")))
183     }
184 
185     #[test]
predicate_matcher_odd_explain_match_does_not_match() -> Result<()>186     fn predicate_matcher_odd_explain_match_does_not_match() -> Result<()> {
187         verify_that!(is_odd().explain_match(&2), displays_as(eq("which is even")))
188     }
189 
190     // Simple Matcher without description
is_even() -> impl Matcher<ActualT = i32>191     fn is_even() -> impl Matcher<ActualT = i32> {
192         predicate(|x| x % 2 == 0)
193     }
194 
195     #[test]
predicate_matcher_even() -> Result<()>196     fn predicate_matcher_even() -> Result<()> {
197         verify_that!(2, is_even())
198     }
199 
200     #[test]
predicate_matcher_even_explain_match_matches() -> Result<()>201     fn predicate_matcher_even_explain_match_matches() -> Result<()> {
202         verify_that!(is_even().explain_match(&2), displays_as(eq("which matches")))
203     }
204 
205     #[test]
predicate_matcher_even_explain_match_does_not_match() -> Result<()>206     fn predicate_matcher_even_explain_match_does_not_match() -> Result<()> {
207         verify_that!(is_even().explain_match(&1), displays_as(eq("which does not match")))
208     }
209 
210     #[test]
predicate_matcher_generator_lambda() -> Result<()>211     fn predicate_matcher_generator_lambda() -> Result<()> {
212         let is_divisible_by = |quotient| {
213             predicate(move |x: &i32| x % quotient == 0).with_description(
214                 move || format!("is divisible by {quotient}"),
215                 move || format!("is not divisible by {quotient}"),
216             )
217         };
218         verify_that!(49, is_divisible_by(7))
219     }
220 
221     #[test]
predicate_matcher_inline() -> Result<()>222     fn predicate_matcher_inline() -> Result<()> {
223         verify_that!(2048, predicate(|x: &i32| x.count_ones() == 1))
224     }
225 
226     #[test]
predicate_matcher_function_pointer() -> Result<()>227     fn predicate_matcher_function_pointer() -> Result<()> {
228         use std::time::Duration;
229         verify_that!(Duration::new(0, 0), predicate(Duration::is_zero))
230     }
231 }
232