Skip to content

Commit 2eca8c4

Browse files
committed
Use DisjunctionMatcher for any![] macro.
1 parent c7d88d1 commit 2eca8c4

File tree

5 files changed

+77
-124
lines changed

5 files changed

+77
-124
lines changed

googletest/src/description.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ pub struct Description {
9090
elements: List,
9191
initial_indentation: usize,
9292
is_conjunction: bool,
93+
is_disjunction: bool,
9394
}
9495

9596
impl Description {
@@ -201,6 +202,11 @@ impl Description {
201202
self.elements.is_empty()
202203
}
203204

205+
pub(crate) fn push_in_last_nested(mut self, inner: Description) -> Self {
206+
self.elements.push_at_end(inner.elements);
207+
self
208+
}
209+
204210
pub(crate) fn conjunction_description(self) -> Self {
205211
Self { is_conjunction: true, ..self }
206212
}
@@ -209,9 +215,12 @@ impl Description {
209215
self.is_conjunction
210216
}
211217

212-
pub(crate) fn push_in_last_nested(mut self, inner: Description) -> Self {
213-
self.elements.push_at_end(inner.elements);
214-
self
218+
pub(crate) fn disjunction_description(self) -> Self {
219+
Self { is_disjunction: true, ..self }
220+
}
221+
222+
pub(crate) fn is_disjunction_description(&self) -> bool {
223+
self.is_disjunction
215224
}
216225
}
217226

googletest/src/matchers/any_matcher.rs

Lines changed: 17 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -55,112 +55,31 @@
5555
#[macro_export]
5656
#[doc(hidden)]
5757
macro_rules! __any {
58-
($($matcher:expr),* $(,)?) => {{
59-
use $crate::matchers::__internal_unstable_do_not_depend_on_these::AnyMatcher;
60-
AnyMatcher::new([$(Box::new($matcher)),*])
58+
($matcher:expr $(,)?) => {{
59+
$matcher
60+
}};
61+
($head:expr, $head2:expr $(,)?) => {{
62+
$crate::matchers::__internal_unstable_do_not_depend_on_these::DisjunctionMatcher::new($head, $head2)
63+
}};
64+
($head:expr, $head2:expr, $($tail:expr),+ $(,)?) => {{
65+
$crate::__any![
66+
$crate::matchers::__internal_unstable_do_not_depend_on_these::DisjunctionMatcher::new($head, $head2),
67+
$($tail),+
68+
]
6169
}}
6270
}
6371

64-
/// Functionality needed by the [`any`] macro.
65-
///
66-
/// For internal use only. API stablility is not guaranteed!
67-
#[doc(hidden)]
68-
pub mod internal {
69-
use crate::description::Description;
70-
use crate::matcher::{Matcher, MatcherResult};
71-
use crate::matchers::anything;
72-
use std::fmt::Debug;
73-
74-
/// A matcher which matches an input value matched by all matchers in the
75-
/// array `components`.
76-
///
77-
/// For internal use only. API stablility is not guaranteed!
78-
#[doc(hidden)]
79-
pub struct AnyMatcher<'a, T: Debug + ?Sized, const N: usize> {
80-
components: [Box<dyn Matcher<ActualT = T> + 'a>; N],
81-
}
82-
83-
impl<'a, T: Debug + ?Sized, const N: usize> AnyMatcher<'a, T, N> {
84-
/// Constructs an [`AnyMatcher`] with the given component matchers.
85-
///
86-
/// Intended for use only by the [`all`] macro.
87-
pub fn new(components: [Box<dyn Matcher<ActualT = T> + 'a>; N]) -> Self {
88-
Self { components }
89-
}
90-
}
91-
92-
impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AnyMatcher<'a, T, N> {
93-
type ActualT = T;
94-
95-
fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
96-
MatcherResult::from(self.components.iter().any(|c| c.matches(actual).is_match()))
97-
}
98-
99-
fn explain_match(&self, actual: &Self::ActualT) -> Description {
100-
match N {
101-
0 => format!("which {}", anything::<T>().describe(MatcherResult::NoMatch)).into(),
102-
1 => self.components[0].explain_match(actual),
103-
_ => {
104-
let failures = self
105-
.components
106-
.iter()
107-
.filter(|component| component.matches(actual).is_no_match())
108-
.collect::<Vec<_>>();
109-
110-
if failures.len() == 1 {
111-
failures[0].explain_match(actual)
112-
} else {
113-
Description::new()
114-
.collect(
115-
failures
116-
.into_iter()
117-
.map(|component| component.explain_match(actual)),
118-
)
119-
.bullet_list()
120-
}
121-
}
122-
}
123-
}
124-
125-
fn describe(&self, matcher_result: MatcherResult) -> Description {
126-
match N {
127-
0 => anything::<T>().describe(matcher_result),
128-
1 => self.components[0].describe(matcher_result),
129-
_ => {
130-
let properties = self
131-
.components
132-
.iter()
133-
.map(|m| m.describe(matcher_result))
134-
.collect::<Description>()
135-
.bullet_list()
136-
.indent();
137-
format!(
138-
"{}:\n{properties}",
139-
if matcher_result.into() {
140-
"has at least one of the following properties"
141-
} else {
142-
"has none of the following properties"
143-
}
144-
)
145-
.into()
146-
}
147-
}
148-
}
149-
}
150-
}
151-
15272
#[cfg(test)]
15373
mod tests {
154-
use super::internal;
15574
use crate::matcher::{Matcher, MatcherResult};
15675
use crate::prelude::*;
15776
use indoc::indoc;
15877

15978
#[test]
16079
fn description_shows_more_than_one_matcher() -> Result<()> {
161-
let first_matcher = starts_with("A");
80+
let first_matcher: StrMatcher<String, &str> = starts_with("A");
16281
let second_matcher = ends_with("string");
163-
let matcher: internal::AnyMatcher<String, 2> = any!(first_matcher, second_matcher);
82+
let matcher = any!(first_matcher, second_matcher);
16483

16584
verify_that!(
16685
matcher.describe(MatcherResult::Match),
@@ -175,8 +94,8 @@ mod tests {
17594

17695
#[test]
17796
fn description_shows_one_matcher_directly() -> Result<()> {
178-
let first_matcher = starts_with("A");
179-
let matcher: internal::AnyMatcher<String, 1> = any!(first_matcher);
97+
let first_matcher: StrMatcher<String, &str> = starts_with("A");
98+
let matcher = any!(first_matcher);
18099

181100
verify_that!(
182101
matcher.describe(MatcherResult::Match),
@@ -189,7 +108,7 @@ mod tests {
189108
{
190109
let first_matcher = starts_with("Another");
191110
let second_matcher = ends_with("string");
192-
let matcher: internal::AnyMatcher<str, 2> = any!(first_matcher, second_matcher);
111+
let matcher = any!(first_matcher, second_matcher);
193112

194113
verify_that!(
195114
matcher.explain_match("A string"),
@@ -200,7 +119,7 @@ mod tests {
200119
#[test]
201120
fn mismatch_description_is_simple_when_only_one_constituent() -> Result<()> {
202121
let first_matcher = starts_with("Another");
203-
let matcher: internal::AnyMatcher<str, 1> = any!(first_matcher);
122+
let matcher = any!(first_matcher);
204123

205124
verify_that!(
206125
matcher.explain_match("A string"),

googletest/src/matchers/disjunction_matcher.rs

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@ use crate::{
2121
};
2222
use std::fmt::Debug;
2323

24-
/// Matcher created by [`Matcher::or`].
24+
/// Matcher created by [`Matcher::or`] and [`any!`].
2525
///
26+
/// Both [`Matcher::or`] and [`any!`] nest on m1. In other words,
27+
/// both `x.or(y).or(z)` and `any![x, y, z]` produce:
28+
/// ```ignore
29+
/// DisjunctionMatcher {
30+
/// m1: DisjunctionMatcher {
31+
/// m1: x, m2: y}, m2: z}
32+
/// ```
2633
/// **For internal use only. API stablility is not guaranteed!**
2734
#[doc(hidden)]
2835
pub struct DisjunctionMatcher<M1, M2> {
@@ -31,7 +38,7 @@ pub struct DisjunctionMatcher<M1, M2> {
3138
}
3239

3340
impl<M1, M2> DisjunctionMatcher<M1, M2> {
34-
pub(crate) fn new(m1: M1, m2: M2) -> Self {
41+
pub fn new(m1: M1, m2: M2) -> Self {
3542
Self { m1, m2 }
3643
}
3744
}
@@ -50,15 +57,43 @@ where
5057
}
5158

5259
fn explain_match(&self, actual: &M1::ActualT) -> Description {
53-
Description::new()
54-
.nested(self.m1.explain_match(actual))
55-
.text("and")
56-
.nested(self.m2.explain_match(actual))
60+
match (self.m1.matches(actual), self.m2.matches(actual)) {
61+
(MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual),
62+
(MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual),
63+
(_, _) => {
64+
let m1_description = self.m1.explain_match(actual);
65+
if m1_description.is_disjunction_description() {
66+
m1_description.nested(self.m2.explain_match(actual))
67+
} else {
68+
Description::new()
69+
.bullet_list()
70+
.collect([self.m1.explain_match(actual), self.m2.explain_match(actual)])
71+
.disjunction_description()
72+
}
73+
}
74+
}
5775
}
5876

5977
fn describe(&self, matcher_result: MatcherResult) -> Description {
60-
format!("{}, or {}", self.m1.describe(matcher_result), self.m2.describe(matcher_result))
61-
.into()
78+
let m1_description = self.m1.describe(matcher_result);
79+
if m1_description.is_disjunction_description() {
80+
m1_description.push_in_last_nested(self.m2.describe(matcher_result))
81+
} else {
82+
let header = if matcher_result.into() {
83+
"has at least one of the following properties:"
84+
} else {
85+
"has all of the following properties:"
86+
};
87+
Description::new()
88+
.text(header)
89+
.nested(
90+
Description::new().bullet_list().collect([
91+
self.m1.describe(matcher_result),
92+
self.m2.describe(matcher_result),
93+
]),
94+
)
95+
.disjunction_description()
96+
}
6297
}
6398
}
6499

@@ -90,11 +125,12 @@ mod tests {
90125
err(displays_as(contains_substring(indoc!(
91126
"
92127
Value of: 1
93-
Expected: never matches, or never matches
128+
Expected: has at least one of the following properties:
129+
* never matches
130+
* never matches
94131
Actual: 1,
95-
which is anything
96-
and
97-
which is anything
132+
* which is anything
133+
* which is anything
98134
"
99135
))))
100136
)

googletest/src/matchers/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ pub use crate::{
105105
// should only be used through their respective macros.
106106
#[doc(hidden)]
107107
pub mod __internal_unstable_do_not_depend_on_these {
108-
pub use super::any_matcher::internal::AnyMatcher;
109108
pub use super::conjunction_matcher::ConjunctionMatcher;
110109
pub use super::disjunction_matcher::DisjunctionMatcher;
111110
pub use super::elements_are_matcher::internal::ElementsAre;

googletest/tests/any_matcher_test.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ use googletest::matcher::Matcher;
1616
use googletest::prelude::*;
1717
use indoc::indoc;
1818

19-
#[test]
20-
fn does_not_match_value_when_list_is_empty() -> Result<()> {
21-
verify_that!((), not(any!()))
22-
}
23-
2419
#[test]
2520
fn matches_value_with_single_matching_component() -> Result<()> {
2621
verify_that!(123, any!(eq(123)))
@@ -65,11 +60,6 @@ fn mismatch_description_two_failed_matchers() -> Result<()> {
6560
)
6661
}
6762

68-
#[test]
69-
fn mismatch_description_empty_matcher() -> Result<()> {
70-
verify_that!(any!().explain_match("Three"), displays_as(eq("which never matches")))
71-
}
72-
7363
#[test]
7464
fn all_multiple_failed_assertions() -> Result<()> {
7565
let result = verify_that!(4, any![eq(1), eq(2), eq(3)]);

0 commit comments

Comments
 (0)