Skip to main content

googletest/matchers/
predicate_matcher.rs

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
15use crate::{
16    description::Description,
17    matcher::{Matcher, MatcherBase, MatcherResult},
18};
19use std::fmt::Debug;
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 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
39pub fn predicate<P>(predicate: P) -> PredicateMatcher<P, NoDescription, NoDescription> {
40    PredicateMatcher {
41        predicate,
42        positive_description: NoDescription,
43        negative_description: NoDescription,
44    }
45}
46
47impl<P> PredicateMatcher<P, NoDescription, NoDescription> {
48    /// Configures this instance to provide a more meaningful description.
49    ///
50    /// For example, to make sure the error message is more useful
51    ///
52    /// ```
53    /// # use googletest::matchers::{predicate, PredicateMatcher};
54    /// # let _ =
55    /// predicate(|x: i32| x % 2 == 1)
56    ///     .with_description("is odd", "is even")
57    /// # ;
58    /// ```
59    ///
60    /// This is optional as it only provides value when the test fails.
61    ///
62    /// Description can be passed by `&str`, `String` or `Fn() -> Into<String>`.
63    pub fn with_description<D1: PredicateDescription, D2: PredicateDescription>(
64        self,
65        positive_description: D1,
66        negative_description: D2,
67    ) -> PredicateMatcher<P, D1, D2> {
68        PredicateMatcher { predicate: self.predicate, positive_description, negative_description }
69    }
70}
71
72/// A matcher which applies `predicate` on the value.
73///
74/// See [`predicate`].
75#[derive(MatcherBase)]
76pub struct PredicateMatcher<P, D1, D2> {
77    predicate: P,
78    positive_description: D1,
79    negative_description: D2,
80}
81
82/// A trait to allow [`PredicateMatcher::with_description`] to accept multiple
83/// types.
84///
85/// See [`PredicateMatcher::with_description`]
86pub trait PredicateDescription {
87    fn to_description(&self) -> Description;
88}
89
90impl PredicateDescription for &str {
91    fn to_description(&self) -> Description {
92        self.to_string().into()
93    }
94}
95
96impl PredicateDescription for String {
97    fn to_description(&self) -> Description {
98        self.to_string().into()
99    }
100}
101
102impl<T, S> PredicateDescription for T
103where
104    T: Fn() -> S,
105    S: Into<String>,
106{
107    fn to_description(&self) -> Description {
108        self().into().into()
109    }
110}
111
112// Sentinel type to tag a MatcherBuilder as without a description.
113#[doc(hidden)]
114pub struct NoDescription;
115
116impl<T: Debug + Copy, P> Matcher<T> for PredicateMatcher<P, NoDescription, NoDescription>
117where
118    P: Fn(T) -> bool,
119{
120    fn matches(&self, actual: T) -> MatcherResult {
121        (self.predicate)(actual).into()
122    }
123
124    fn describe(&self, result: MatcherResult) -> Description {
125        match result {
126            MatcherResult::Match => "matches".into(),
127            MatcherResult::NoMatch => "does not match".into(),
128        }
129    }
130}
131
132impl<T: Debug + Copy, P, D1: PredicateDescription, D2: PredicateDescription> Matcher<T>
133    for PredicateMatcher<P, D1, D2>
134where
135    P: Fn(T) -> bool,
136{
137    fn matches(&self, actual: T) -> MatcherResult {
138        (self.predicate)(actual).into()
139    }
140
141    fn describe(&self, result: MatcherResult) -> Description {
142        match result {
143            MatcherResult::Match => self.positive_description.to_description(),
144            MatcherResult::NoMatch => self.negative_description.to_description(),
145        }
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use crate::prelude::*;
152    use crate::Result;
153
154    // Simple matcher with a description
155    fn is_odd() -> impl Matcher<i32> {
156        predicate(|x| x % 2 == 1).with_description("is odd", "is even")
157    }
158
159    #[test]
160    fn predicate_matcher_odd() -> Result<()> {
161        verify_that!(1, is_odd())
162    }
163
164    #[test]
165    fn predicate_matcher_odd_explain_match_matches() -> Result<()> {
166        verify_that!(is_odd().explain_match(1), displays_as(eq("which is odd")))
167    }
168
169    #[test]
170    fn predicate_matcher_odd_explain_match_does_not_match() -> Result<()> {
171        verify_that!(is_odd().explain_match(2), displays_as(eq("which is even")))
172    }
173
174    // Simple Matcher without description
175    fn is_even() -> impl Matcher<i32> {
176        predicate(|x| x % 2 == 0)
177    }
178
179    #[test]
180    fn predicate_matcher_even() -> Result<()> {
181        verify_that!(2, is_even())
182    }
183
184    #[test]
185    fn predicate_matcher_even_explain_match_matches() -> Result<()> {
186        verify_that!(is_even().explain_match(2), displays_as(eq("which matches")))
187    }
188
189    #[test]
190    fn predicate_matcher_even_explain_match_does_not_match() -> Result<()> {
191        verify_that!(is_even().explain_match(1), displays_as(eq("which does not match")))
192    }
193
194    #[test]
195    fn predicate_matcher_generator_lambda() -> Result<()> {
196        let is_divisible_by = |quotient| {
197            predicate(move |x: i32| x % quotient == 0).with_description(
198                move || format!("is divisible by {quotient}"),
199                move || format!("is not divisible by {quotient}"),
200            )
201        };
202        verify_that!(49, is_divisible_by(7))
203    }
204
205    #[test]
206    fn predicate_matcher_inline() -> Result<()> {
207        verify_that!(2048, predicate(|x: i32| x.count_ones() == 1))
208    }
209
210    #[test]
211    fn predicate_matcher_function_pointer() -> Result<()> {
212        use std::time::Duration;
213        verify_that!(&Duration::new(0, 0), predicate(Duration::is_zero))
214    }
215}