Skip to content

Commit 48ffd20

Browse files
Googlercopybara-github
Googler
authored andcommitted
Implements container_eq(...).ignore_order() matcher.
This adds a matcher `IgnoringOrder` for the `container_eq` matcher. It reuses the `match_matrix` library so it can apply a maximal matching. PiperOrigin-RevId: 707634546
1 parent 4e763c4 commit 48ffd20

File tree

1 file changed

+229
-0
lines changed

1 file changed

+229
-0
lines changed

googletest/src/matchers/container_eq_matcher.rs

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
use crate::description::Description;
1616
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
17+
use crate::matcher_support::match_matrix::internal::{MatchMatrix, Requirements};
18+
use crate::matchers::eq_matcher::eq;
1719
use std::fmt::Debug;
1820

1921
/// Matches a container equal (in the sense of `==`) to `expected`.
@@ -153,6 +155,102 @@ fn build_explanation<T: Debug, U: Debug>(missing: Vec<T>, unexpected: Vec<U>) ->
153155
}
154156
}
155157

158+
impl<ExpectedContainerT> ContainerEqMatcher<ExpectedContainerT> {
159+
/// Match container equality, but ignoring element order.
160+
pub fn ignore_order(self) -> IgnoringOrder<ExpectedContainerT> {
161+
IgnoringOrder { expected: self.expected }
162+
}
163+
}
164+
165+
#[derive(MatcherBase)]
166+
pub struct IgnoringOrder<ExpectedContainerT> {
167+
expected: ExpectedContainerT,
168+
}
169+
170+
/// Implements a matcher that ignores the relative order of the elements.
171+
impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT>
172+
Matcher<ActualContainerT> for IgnoringOrder<ExpectedContainerT>
173+
where
174+
ActualElementT: Debug + Copy + for<'a> PartialEq<&'a ExpectedElementT>,
175+
ActualContainerT: Debug + Copy + IntoIterator<Item = ActualElementT>,
176+
ExpectedElementT: Debug,
177+
for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
178+
{
179+
fn matches(&self, actual: ActualContainerT) -> MatcherResult {
180+
let expected: Vec<Box<dyn Matcher<ActualElementT>>> = self
181+
.expected
182+
.into_iter()
183+
.map(|x| Box::new(eq(x)) as Box<dyn Matcher<ActualElementT>>)
184+
.collect();
185+
let match_matrix = MatchMatrix::generate(actual, &expected);
186+
// TODO: investigate why Requirements::PerfectMatch and
187+
// match_matrix.is_full_match is not doing what we expect
188+
// here.
189+
(match_matrix.is_match_for(Requirements::Subset)
190+
&& match_matrix.is_match_for(Requirements::Superset))
191+
.into()
192+
}
193+
194+
fn explain_match(&self, actual: ActualContainerT) -> Description {
195+
// We need to materialize the collections in order to have reliable iteration
196+
// order when generating our reports.
197+
let expected_items: Vec<&ExpectedElementT> = self.expected.into_iter().collect();
198+
let actual_items: Vec<ActualElementT> = actual.into_iter().collect();
199+
200+
let expected_matchers: Vec<_> = expected_items
201+
.iter()
202+
.map(|&x| Box::new(eq(x)) as Box<dyn Matcher<ActualElementT>>)
203+
.collect();
204+
let match_matrix = MatchMatrix::generate(actual_items.iter().copied(), &expected_matchers);
205+
206+
let best_match = match_matrix.find_best_match();
207+
208+
// Since we are doing equality checks, we can generate a slightly less verbose
209+
// message than BestMatch::get_explanation.
210+
let matches = best_match.get_matches().map(|(actual_idx, expected_idx)|{
211+
Description::new().text(
212+
format!(
213+
"Actual element {:?} at index {actual_idx} is equal to expected element at index {expected_idx}.",
214+
actual_items[actual_idx],
215+
))});
216+
217+
let unmatched_actual = best_match.get_unmatched_actual().map(|actual_idx| {
218+
Description::new().text(
219+
format!(
220+
"Actual element {:?} at index {actual_idx} did not match any remaining expected element.",
221+
actual_items[actual_idx],
222+
))
223+
});
224+
225+
let unmatched_expected =
226+
best_match.get_unmatched_expected().into_iter().map(|expected_idx| {
227+
Description::new().text(format!(
228+
"Expected element {:?} at index {expected_idx} did not match any remaining actual element.",
229+
expected_items[expected_idx]
230+
))
231+
});
232+
233+
Description::new()
234+
.text("which does not have a perfect match. The best match found was:")
235+
.collect(matches.chain(unmatched_actual).chain(unmatched_expected))
236+
}
237+
238+
fn describe(&self, matcher_result: MatcherResult) -> Description {
239+
Description::new()
240+
.text(format!(
241+
"{} all elements matching in any order:",
242+
if matcher_result.into() { "contains" } else { "doesn't contain" },
243+
))
244+
.nested(
245+
self.expected
246+
.into_iter()
247+
.map(|element| format!("{:?}", element))
248+
.collect::<Description>()
249+
.bullet_list(),
250+
)
251+
}
252+
}
253+
156254
#[cfg(test)]
157255
mod tests {
158256
use crate::matcher::MatcherResult;
@@ -284,4 +382,135 @@ mod tests {
284382
displays_as(eq("which contains the unexpected element \"C\""))
285383
)
286384
}
385+
386+
#[test]
387+
fn ignoring_order_match() -> Result<()> {
388+
verify_that!(vec!["a", "b"], container_eq(["b", "a"]).ignore_order())
389+
}
390+
391+
#[test]
392+
fn ignoring_order_mismatch() -> Result<()> {
393+
verify_that!(vec!["a", "b"], not(container_eq(["1", "2"]).ignore_order()))
394+
}
395+
396+
#[test]
397+
fn ignoring_order_mismatch_explain() -> Result<()> {
398+
let expected_err = verify_that!(vec!["a", "b"], container_eq(["1", "2"]).ignore_order());
399+
verify_that!(
400+
expected_err,
401+
err(displays_as(contains_substring(indoc!(
402+
r#"
403+
Value of: vec!["a", "b"]
404+
Expected: contains all elements matching in any order:
405+
* "1"
406+
* "2"
407+
Actual: ["a", "b"],
408+
which does not have a perfect match. The best match found was:
409+
Actual element "a" at index 0 did not match any remaining expected element.
410+
Actual element "b" at index 1 did not match any remaining expected element.
411+
Expected element "1" at index 0 did not match any remaining actual element.
412+
Expected element "2" at index 1 did not match any remaining actual element.
413+
"#
414+
))))
415+
)
416+
}
417+
418+
#[test]
419+
fn ignoring_order_unaccounted_extra_expected() -> Result<()> {
420+
verify_that!(vec!["a", "b"], not(container_eq(["a", "b", "a"]).ignore_order()))
421+
}
422+
423+
#[test]
424+
fn ignoring_order_unaccounted_extra_expected_explain() -> Result<()> {
425+
let expected_err =
426+
verify_that!(vec!["a", "b"], container_eq(["a", "b", "a"]).ignore_order());
427+
verify_that!(
428+
expected_err,
429+
err(displays_as(contains_substring(indoc!(
430+
r#"
431+
Value of: vec!["a", "b"]
432+
Expected: contains all elements matching in any order:
433+
* "a"
434+
* "b"
435+
* "a"
436+
Actual: ["a", "b"],
437+
which does not have a perfect match. The best match found was:
438+
Actual element "a" at index 0 is equal to expected element at index 0.
439+
Actual element "b" at index 1 is equal to expected element at index 1.
440+
Expected element "a" at index 2 did not match any remaining actual element.
441+
"#
442+
))))
443+
)
444+
}
445+
446+
#[test]
447+
fn ignoring_order_unaccounted_extra_actual() -> Result<()> {
448+
verify_that!(vec!["a", "b", "a"], not(container_eq(["b", "a"]).ignore_order()))
449+
}
450+
451+
#[test]
452+
fn ignoring_order_unaccounted_extra_actual_explain() -> Result<()> {
453+
let expected_err =
454+
verify_that!(vec!["a", "b", "a"], container_eq(["b", "a"]).ignore_order());
455+
456+
verify_that!(
457+
expected_err,
458+
err(displays_as(contains_substring(indoc!(
459+
r#"
460+
Value of: vec!["a", "b", "a"]
461+
Expected: contains all elements matching in any order:
462+
* "b"
463+
* "a"
464+
Actual: ["a", "b", "a"],
465+
which does not have a perfect match. The best match found was:
466+
Actual element "a" at index 0 is equal to expected element at index 1.
467+
Actual element "b" at index 1 is equal to expected element at index 0.
468+
Actual element "a" at index 2 did not match any remaining expected element.
469+
"#
470+
))))
471+
)
472+
}
473+
474+
#[test]
475+
fn ignoring_order_on_sets() -> Result<()> {
476+
let mut actual = std::collections::HashSet::new();
477+
actual.insert("b");
478+
actual.insert("a");
479+
actual.insert("c");
480+
verify_that!(actual, container_eq(["c", "b", "a"]).ignore_order())
481+
}
482+
483+
#[test]
484+
fn ignoring_order_on_sets_explain() -> Result<()> {
485+
let mut actual = std::collections::HashSet::new();
486+
actual.insert("b");
487+
actual.insert("a");
488+
actual.insert("c");
489+
let expected_err = verify_that!(actual, container_eq(["c", "a"]).ignore_order());
490+
verify_that!(
491+
expected_err,
492+
err(displays_as(contains_regex(indoc!(
493+
r#"
494+
Value of: actual
495+
Expected: contains all elements matching in any order:
496+
\* "c"
497+
\* "a"
498+
Actual: \{"\w", "\w", "\w"\},
499+
which does not have a perfect match. The best match found was:
500+
Actual element "\w" at index \d is equal to expected element at index \d\.
501+
Actual element "\w" at index \d is equal to expected element at index \d\.
502+
Actual element "\w" at index \d did not match any remaining expected element\.
503+
"#
504+
))))
505+
)
506+
}
507+
508+
#[test]
509+
fn ignoring_order_on_number_sets() -> Result<()> {
510+
let mut actual = std::collections::HashSet::new();
511+
actual.insert(1);
512+
actual.insert(2);
513+
actual.insert(3);
514+
verify_that!(actual, container_eq([3, 2, 1]).ignore_order())
515+
}
287516
}

0 commit comments

Comments
 (0)