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