test_that/matchers/
predicate_matcher.rs1use crate::{
17 description::Description,
18 matcher::{Describable, Matcher, MatcherResult},
19};
20use alloc::string::{String, ToString};
21use core::{fmt::Debug, marker::PhantomData};
22
23pub 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 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
85pub struct PredicateMatcher<T: ?Sized, P, D1, D2> {
89 predicate: P,
90 positive_description: D1,
91 negative_description: D2,
92 phantom: PhantomData<T>,
93}
94
95pub 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 #[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 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 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}