Skip to content

Commit 2ef3ca4

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

File tree

6 files changed

+86
-129
lines changed

6 files changed

+86
-129
lines changed

googletest/src/description.rs

+12-3
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

+20-98
Original file line numberDiff line numberDiff line change
@@ -55,112 +55,34 @@
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+
($(,)?) => {{
59+
std::compile_error!("any![...] expects at least one argument");
60+
}} ;
61+
($matcher:expr $(,)?) => {{
62+
$matcher
63+
}};
64+
($head:expr, $head2:expr $(,)?) => {{
65+
$crate::matchers::__internal_unstable_do_not_depend_on_these::DisjunctionMatcher::new($head, $head2)
66+
}};
67+
($head:expr, $head2:expr, $($tail:expr),+ $(,)?) => {{
68+
$crate::__any![
69+
$crate::matchers::__internal_unstable_do_not_depend_on_these::DisjunctionMatcher::new($head, $head2),
70+
$($tail),+
71+
]
6172
}}
6273
}
6374

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-
15275
#[cfg(test)]
15376
mod tests {
154-
use super::internal;
15577
use crate::matcher::{Matcher, MatcherResult};
15678
use crate::prelude::*;
15779
use indoc::indoc;
15880

15981
#[test]
16082
fn description_shows_more_than_one_matcher() -> Result<()> {
161-
let first_matcher = starts_with("A");
83+
let first_matcher: StrMatcher<String, &str> = starts_with("A");
16284
let second_matcher = ends_with("string");
163-
let matcher: internal::AnyMatcher<String, 2> = any!(first_matcher, second_matcher);
85+
let matcher = any!(first_matcher, second_matcher);
16486

16587
verify_that!(
16688
matcher.describe(MatcherResult::Match),
@@ -175,8 +97,8 @@ mod tests {
17597

17698
#[test]
17799
fn description_shows_one_matcher_directly() -> Result<()> {
178-
let first_matcher = starts_with("A");
179-
let matcher: internal::AnyMatcher<String, 1> = any!(first_matcher);
100+
let first_matcher: StrMatcher<String, &str> = starts_with("A");
101+
let matcher = any!(first_matcher);
180102

181103
verify_that!(
182104
matcher.describe(MatcherResult::Match),
@@ -189,7 +111,7 @@ mod tests {
189111
{
190112
let first_matcher = starts_with("Another");
191113
let second_matcher = ends_with("string");
192-
let matcher: internal::AnyMatcher<str, 2> = any!(first_matcher, second_matcher);
114+
let matcher = any!(first_matcher, second_matcher);
193115

194116
verify_that!(
195117
matcher.explain_match("A string"),
@@ -200,7 +122,7 @@ mod tests {
200122
#[test]
201123
fn mismatch_description_is_simple_when_only_one_constituent() -> Result<()> {
202124
let first_matcher = starts_with("Another");
203-
let matcher: internal::AnyMatcher<str, 1> = any!(first_matcher);
125+
let matcher = any!(first_matcher);
204126

205127
verify_that!(
206128
matcher.explain_match("A string"),

googletest/src/matchers/conjunction_matcher.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ where
7676
} else {
7777
Description::new()
7878
.bullet_list()
79-
.collect([self.m1.explain_match(actual), self.m2.explain_match(actual)])
79+
.collect([m1_description, self.m2.explain_match(actual)])
8080
.conjunction_description()
8181
}
8282
}
@@ -96,10 +96,9 @@ where
9696
Description::new()
9797
.text(header)
9898
.nested(
99-
Description::new().bullet_list().collect([
100-
self.m1.describe(matcher_result),
101-
self.m2.describe(matcher_result),
102-
]),
99+
Description::new()
100+
.bullet_list()
101+
.collect([m1_description, self.m2.describe(matcher_result)]),
103102
)
104103
.conjunction_description()
105104
}

googletest/src/matchers/disjunction_matcher.rs

+50-12
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,18 @@ 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
32+
/// },
33+
/// m2: z
34+
/// }
35+
/// ```
2636
/// **For internal use only. API stablility is not guaranteed!**
2737
#[doc(hidden)]
2838
pub struct DisjunctionMatcher<M1, M2> {
@@ -31,7 +41,7 @@ pub struct DisjunctionMatcher<M1, M2> {
3141
}
3242

3343
impl<M1, M2> DisjunctionMatcher<M1, M2> {
34-
pub(crate) fn new(m1: M1, m2: M2) -> Self {
44+
pub fn new(m1: M1, m2: M2) -> Self {
3545
Self { m1, m2 }
3646
}
3747
}
@@ -50,15 +60,42 @@ where
5060
}
5161

5262
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))
63+
match (self.m1.matches(actual), self.m2.matches(actual)) {
64+
(MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual),
65+
(MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual),
66+
(_, _) => {
67+
let m1_description = self.m1.explain_match(actual);
68+
if m1_description.is_disjunction_description() {
69+
m1_description.nested(self.m2.explain_match(actual))
70+
} else {
71+
Description::new()
72+
.bullet_list()
73+
.collect([m1_description, self.m2.explain_match(actual)])
74+
.disjunction_description()
75+
}
76+
}
77+
}
5778
}
5879

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

@@ -90,11 +127,12 @@ mod tests {
90127
err(displays_as(contains_substring(indoc!(
91128
"
92129
Value of: 1
93-
Expected: never matches, or never matches
130+
Expected: has at least one of the following properties:
131+
* never matches
132+
* never matches
94133
Actual: 1,
95-
which is anything
96-
and
97-
which is anything
134+
* which is anything
135+
* which is anything
98136
"
99137
))))
100138
)

googletest/src/matchers/mod.rs

-1
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

-10
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)