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