test_that/matcher.rs
1// Copyright 2022 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
16//! The components required to implement matchers.
17
18use crate::description::Description;
19use crate::internal::source_location::SourceLocation;
20use crate::internal::test_outcome::TestAssertionFailure;
21use crate::matchers::__internal::ConjunctionMatcher;
22use crate::matchers::__internal::DisjunctionMatcher;
23use std::fmt::Debug;
24
25/// Represents an arbitrary condition on data of the given type which can be
26/// checked to perform an assertion.
27///
28/// Matchers are the core of the assertion language of Test That!. They can be
29/// combined and composed to assert on complex data structures. The variety
30/// of available matchers allows precise specification of the intent of the
31/// assertion.
32///
33/// Matchers can be logically combined with the [`and`] and [`or`] methods as
34/// well as the [`not`] matcher.
35///
36/// This trait is implemented for tuples of up to twelve arbitrary
37/// implementations of `Matcher`. So one can match tuples of up to twelve
38/// items using corresponding tuple of matchers.
39///
40/// ```rust
41/// # use test_that::prelude::*;
42/// let value = (1, "Hello, world");
43/// assert_that!(value, (eq(1), ends_with("world")));
44/// ```
45///
46/// Tuples of more than twelve items do not automatically inherit the `Debug`
47/// trait from their members, so are generally not supported; see
48/// [Rust by Example](https://doc.rust-lang.org/rust-by-example/primitives/tuples.html#tuples).
49///
50/// [`and`]: crate::matcher::MatcherExt::and
51/// [`or`]: crate::matcher::MatcherExt::or
52/// [`not`]: crate::matchers::not
53pub trait Matcher<ActualT: Debug + ?Sized>: Describable {
54 /// Returns whether the condition matches the datum `actual`.
55 ///
56 /// The trait implementation defines what it means to "match". Often the
57 /// matching condition is based on data stored in the matcher. For example,
58 /// `eq` matches when its stored expected value is equal (in the sense of
59 /// the `==` operator) to the value `actual`.
60 fn matches(&self, actual: &ActualT) -> MatcherResult;
61
62 /// Prepares a [`String`] describing how the expected value
63 /// encoded in this instance matches or does not match the given value
64 /// `actual`.
65 ///
66 /// This should be in the form of a relative clause, i.e. something starting
67 /// with a relative pronoun such as "which" or "whose". It will appear next
68 /// to the actual value in an assertion failure. For example:
69 ///
70 /// ```text
71 /// Value of: ...
72 /// Expected: ...
73 /// Actual: ["Something"], which does not contain "Something else"
74 /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
75 /// ```
76 ///
77 /// The default implementation relies on
78 /// [`describe`][Describable::describe]. Thus it does not make any use
79 /// of the actual value itself, but rather only whether the value is
80 /// matched.
81 ///
82 /// Override the default implementation to provide additional context on why
83 /// a particular value matched or did not match. For example, the
84 /// [`container_eq`][crate::matchers::containers::container_eq] matcher
85 /// displays information on which elements of the actual value were not
86 /// present in the expected value and vice versa.
87 ///
88 /// This implementation should be overridden in any matcher which contains
89 /// one or more inner matchers. The implementation should invoke
90 /// `explain_match` on the inner matchers, so that the generated match
91 /// explanation also reflects their implementation. Without this, the match
92 /// explanation of the inner matchers will not be able to make use of the
93 /// actual value at all.
94 ///
95 /// For example, the `explain_match` implementation of the matcher
96 /// [`points_to`][crate::matchers::points_to] defers immediately to the
97 /// inner matcher and appears as follows:
98 ///
99 /// ```ignore
100 /// fn explain_match(&self, actual: &Self::ActualT) -> Description {
101 /// self.expected.explain_match(actual.deref())
102 /// }
103 /// ```
104 ///
105 /// The matcher can also provide some additional context before deferring to
106 /// an inner matcher. In that case it should invoke `explain_match` on the
107 /// inner matcher at a point where a relative clause would fit. For example:
108 ///
109 /// ```ignore
110 /// fn explain_match(&self, actual: &Self::ActualT) -> Description {
111 /// Description::new()
112 /// .text("which points to a value")
113 /// .nested(self.expected.explain_match(actual.deref()))
114 /// }
115 /// ```
116 fn explain_match(&self, actual: &ActualT) -> Description {
117 format!("which {}", self.describe(self.matches(actual))).into()
118 }
119}
120
121/// Extension methods for composing matchers.
122///
123/// This trait is implemented for all [`Sized`] types, but the resulting
124/// combinators are only useful when the underlying types implement [`Matcher`].
125//
126// This is kept separate from [`Matcher`] so that the type parameter `ActualT`
127// does not need to be known at the `.and()` / `.or()` call site. Type
128// inference determines `ActualT` later, when the combined matcher is applied
129// to an actual value.
130pub trait MatcherExt: Sized {
131 /// Constructs a matcher that matches both `self` and `right`.
132 ///
133 /// ```
134 /// # use test_that::prelude::*;
135 /// # fn should_pass() -> TestResult<()> {
136 /// verify_that!("A string", starts_with("A").and(ends_with("string")))?; // Passes
137 /// # Ok(())
138 /// # }
139 /// # fn should_fail_1() -> TestResult<()> {
140 /// verify_that!("A string", starts_with("Another").and(ends_with("string")))?; // Fails
141 /// # Ok(())
142 /// # }
143 /// # fn should_fail_2() -> TestResult<()> {
144 /// verify_that!("A string", starts_with("A").and(ends_with("non-string")))?; // Fails
145 /// # Ok(())
146 /// # }
147 /// # should_pass().unwrap();
148 /// # should_fail_1().unwrap_err();
149 /// # should_fail_2().unwrap_err();
150 /// ```
151 // TODO(b/264518763): Replace the return type with impl Matcher and reduce
152 // visibility of ConjunctionMatcher once impl in return position in trait
153 // methods is stable.
154 fn and<Right>(self, right: Right) -> ConjunctionMatcher<Self, Right> {
155 ConjunctionMatcher::new(self, right)
156 }
157
158 /// Constructs a matcher that matches when at least one of `self` or `right`
159 /// matches the input.
160 ///
161 /// ```
162 /// # use test_that::prelude::*;
163 /// # fn should_pass() -> TestResult<()> {
164 /// verify_that!(10, eq(2).or(ge(5)))?; // Passes
165 /// verify_that!(10, eq(2).or(eq(5)).or(ge(9)))?; // Passes
166 /// # Ok(())
167 /// # }
168 /// # fn should_fail() -> TestResult<()> {
169 /// verify_that!(10, eq(2).or(ge(15)))?; // Fails
170 /// # Ok(())
171 /// # }
172 /// # should_pass().unwrap();
173 /// # should_fail().unwrap_err();
174 /// ```
175 // TODO(b/264518763): Replace the return type with impl Matcher and reduce
176 // visibility of DisjunctionMatcher once impl in return position in trait
177 // methods is stable.
178 fn or<Right>(self, right: Right) -> DisjunctionMatcher<Self, Right> {
179 DisjunctionMatcher::new(self, right)
180 }
181}
182
183impl<M: Sized> MatcherExt for M {}
184
185/// An item, normally a [Matcher] with positive and negative valences which can
186/// be turned into a [Description] for human consumption.
187pub trait Describable {
188 /// Returns a description of `self` or a negative description if
189 /// `matcher_result` is `DoesNotMatch`.
190 ///
191 /// The function should print a verb phrase that describes the property a
192 /// value matching, respectively not matching, this matcher should have.
193 /// The subject of the verb phrase is the value being matched.
194 ///
195 /// The output appears next to `Expected` in an assertion failure message.
196 /// For example:
197 ///
198 /// ```text
199 /// Value of: ...
200 /// Expected: is equal to 7
201 /// ^^^^^^^^^^^^^
202 /// Actual: ...
203 /// ```
204 ///
205 /// When the matcher contains one or more inner matchers, the implementation
206 /// should invoke [`Self::describe`] on the inner matchers to complete the
207 /// description. It should place the inner description at a point where a
208 /// verb phrase would fit. For example, the matcher
209 /// [`some`][crate::matchers::some] implements `describe` as follows:
210 ///
211 /// ```ignore
212 /// fn describe(&self, matcher_result: MatcherResult) -> Description {
213 /// match matcher_result {
214 /// MatcherResult::Matches => {
215 /// Description::new()
216 /// .text("has a value which")
217 /// .nested(self.inner.describe(MatcherResult::Matches))
218 /// // Inner matcher: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
219 /// }
220 /// MatcherResult::DoesNotMatch => {...} // Similar to the above
221 /// }
222 /// }
223 /// ```
224 ///
225 /// The output expectation differs from that of
226 /// [`explain_match`][Matcher::explain_match] in that it is a verb phrase
227 /// (beginning with a verb like "is") rather than a relative clause
228 /// (beginning with "which" or "whose"). This difference is because the
229 /// output of `explain_match` is always used adjectivally to describe the
230 /// actual value, while `describe` is used in contexts where a relative
231 /// clause would not make sense.
232 fn describe(&self, matcher_result: MatcherResult) -> Description;
233}
234
235/// Any actual value whose debug length is greater than this value will be
236/// pretty-printed. Otherwise, it will have normal debug output formatting.
237const PRETTY_PRINT_LENGTH_THRESHOLD: usize = 60;
238
239/// Constructs a [`TestAssertionFailure`] reporting that the given `matcher`
240/// does not match the value `actual`.
241///
242/// The parameter `actual_expr` contains the expression which was evaluated to
243/// obtain `actual`.
244pub(crate) fn create_assertion_failure<T: Debug + ?Sized>(
245 matcher: &impl Matcher<T>,
246 actual: &T,
247 actual_expr: &'static str,
248 source_location: SourceLocation,
249) -> TestAssertionFailure {
250 let actual_formatted = format!("{actual:?}");
251 let actual_formatted = if actual_formatted.len() > PRETTY_PRINT_LENGTH_THRESHOLD {
252 format!("{actual:#?}")
253 } else {
254 actual_formatted
255 };
256 TestAssertionFailure::create(format!(
257 "\
258Value of: {actual_expr}
259Expected: {}
260Actual: {actual_formatted},
261{}
262{source_location}",
263 matcher.describe(MatcherResult::Match),
264 matcher.explain_match(actual).indent(),
265 ))
266}
267
268/// The result of applying a [`Matcher`] on an actual value.
269#[derive(Debug, PartialEq, Clone, Copy)]
270pub enum MatcherResult {
271 /// The actual value matches according to the [`Matcher`] definition.
272 Match,
273 /// The actual value does not match according to the [`Matcher`] definition.
274 NoMatch,
275}
276
277impl From<bool> for MatcherResult {
278 fn from(b: bool) -> Self {
279 if b { MatcherResult::Match } else { MatcherResult::NoMatch }
280 }
281}
282
283impl From<MatcherResult> for bool {
284 fn from(matcher_result: MatcherResult) -> Self {
285 matcher_result.is_match()
286 }
287}
288
289impl MatcherResult {
290 /// Returns `true` if `self` is [`MatcherResult::Match`], otherwise
291 /// `false`.
292 pub fn is_match(self) -> bool {
293 matches!(self, MatcherResult::Match)
294 }
295
296 /// Returns `true` if `self` is [`MatcherResult::NoMatch`], otherwise
297 /// `false`.
298 pub fn is_no_match(self) -> bool {
299 matches!(self, MatcherResult::NoMatch)
300 }
301}