Skip to content

Commit c7d88d1

Browse files
Merge pull request #358 from google:all_with_conjunction
PiperOrigin-RevId: 603705344
2 parents 10c780e + f317505 commit c7d88d1

File tree

5 files changed

+130
-120
lines changed

5 files changed

+130
-120
lines changed

googletest/src/description.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ use crate::internal::description_renderer::{List, INDENTATION_SIZE};
8989
pub struct Description {
9090
elements: List,
9191
initial_indentation: usize,
92+
is_conjunction: bool,
9293
}
9394

9495
impl Description {
@@ -199,6 +200,19 @@ impl Description {
199200
pub fn is_empty(&self) -> bool {
200201
self.elements.is_empty()
201202
}
203+
204+
pub(crate) fn conjunction_description(self) -> Self {
205+
Self { is_conjunction: true, ..self }
206+
}
207+
208+
pub(crate) fn is_conjunction_description(&self) -> bool {
209+
self.is_conjunction
210+
}
211+
212+
pub(crate) fn push_in_last_nested(mut self, inner: Description) -> Self {
213+
self.elements.push_at_end(inner.elements);
214+
self
215+
}
202216
}
203217

204218
impl Display for Description {

googletest/src/internal/description_renderer.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ impl List {
8383
self.0.is_empty()
8484
}
8585

86+
/// Append a new [`List`] in the last element which must be a
87+
/// [`Block::Nested`]. Panic if `self` is empty or the last element is
88+
/// not [`Block::Nested`].
89+
pub(crate) fn push_at_end(&mut self, list: List) {
90+
if let Some(Block::Nested(self_list)) = self.0.last_mut() {
91+
self_list.push_nested(list);
92+
} else {
93+
panic!("pushing elements at the end of {self:#?} which last element is not Nested")
94+
}
95+
}
96+
8697
fn render_with_prefix(
8798
&self,
8899
f: &mut dyn Write,

googletest/src/matchers/all_matcher.rs

Lines changed: 21 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -53,114 +53,34 @@
5353
#[macro_export]
5454
#[doc(hidden)]
5555
macro_rules! __all {
56-
($($matcher:expr),* $(,)?) => {{
57-
use $crate::matchers::__internal_unstable_do_not_depend_on_these::AllMatcher;
58-
AllMatcher::new([$(Box::new($matcher)),*])
56+
($(,)?) => {{
57+
$crate::matchers::anything()
58+
}} ;
59+
($matcher:expr $(,)?) => {{
60+
$matcher
61+
}};
62+
($head:expr, $head2:expr $(,)?) => {{
63+
$crate::matchers::__internal_unstable_do_not_depend_on_these::ConjunctionMatcher::new($head, $head2)
64+
}};
65+
($head:expr, $head2:expr, $($tail:expr),+ $(,)?) => {{
66+
$crate::__all![
67+
$crate::matchers::__internal_unstable_do_not_depend_on_these::ConjunctionMatcher::new($head, $head2),
68+
$($tail),+
69+
]
5970
}}
6071
}
6172

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

15979
#[test]
16080
fn description_shows_more_than_one_matcher() -> Result<()> {
161-
let first_matcher = starts_with("A");
81+
let first_matcher: StrMatcher<String, _> = starts_with("A");
16282
let second_matcher = ends_with("string");
163-
let matcher: internal::AllMatcher<String, 2> = all!(first_matcher, second_matcher);
83+
let matcher = all!(first_matcher, second_matcher);
16484

16585
verify_that!(
16686
matcher.describe(MatcherResult::Match),
@@ -175,8 +95,8 @@ mod tests {
17595

17696
#[test]
17797
fn description_shows_one_matcher_directly() -> Result<()> {
178-
let first_matcher = starts_with("A");
179-
let matcher: internal::AllMatcher<String, 1> = all!(first_matcher);
98+
let first_matcher: StrMatcher<String, _> = starts_with("A");
99+
let matcher = all!(first_matcher);
180100

181101
verify_that!(
182102
matcher.describe(MatcherResult::Match),
@@ -187,9 +107,9 @@ mod tests {
187107
#[test]
188108
fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent() -> Result<()>
189109
{
190-
let first_matcher = starts_with("Another");
110+
let first_matcher: StrMatcher<str, _> = starts_with("Another");
191111
let second_matcher = ends_with("string");
192-
let matcher: internal::AllMatcher<str, 2> = all!(first_matcher, second_matcher);
112+
let matcher = all!(first_matcher, second_matcher);
193113

194114
verify_that!(
195115
matcher.explain_match("A string"),
@@ -200,7 +120,7 @@ mod tests {
200120
#[test]
201121
fn mismatch_description_is_simple_when_only_one_consistuent() -> Result<()> {
202122
let first_matcher = starts_with("Another");
203-
let matcher: internal::AllMatcher<str, 1> = all!(first_matcher);
123+
let matcher = all!(first_matcher);
204124

205125
verify_that!(
206126
matcher.explain_match("A string"),

googletest/src/matchers/conjunction_matcher.rs

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,23 @@ use crate::{
2121
};
2222
use std::fmt::Debug;
2323

24-
/// Matcher created by [`Matcher::and`].
24+
/// Matcher created by [`Matcher::and`] and [`all!`].
25+
///
26+
/// Both [`Matcher::and`] and [`all!`] nest on m1. In other words,
27+
/// both `x.and(y).and(z)` and `all![x, y, z]` produce:
28+
/// ```ignore
29+
/// ConjunctionMatcher {
30+
/// m1: ConjunctionMatcher {
31+
/// m1: x,
32+
/// m2: y
33+
/// },
34+
/// m2: z
35+
/// }
36+
/// ```
37+
///
38+
/// This behavior must be respected
39+
/// to ensure that [`Matcher::explain_match`] and [`Matcher::describe`] produce
40+
/// useful descriptions.
2541
///
2642
/// **For internal use only. API stablility is not guaranteed!**
2743
#[doc(hidden)]
@@ -31,7 +47,7 @@ pub struct ConjunctionMatcher<M1, M2> {
3147
}
3248

3349
impl<M1, M2> ConjunctionMatcher<M1, M2> {
34-
pub(crate) fn new(m1: M1, m2: M2) -> Self {
50+
pub fn new(m1: M1, m2: M2) -> Self {
3551
Self { m1, m2 }
3652
}
3753
}
@@ -51,22 +67,42 @@ where
5167

5268
fn explain_match(&self, actual: &M1::ActualT) -> Description {
5369
match (self.m1.matches(actual), self.m2.matches(actual)) {
54-
(MatcherResult::Match, MatcherResult::Match) => Description::new()
55-
.nested(self.m1.explain_match(actual))
56-
.text("and")
57-
.nested(self.m2.explain_match(actual)),
5870
(MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual),
5971
(MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual),
60-
(MatcherResult::NoMatch, MatcherResult::NoMatch) => Description::new()
61-
.nested(self.m1.explain_match(actual))
62-
.text("and")
63-
.nested(self.m2.explain_match(actual)),
72+
(_, _) => {
73+
let m1_description = self.m1.explain_match(actual);
74+
if m1_description.is_conjunction_description() {
75+
m1_description.nested(self.m2.explain_match(actual))
76+
} else {
77+
Description::new()
78+
.bullet_list()
79+
.collect([self.m1.explain_match(actual), self.m2.explain_match(actual)])
80+
.conjunction_description()
81+
}
82+
}
6483
}
6584
}
6685

6786
fn describe(&self, matcher_result: MatcherResult) -> Description {
68-
format!("{}, and {}", self.m1.describe(matcher_result), self.m2.describe(matcher_result))
69-
.into()
87+
let m1_description = self.m1.describe(matcher_result);
88+
if m1_description.is_conjunction_description() {
89+
m1_description.push_in_last_nested(self.m2.describe(matcher_result))
90+
} else {
91+
let header = if matcher_result.into() {
92+
"has all the following properties:"
93+
} else {
94+
"has at least one of the following properties:"
95+
};
96+
Description::new()
97+
.text(header)
98+
.nested(
99+
Description::new().bullet_list().collect([
100+
self.m1.describe(matcher_result),
101+
self.m2.describe(matcher_result),
102+
]),
103+
)
104+
.conjunction_description()
105+
}
70106
}
71107
}
72108

@@ -88,7 +124,9 @@ mod tests {
88124
err(displays_as(contains_substring(indoc!(
89125
"
90126
Value of: 1
91-
Expected: is anything, and never matches
127+
Expected: has all the following properties:
128+
* is anything
129+
* never matches
92130
Actual: 1,
93131
which is anything
94132
"
@@ -104,7 +142,9 @@ mod tests {
104142
err(displays_as(contains_substring(indoc!(
105143
"
106144
Value of: 1
107-
Expected: never matches, and is anything
145+
Expected: has all the following properties:
146+
* never matches
147+
* is anything
108148
Actual: 1,
109149
which is anything
110150
"
@@ -120,11 +160,37 @@ mod tests {
120160
err(displays_as(contains_substring(indoc!(
121161
"
122162
Value of: 1
123-
Expected: never matches, and never matches
163+
Expected: has all the following properties:
164+
* never matches
165+
* never matches
166+
Actual: 1,
167+
* which is anything
168+
* which is anything
169+
"
170+
))))
171+
)
172+
}
173+
174+
#[test]
175+
fn and_long_chain_of_matchers() -> Result<()> {
176+
let result = verify_that!(
177+
1,
178+
anything().and(not(anything())).and(anything()).and(not(anything())).and(anything())
179+
);
180+
verify_that!(
181+
result,
182+
err(displays_as(contains_substring(indoc!(
183+
"
184+
Value of: 1
185+
Expected: has all the following properties:
186+
* is anything
187+
* never matches
188+
* is anything
189+
* never matches
190+
* is anything
124191
Actual: 1,
125-
which is anything
126-
and
127-
which is anything
192+
* which is anything
193+
* which is anything
128194
"
129195
))))
130196
)

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::all_matcher::internal::AllMatcher;
109108
pub use super::any_matcher::internal::AnyMatcher;
110109
pub use super::conjunction_matcher::ConjunctionMatcher;
111110
pub use super::disjunction_matcher::DisjunctionMatcher;

0 commit comments

Comments
 (0)