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