test_better_matchers/
primitives.rs1use std::fmt;
8
9use crate::description::Description;
10use crate::matcher::{MatchResult, Matcher, Mismatch};
11
12macro_rules! comparison_matcher {
16 ($matcher:ident, $bound:ident, $op:tt, $describe:literal) => {
17 struct $matcher<T> {
18 expected: T,
19 }
20
21 impl<T> Matcher<T> for $matcher<T>
22 where
23 T: $bound + fmt::Debug,
24 {
25 fn check(&self, actual: &T) -> MatchResult {
26 if *actual $op self.expected {
27 MatchResult::pass()
28 } else {
29 MatchResult::fail(Mismatch::new(self.description(), format!("{actual:?}")))
30 }
31 }
32
33 fn description(&self) -> Description {
34 Description::text(format!(concat!($describe, " {:?}"), self.expected))
35 }
36 }
37 };
38}
39
40comparison_matcher!(NeMatcher, PartialEq, !=, "not equal to");
41comparison_matcher!(LtMatcher, PartialOrd, <, "less than");
42comparison_matcher!(LeMatcher, PartialOrd, <=, "less than or equal to");
43comparison_matcher!(GtMatcher, PartialOrd, >, "greater than");
44comparison_matcher!(GeMatcher, PartialOrd, >=, "greater than or equal to");
45
46struct EqMatcher<T> {
51 expected: T,
52}
53
54impl<T> Matcher<T> for EqMatcher<T>
55where
56 T: PartialEq + fmt::Debug,
57{
58 fn check(&self, actual: &T) -> MatchResult {
59 if *actual == self.expected {
60 return MatchResult::pass();
61 }
62 let mut mismatch = Mismatch::new(self.description(), format!("{actual:?}"));
63 if let Some(diff) =
64 multi_line_diff(&format!("{:#?}", self.expected), &format!("{actual:#?}"))
65 {
66 mismatch = mismatch.with_diff(diff);
67 }
68 MatchResult::fail(mismatch)
69 }
70
71 fn description(&self) -> Description {
72 Description::text(format!("equal to {:?}", self.expected))
73 }
74}
75
76#[cfg(feature = "diff")]
83fn multi_line_diff(expected: &str, actual: &str) -> Option<String> {
84 if expected.contains('\n') || actual.contains('\n') {
85 Some(crate::diff::diff_lines(expected, actual))
86 } else {
87 None
88 }
89}
90
91#[cfg(not(feature = "diff"))]
92fn multi_line_diff(_expected: &str, _actual: &str) -> Option<String> {
93 None
94}
95
96#[must_use]
112pub fn eq<T>(expected: T) -> impl Matcher<T>
113where
114 T: PartialEq + fmt::Debug,
115{
116 EqMatcher { expected }
117}
118
119#[must_use]
132pub fn ne<T>(expected: T) -> impl Matcher<T>
133where
134 T: PartialEq + fmt::Debug,
135{
136 NeMatcher { expected }
137}
138
139#[must_use]
152pub fn lt<T>(expected: T) -> impl Matcher<T>
153where
154 T: PartialOrd + fmt::Debug,
155{
156 LtMatcher { expected }
157}
158
159#[must_use]
172pub fn le<T>(expected: T) -> impl Matcher<T>
173where
174 T: PartialOrd + fmt::Debug,
175{
176 LeMatcher { expected }
177}
178
179#[must_use]
192pub fn gt<T>(expected: T) -> impl Matcher<T>
193where
194 T: PartialOrd + fmt::Debug,
195{
196 GtMatcher { expected }
197}
198
199#[must_use]
212pub fn ge<T>(expected: T) -> impl Matcher<T>
213where
214 T: PartialOrd + fmt::Debug,
215{
216 GeMatcher { expected }
217}
218
219struct BoolMatcher {
221 expected: bool,
222}
223
224impl Matcher<bool> for BoolMatcher {
225 fn check(&self, actual: &bool) -> MatchResult {
226 if *actual == self.expected {
227 MatchResult::pass()
228 } else {
229 MatchResult::fail(Mismatch::new(self.description(), format!("{actual:?}")))
230 }
231 }
232
233 fn description(&self) -> Description {
234 Description::text(if self.expected { "true" } else { "false" })
235 }
236}
237
238#[must_use]
251pub fn is_true() -> impl Matcher<bool> {
252 BoolMatcher { expected: true }
253}
254
255#[must_use]
268pub fn is_false() -> impl Matcher<bool> {
269 BoolMatcher { expected: false }
270}
271
272#[cfg(test)]
273mod tests {
274 use test_better_core::{OrFail, TestResult};
275
276 use super::*;
277 use crate::{check, eq, is_true};
278
279 #[test]
280 fn eq_passes_and_fails_with_rendered_mismatch() -> TestResult {
281 check!(eq(4).check(&4).matched).satisfies(is_true())?;
282 let failure = eq(4).check(&5).failure.or_fail_with("5 is not 4")?;
283 check!(failure.expected.to_string()).satisfies(eq("equal to 4".to_string()))?;
284 check!(failure.actual).satisfies(eq("5".to_string()))?;
285 Ok(())
286 }
287
288 #[test]
289 fn eq_omits_a_diff_for_single_line_values() -> TestResult {
290 let failure = eq(4).check(&5).failure.or_fail_with("5 is not 4")?;
291 check!(failure.diff.is_none()).satisfies(is_true())?;
292 Ok(())
293 }
294
295 #[cfg(feature = "diff")]
296 #[test]
297 fn eq_attaches_a_diff_when_the_pretty_repr_is_multi_line() -> TestResult {
298 let failure = eq(vec![1, 2, 3])
299 .check(&vec![1, 2, 4])
300 .failure
301 .or_fail_with("the vectors differ")?;
302 let diff = failure
303 .diff
304 .or_fail_with("multi-line pretty reprs get a diff")?;
305 check!(diff.contains("- 3,")).satisfies(is_true())?;
306 check!(diff.contains("+ 4,")).satisfies(is_true())?;
307 Ok(())
308 }
309
310 #[test]
311 fn ne_passes_and_fails_with_rendered_mismatch() -> TestResult {
312 check!(ne(4).check(&5).matched).satisfies(is_true())?;
313 let failure = ne(4).check(&4).failure.or_fail_with("4 is equal to 4")?;
314 check!(failure.expected.to_string()).satisfies(eq("not equal to 4".to_string()))?;
315 check!(failure.actual).satisfies(eq("4".to_string()))?;
316 Ok(())
317 }
318
319 #[test]
320 fn lt_passes_and_fails_with_rendered_mismatch() -> TestResult {
321 check!(lt(10).check(&9).matched).satisfies(is_true())?;
322 let failure = lt(10).check(&10).failure.or_fail_with("10 is not < 10")?;
323 check!(failure.expected.to_string()).satisfies(eq("less than 10".to_string()))?;
324 check!(failure.actual).satisfies(eq("10".to_string()))?;
325 Ok(())
326 }
327
328 #[test]
329 fn le_passes_and_fails_with_rendered_mismatch() -> TestResult {
330 check!(le(10).check(&10).matched).satisfies(is_true())?;
331 let failure = le(10).check(&11).failure.or_fail_with("11 is not <= 10")?;
332 check!(failure.expected.to_string())
333 .satisfies(eq("less than or equal to 10".to_string()))?;
334 check!(failure.actual).satisfies(eq("11".to_string()))?;
335 Ok(())
336 }
337
338 #[test]
339 fn gt_passes_and_fails_with_rendered_mismatch() -> TestResult {
340 check!(gt(0).check(&1).matched).satisfies(is_true())?;
341 let failure = gt(0).check(&0).failure.or_fail_with("0 is not > 0")?;
342 check!(failure.expected.to_string()).satisfies(eq("greater than 0".to_string()))?;
343 check!(failure.actual).satisfies(eq("0".to_string()))?;
344 Ok(())
345 }
346
347 #[test]
348 fn ge_passes_and_fails_with_rendered_mismatch() -> TestResult {
349 check!(ge(0).check(&0).matched).satisfies(is_true())?;
350 let failure = ge(0).check(&-1).failure.or_fail_with("-1 is not >= 0")?;
351 check!(failure.expected.to_string())
352 .satisfies(eq("greater than or equal to 0".to_string()))?;
353 check!(failure.actual).satisfies(eq("-1".to_string()))?;
354 Ok(())
355 }
356
357 #[test]
358 fn is_true_passes_and_fails_with_rendered_mismatch() -> TestResult {
359 check!(is_true().check(&true).matched).satisfies(is_true())?;
360 let failure = is_true()
361 .check(&false)
362 .failure
363 .or_fail_with("false is not true")?;
364 check!(failure.expected.to_string()).satisfies(eq("true".to_string()))?;
365 check!(failure.actual).satisfies(eq("false".to_string()))?;
366 Ok(())
367 }
368
369 #[test]
370 fn is_false_passes_and_fails_with_rendered_mismatch() -> TestResult {
371 check!(is_false().check(&false).matched).satisfies(is_true())?;
372 let failure = is_false()
373 .check(&true)
374 .failure
375 .or_fail_with("true is not false")?;
376 check!(failure.expected.to_string()).satisfies(eq("false".to_string()))?;
377 check!(failure.actual).satisfies(eq("true".to_string()))?;
378 Ok(())
379 }
380}