Skip to content

Commit d2be133

Browse files
hovinenbcopybara-github
authored andcommitted
Implement explain_match for FieldMatcher.
Previously, this wasn't implemented, so test assertion failure messages from both field! and matches_pattern! would use the default implementation based on Matcher::describe. The latter operates without reference to the actual value, so all information from the actual value would be lost for that and all further inner matchers. That makes diagnosis of problems much harder. This implements explain_match as part of an effort to ensure that all matchers which referene inner matchers have suitable explain_match implementations. This also renames the type parameters of FieldMatcher to make the names more expressive. PiperOrigin-RevId: 505623614
1 parent 9d0f6e0 commit d2be133

File tree

1 file changed

+63
-10
lines changed

1 file changed

+63
-10
lines changed

googletest/src/matchers/field_matcher.rs

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,37 +95,54 @@ macro_rules! field {
9595
pub mod internal {
9696
#[cfg(not(google3))]
9797
use crate as googletest;
98-
use googletest::matcher::{Matcher, MatcherResult};
98+
use googletest::matcher::{MatchExplanation, Matcher, MatcherResult};
9999
use std::fmt::Debug;
100100

101101
/// Creates a matcher to verify a specific field of the actual struct using
102102
/// the provided inner matcher.
103103
///
104104
/// **For internal use only. API stablility is not guaranteed!**
105105
#[doc(hidden)]
106-
pub fn field_matcher<O: Debug, I: Debug, InnerMatcher: Matcher<I>>(
107-
field_accessor: fn(&O) -> Option<&I>,
106+
pub fn field_matcher<OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<InnerT>>(
107+
field_accessor: fn(&OuterT) -> Option<&InnerT>,
108108
field_path: &'static str,
109109
inner: InnerMatcher,
110-
) -> impl Matcher<O> {
110+
) -> impl Matcher<OuterT> {
111111
FieldMatcher { field_accessor, field_path, inner }
112112
}
113113

114-
struct FieldMatcher<O, I, InnerMatcher> {
115-
field_accessor: fn(&O) -> Option<&I>,
114+
struct FieldMatcher<OuterT, InnerT, InnerMatcher> {
115+
field_accessor: fn(&OuterT) -> Option<&InnerT>,
116116
field_path: &'static str,
117117
inner: InnerMatcher,
118118
}
119119

120-
impl<O: Debug, I: Debug, InnerMatcher: Matcher<I>> Matcher<O> for FieldMatcher<O, I, InnerMatcher> {
121-
fn matches(&self, actual: &O) -> MatcherResult {
120+
impl<OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<InnerT>> Matcher<OuterT>
121+
for FieldMatcher<OuterT, InnerT, InnerMatcher>
122+
{
123+
fn matches(&self, actual: &OuterT) -> MatcherResult {
122124
if let Some(value) = (self.field_accessor)(actual) {
123125
self.inner.matches(value)
124126
} else {
125127
MatcherResult::DoesNotMatch
126128
}
127129
}
128130

131+
fn explain_match(&self, actual: &OuterT) -> MatchExplanation {
132+
if let Some(actual) = (self.field_accessor)(actual) {
133+
MatchExplanation::create(format!(
134+
"which has field `{}`, {}",
135+
self.field_path,
136+
self.inner.explain_match(actual)
137+
))
138+
} else {
139+
// TODO(hovinen): This message could be misinterpreted to mean that there were a
140+
// typo in the field, when it actually means that the actual value uses the
141+
// wrong enum variant. Reword this appropriately.
142+
MatchExplanation::create(format!("which has no field `{}`", self.field_path))
143+
}
144+
}
145+
129146
fn describe(&self, matcher_result: MatcherResult) -> String {
130147
format!(
131148
"has field `{}`, which {}",
@@ -144,7 +161,7 @@ mod tests {
144161
#[cfg(not(google3))]
145162
use googletest::matchers;
146163
use googletest::{google_test, verify_that, Result};
147-
use matchers::{eq, not};
164+
use matchers::{container_eq, contains_substring, displays_as, eq, err, not};
148165

149166
#[derive(Debug)]
150167
struct IntField {
@@ -185,6 +202,7 @@ mod tests {
185202
pub field: i32,
186203
}
187204
}
205+
188206
#[google_test]
189207
fn struct_in_other_module_matches() -> Result<()> {
190208
verify_that!(sub::SubStruct { field: 32 }, field!(sub::SubStruct.field, eq(32)))
@@ -210,7 +228,25 @@ mod tests {
210228
}
211229

212230
#[google_test]
213-
fn does_not_match_enum_value_with_wrong_enum_value() -> Result<()> {
231+
fn shows_correct_failure_message_for_wrong_struct_entry() -> Result<()> {
232+
#[derive(Debug)]
233+
struct AStruct {
234+
a: Vec<u32>,
235+
}
236+
let value = AStruct { a: vec![1] };
237+
238+
let result = verify_that!(value, field!(AStruct.a, container_eq([])));
239+
240+
verify_that!(
241+
result,
242+
err(displays_as(contains_substring(
243+
"which has field `a`, which contains the unexpected element 1"
244+
)))
245+
)
246+
}
247+
248+
#[google_test]
249+
fn does_not_match_enum_value_with_wrong_enum_variant() -> Result<()> {
214250
#[derive(Debug)]
215251
enum AnEnum {
216252
#[allow(dead_code)] // This variant is intentionally unused.
@@ -222,6 +258,23 @@ mod tests {
222258
verify_that!(value, not(field!(AnEnum::AValue.0, eq(123))))
223259
}
224260

261+
#[google_test]
262+
fn shows_correct_failure_message_for_wrong_enum_value() -> Result<()> {
263+
#[derive(Debug)]
264+
enum AnEnum {
265+
#[allow(dead_code)] // This variant is intentionally unused.
266+
AValue {
267+
a: u32,
268+
},
269+
AnotherValue,
270+
}
271+
let value = AnEnum::AnotherValue;
272+
273+
let result = verify_that!(value, field!(AnEnum::AValue.a, eq(123)));
274+
275+
verify_that!(result, err(displays_as(contains_substring("which has no field `a`"))))
276+
}
277+
225278
#[google_test]
226279
fn matches_struct_like_enum_value() -> Result<()> {
227280
#[derive(Debug)]

0 commit comments

Comments
 (0)