Skip to main content

test_that/matchers/
predicate_matcher.rs

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