1 // Copyright 2022 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // There are no visible documentation elements in this module; the declarative 16 // macro is documented in the matchers module. 17 #![doc(hidden)] 18 19 /// Matches a container whose elements in any order have a 1:1 correspondence 20 /// with the provided element matchers. 21 /// 22 /// ``` 23 /// # use googletest::prelude::*; 24 /// # fn should_pass() -> Result<()> { 25 /// verify_that!(vec![3, 2, 1], unordered_elements_are![eq(1), ge(2), anything()])?; // Passes 26 /// # Ok(()) 27 /// # } 28 /// # fn should_fail_1() -> Result<()> { 29 /// verify_that!(vec![1], unordered_elements_are![eq(1), ge(2)])?; // Fails: container has wrong size 30 /// # Ok(()) 31 /// # } 32 /// # fn should_fail_2() -> Result<()> { 33 /// verify_that!(vec![3, 2, 1], unordered_elements_are![eq(1), ge(4), eq(2)])?; // Fails: second matcher not matched 34 /// # Ok(()) 35 /// # } 36 /// # fn should_fail_3() -> Result<()> { 37 /// verify_that!(vec![3, 2, 1], unordered_elements_are![ge(3), ge(3), ge(3)])?; // Fails: no 1:1 correspondence 38 /// # Ok(()) 39 /// # } 40 /// # should_pass().unwrap(); 41 /// # should_fail_1().unwrap_err(); 42 /// # should_fail_2().unwrap_err(); 43 /// # should_fail_3().unwrap_err(); 44 /// ``` 45 /// 46 /// The actual value must be a container such as a `Vec`, an array, or a 47 /// dereferenced slice. More precisely, a shared borrow of the actual value must 48 /// implement [`IntoIterator`]. 49 /// 50 /// This can also match against [`HashMap`][std::collections::HashMap] and 51 /// similar collections. The arguments are a sequence of pairs of matchers 52 /// corresponding to the keys and their respective values. 53 /// 54 /// ``` 55 /// # use googletest::prelude::*; 56 /// # use std::collections::HashMap; 57 /// let value: HashMap<u32, &'static str> = 58 /// HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]); 59 /// verify_that!( 60 /// value, 61 /// unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))] 62 /// ) 63 /// # .unwrap(); 64 /// ``` 65 /// 66 /// This can also be omitted in [`verify_that!`] macros and replaced with curly 67 /// brackets. 68 /// 69 /// ``` 70 /// # use googletest::prelude::*; 71 /// verify_that!(vec![1, 2], {eq(2), eq(1)}) 72 /// # .unwrap(); 73 /// ``` 74 /// 75 /// Note: This behavior is only possible in [`verify_that!`] macros. In any 76 /// other cases, it is still necessary to use the 77 /// [`unordered_elements_are!`][crate::matchers::unordered_elements_are] macro. 78 /// 79 /// ```compile_fail 80 /// # use googletest::prelude::*; 81 /// verify_that!(vec![vec![1,2], vec![3]], {{eq(2), eq(1)}, {eq(3)}}) 82 /// # .unwrap(); 83 /// ``` 84 /// 85 /// Use this instead: 86 /// ``` 87 /// # use googletest::prelude::*; 88 /// verify_that!(vec![vec![1,2], vec![3]], 89 /// {unordered_elements_are![eq(2), eq(1)], unordered_elements_are![eq(3)]}) 90 /// # .unwrap(); 91 /// ``` 92 /// 93 /// This matcher does not support matching directly against an [`Iterator`]. To 94 /// match against an iterator, use [`Iterator::collect`] to build a [`Vec`]. 95 /// 96 /// The matcher proceeds in three stages: 97 /// 98 /// 1. It first checks whether the actual value is of the right size to possibly 99 /// be matched by each of the given matchers. If not, then it immediately 100 /// fails explaining that the size is incorrect. 101 /// 102 /// 2. It then checks whether each matcher matches at least one corresponding 103 /// element in the actual container and each element in the actual container 104 /// is matched by at least one matcher. If not, it fails with a message 105 /// indicating which matcher respectively container elements had no 106 /// counterparts. 107 /// 108 /// 3. Finally, it checks whether the mapping of matchers to corresponding 109 /// actual elements is a 1-1 correspondence and fails if that is not the 110 /// case. The failure message then shows the best matching it could find, 111 /// including which matchers did not have corresponding unique elements in 112 /// the container and which container elements had no corresponding matchers. 113 /// 114 /// [`IntoIterator`]: std::iter::IntoIterator 115 /// [`Iterator`]: std::iter::Iterator 116 /// [`Iterator::collect`]: std::iter::Iterator::collect 117 /// [`Vec`]: std::vec::Vec 118 #[macro_export] 119 #[doc(hidden)] 120 macro_rules! __unordered_elements_are { 121 ($(,)?) => {{ 122 use $crate::matchers::__internal_unstable_do_not_depend_on_these::{ 123 UnorderedElementsAreMatcher, Requirements 124 }; 125 UnorderedElementsAreMatcher::new([], Requirements::PerfectMatch) 126 }}; 127 128 // TODO: Consider an alternative map-like syntax here similar to that used in 129 // https://crates.io/crates/maplit. 130 ($(($key_matcher:expr, $value_matcher:expr)),* $(,)?) => {{ 131 use $crate::matchers::__internal_unstable_do_not_depend_on_these::{ 132 UnorderedElementsOfMapAreMatcher, Requirements 133 }; 134 UnorderedElementsOfMapAreMatcher::new( 135 [$((Box::new($key_matcher), Box::new($value_matcher))),*], 136 Requirements::PerfectMatch 137 ) 138 }}; 139 140 ($($matcher:expr),* $(,)?) => {{ 141 use $crate::matchers::__internal_unstable_do_not_depend_on_these::{ 142 UnorderedElementsAreMatcher, Requirements 143 }; 144 UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::PerfectMatch) 145 }}; 146 } 147 148 /// Matches a container containing elements matched by the given matchers. 149 /// 150 /// To match, each given matcher must have a corresponding element in the 151 /// container which it matches. There must be a mapping uniquely matching each 152 /// matcher to a container element. The container can, however, contain 153 /// additional elements that don't correspond to any matcher. 154 /// 155 /// Put another way, `contains_each!` matches if there is a subset of the actual 156 /// container which 157 /// [`unordered_elements_are`][crate::matchers::unordered_elements_are] would 158 /// match. 159 /// 160 /// ``` 161 /// # use googletest::prelude::*; 162 /// # fn should_pass() -> Result<()> { 163 /// verify_that!(vec![3, 2, 1], contains_each![eq(2), ge(3)])?; // Passes 164 /// verify_that!(vec![3, 2, 1], contains_each![ge(2), ge(2)])?; // Passes 165 /// # Ok(()) 166 /// # } 167 /// # fn should_fail_1() -> Result<()> { 168 /// verify_that!(vec![1], contains_each![eq(1), ge(2)])?; // Fails: container too small 169 /// # Ok(()) 170 /// # } 171 /// # fn should_fail_2() -> Result<()> { 172 /// verify_that!(vec![3, 2, 1], contains_each![eq(1), ge(4)])?; // Fails: second matcher unmatched 173 /// # Ok(()) 174 /// # } 175 /// # fn should_fail_3() -> Result<()> { 176 /// verify_that!(vec![3, 2, 1], contains_each![ge(3), ge(3), ge(3)])?; // Fails: no matching 177 /// # Ok(()) 178 /// # } 179 /// # should_pass().unwrap(); 180 /// # should_fail_1().unwrap_err(); 181 /// # should_fail_2().unwrap_err(); 182 /// # should_fail_3().unwrap_err(); 183 /// ``` 184 /// 185 /// The actual value must be a container such as a `Vec`, an array, or a 186 /// dereferenced slice. More precisely, a shared borrow of the actual value must 187 /// implement [`IntoIterator`]. 188 /// 189 /// This can also match against [`HashMap`][std::collections::HashMap] and 190 /// similar collections. The arguments are a sequence of pairs of matchers 191 /// corresponding to the keys and their respective values. 192 /// 193 /// ``` 194 /// # use googletest::prelude::*; 195 /// # use std::collections::HashMap; 196 /// let value: HashMap<u32, &'static str> = 197 /// HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]); 198 /// verify_that!(value, contains_each![(eq(2), eq("Two")), (eq(1), eq("One"))]) 199 /// # .unwrap(); 200 /// ``` 201 /// 202 /// This matcher does not support matching directly against an [`Iterator`]. To 203 /// match against an iterator, use [`Iterator::collect`] to build a [`Vec`]. 204 /// 205 /// The matcher proceeds in three stages: 206 /// 207 /// 1. It first checks whether the actual value is large enough to possibly be 208 /// matched by each of the given matchers. If not, then it immediately fails 209 /// explaining that the size is too small. 210 /// 211 /// 2. It then checks whether each matcher matches at least one corresponding 212 /// element in the actual container and fails if that is not the case. The 213 /// failure message indicates which matcher had no corresponding element. 214 /// 215 /// 3. Finally, it checks whether the mapping of matchers to corresponding 216 /// actual elements is 1-1 and fails if that is not the case. The failure 217 /// message then shows the best matching it could find, including which 218 /// matchers did not have corresponding unique elements in the container. 219 /// 220 /// [`IntoIterator`]: std::iter::IntoIterator 221 /// [`Iterator`]: std::iter::Iterator 222 /// [`Iterator::collect`]: std::iter::Iterator::collect 223 /// [`Vec`]: std::vec::Vec 224 #[macro_export] 225 #[doc(hidden)] 226 macro_rules! __contains_each { 227 ($(,)?) => {{ 228 use $crate::matchers::__internal_unstable_do_not_depend_on_these::{ 229 UnorderedElementsAreMatcher, Requirements 230 }; 231 UnorderedElementsAreMatcher::new([], Requirements::Superset) 232 }}; 233 234 // TODO: Consider an alternative map-like syntax here similar to that used in 235 // https://crates.io/crates/maplit. 236 ($(($key_matcher:expr, $value_matcher:expr)),* $(,)?) => {{ 237 use $crate::matchers::__internal_unstable_do_not_depend_on_these::{ 238 UnorderedElementsOfMapAreMatcher, Requirements 239 }; 240 UnorderedElementsOfMapAreMatcher::new( 241 [$((Box::new($key_matcher), Box::new($value_matcher))),*], 242 Requirements::Superset 243 ) 244 }}; 245 246 ($($matcher:expr),* $(,)?) => {{ 247 use $crate::matchers::__internal_unstable_do_not_depend_on_these::{ 248 UnorderedElementsAreMatcher, Requirements 249 }; 250 UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::Superset) 251 }} 252 } 253 254 /// Matches a container all of whose elements are matched by the given matchers. 255 /// 256 /// To match, each element in the container must have a corresponding matcher 257 /// which matches it. There must be a 1-1 mapping from container elements to 258 /// matchers, so that no matcher has more than one corresponding element. 259 /// 260 /// There may, however, be matchers not corresponding to any elements in the 261 /// container. 262 /// 263 /// Put another way, `is_contained_in!` matches if there is a subset of the 264 /// matchers which would match with 265 /// [`unordered_elements_are`][crate::matchers::unordered_elements_are]. 266 /// 267 /// ``` 268 /// # use googletest::prelude::*; 269 /// # fn should_pass() -> Result<()> { 270 /// verify_that!(vec![2, 1], is_contained_in![eq(1), ge(2)])?; // Passes 271 /// verify_that!(vec![2, 1], is_contained_in![ge(1), ge(1)])?; // Passes 272 /// # Ok(()) 273 /// # } 274 /// # fn should_fail_1() -> Result<()> { 275 /// verify_that!(vec![1, 2, 3], is_contained_in![eq(1), ge(2)])?; // Fails: container too large 276 /// # Ok(()) 277 /// # } 278 /// # fn should_fail_2() -> Result<()> { 279 /// verify_that!(vec![2, 1], is_contained_in![eq(1), ge(4)])?; // Fails: second matcher unmatched 280 /// # Ok(()) 281 /// # } 282 /// # fn should_fail_3() -> Result<()> { 283 /// verify_that!(vec![3, 1], is_contained_in![ge(3), ge(3), ge(3)])?; // Fails: no matching 284 /// # Ok(()) 285 /// # } 286 /// # should_pass().unwrap(); 287 /// # should_fail_1().unwrap_err(); 288 /// # should_fail_2().unwrap_err(); 289 /// # should_fail_3().unwrap_err(); 290 /// ``` 291 /// 292 /// The actual value must be a container such as a `Vec`, an array, or a 293 /// dereferenced slice. More precisely, a shared borrow of the actual value must 294 /// implement [`IntoIterator`]. 295 /// 296 /// This can also match against [`HashMap`][std::collections::HashMap] and 297 /// similar collections. The arguments are a sequence of pairs of matchers 298 /// corresponding to the keys and their respective values. 299 /// 300 /// ``` 301 /// # use googletest::prelude::*; 302 /// # use std::collections::HashMap; 303 /// let value: HashMap<u32, &'static str> = HashMap::from_iter([(1, "One"), (2, "Two")]); 304 /// verify_that!( 305 /// value, 306 /// is_contained_in![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))] 307 /// ) 308 /// # .unwrap(); 309 /// ``` 310 /// 311 /// This matcher does not support matching directly against an [`Iterator`]. To 312 /// match against an iterator, use [`Iterator::collect`] to build a [`Vec`]. 313 /// 314 /// The matcher proceeds in three stages: 315 /// 316 /// 1. It first checks whether the actual value is too large to possibly be 317 /// matched by each of the given matchers. If so, it immediately fails 318 /// explaining that the size is too large. 319 /// 320 /// 2. It then checks whether each actual container element is matched by at 321 /// least one matcher and fails if that is not the case. The failure message 322 /// indicates which element had no corresponding matcher. 323 /// 324 /// 3. Finally, it checks whether the mapping of elements to corresponding 325 /// matchers is 1-1 and fails if that is not the case. The failure message 326 /// then shows the best matching it could find, including which container 327 /// elements did not have corresponding matchers. 328 /// 329 /// [`IntoIterator`]: std::iter::IntoIterator 330 /// [`Iterator`]: std::iter::Iterator 331 /// [`Iterator::collect`]: std::iter::Iterator::collect 332 /// [`Vec`]: std::vec::Vec 333 #[macro_export] 334 #[doc(hidden)] 335 macro_rules! __is_contained_in { 336 ($(,)?) => {{ 337 use $crate::matchers::__internal_unstable_do_not_depend_on_these::{ 338 UnorderedElementsAreMatcher, Requirements 339 }; 340 UnorderedElementsAreMatcher::new([], Requirements::Subset) 341 }}; 342 343 // TODO: Consider an alternative map-like syntax here similar to that used in 344 // https://crates.io/crates/maplit. 345 ($(($key_matcher:expr, $value_matcher:expr)),* $(,)?) => {{ 346 use $crate::matchers::__internal_unstable_do_not_depend_on_these::{ 347 UnorderedElementsOfMapAreMatcher, Requirements 348 }; 349 UnorderedElementsOfMapAreMatcher::new( 350 [$((Box::new($key_matcher), Box::new($value_matcher))),*], 351 Requirements::Subset 352 ) 353 }}; 354 355 ($($matcher:expr),* $(,)?) => {{ 356 use $crate::matchers::__internal_unstable_do_not_depend_on_these::{ 357 UnorderedElementsAreMatcher, Requirements 358 }; 359 UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::Subset) 360 }} 361 } 362 363 /// Module for use only by the macros in this module. 364 /// 365 /// **For internal use only. API stablility is not guaranteed!** 366 #[doc(hidden)] 367 pub mod internal { 368 use crate::description::Description; 369 use crate::matcher::{Matcher, MatcherResult}; 370 use crate::matcher_support::count_elements::count_elements; 371 use std::collections::HashSet; 372 use std::fmt::{Debug, Display}; 373 use std::marker::PhantomData; 374 375 /// This struct is meant to be used only through the 376 /// `unordered_elements_are![...]` macro. 377 /// 378 /// **For internal use only. API stablility is not guaranteed!** 379 #[doc(hidden)] 380 pub struct UnorderedElementsAreMatcher<'a, ContainerT: ?Sized, T: Debug, const N: usize> { 381 elements: [Box<dyn Matcher<ActualT = T> + 'a>; N], 382 requirements: Requirements, 383 phantom: PhantomData<ContainerT>, 384 } 385 386 impl<'a, ContainerT: ?Sized, T: Debug, const N: usize> 387 UnorderedElementsAreMatcher<'a, ContainerT, T, N> 388 { new( elements: [Box<dyn Matcher<ActualT = T> + 'a>; N], requirements: Requirements, ) -> Self389 pub fn new( 390 elements: [Box<dyn Matcher<ActualT = T> + 'a>; N], 391 requirements: Requirements, 392 ) -> Self { 393 Self { elements, requirements, phantom: Default::default() } 394 } 395 } 396 397 // This matcher performs the checks in three different steps in both `matches` 398 // and `explain_match`. This is useful for performance but also to produce 399 // an actionable error message. 400 // 1. `UnorderedElementsAreMatcher` verifies that both collections have the same 401 // size 402 // 2. `UnorderedElementsAreMatcher` verifies that each actual element matches at 403 // least one expected element and vice versa. 404 // 3. `UnorderedElementsAreMatcher` verifies that a perfect matching exists 405 // using Ford-Fulkerson. 406 impl<'a, T: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher 407 for UnorderedElementsAreMatcher<'a, ContainerT, T, N> 408 where 409 for<'b> &'b ContainerT: IntoIterator<Item = &'b T>, 410 { 411 type ActualT = ContainerT; 412 matches(&self, actual: &ContainerT) -> MatcherResult413 fn matches(&self, actual: &ContainerT) -> MatcherResult { 414 let match_matrix = MatchMatrix::generate(actual, &self.elements); 415 match_matrix.is_match_for(self.requirements).into() 416 } 417 explain_match(&self, actual: &ContainerT) -> Description418 fn explain_match(&self, actual: &ContainerT) -> Description { 419 if let Some(size_mismatch_explanation) = 420 self.requirements.explain_size_mismatch(actual, N) 421 { 422 return size_mismatch_explanation; 423 } 424 425 let match_matrix = MatchMatrix::generate(actual, &self.elements); 426 if let Some(unmatchable_explanation) = 427 match_matrix.explain_unmatchable(self.requirements) 428 { 429 return unmatchable_explanation; 430 } 431 432 let best_match = match_matrix.find_best_match(); 433 best_match 434 .get_explanation(actual, &self.elements, self.requirements) 435 .unwrap_or("whose elements all match".into()) 436 } 437 describe(&self, matcher_result: MatcherResult) -> Description438 fn describe(&self, matcher_result: MatcherResult) -> Description { 439 format!( 440 "{} elements matching in any order:\n{}", 441 if matcher_result.into() { "contains" } else { "doesn't contain" }, 442 self.elements 443 .iter() 444 .map(|matcher| matcher.describe(MatcherResult::Match)) 445 .collect::<Description>() 446 .enumerate() 447 .indent() 448 ) 449 .into() 450 } 451 } 452 453 type KeyValueMatcher<'a, KeyT, ValueT> = 454 (Box<dyn Matcher<ActualT = KeyT> + 'a>, Box<dyn Matcher<ActualT = ValueT> + 'a>); 455 456 /// This is the analogue to [UnorderedElementsAreMatcher] for maps and 457 /// map-like collections. 458 /// 459 /// **For internal use only. API stablility is not guaranteed!** 460 #[doc(hidden)] 461 pub struct UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, const N: usize> 462 where 463 ContainerT: ?Sized, 464 KeyT: Debug, 465 ValueT: Debug, 466 { 467 elements: [KeyValueMatcher<'a, KeyT, ValueT>; N], 468 requirements: Requirements, 469 phantom: PhantomData<ContainerT>, 470 } 471 472 impl<'a, ContainerT, KeyT: Debug, ValueT: Debug, const N: usize> 473 UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, N> 474 { new( elements: [KeyValueMatcher<'a, KeyT, ValueT>; N], requirements: Requirements, ) -> Self475 pub fn new( 476 elements: [KeyValueMatcher<'a, KeyT, ValueT>; N], 477 requirements: Requirements, 478 ) -> Self { 479 Self { elements, requirements, phantom: Default::default() } 480 } 481 } 482 483 impl<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher 484 for UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, N> 485 where 486 for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>, 487 { 488 type ActualT = ContainerT; 489 matches(&self, actual: &ContainerT) -> MatcherResult490 fn matches(&self, actual: &ContainerT) -> MatcherResult { 491 let match_matrix = MatchMatrix::generate_for_map(actual, &self.elements); 492 match_matrix.is_match_for(self.requirements).into() 493 } 494 explain_match(&self, actual: &ContainerT) -> Description495 fn explain_match(&self, actual: &ContainerT) -> Description { 496 if let Some(size_mismatch_explanation) = 497 self.requirements.explain_size_mismatch(actual, N) 498 { 499 return size_mismatch_explanation; 500 } 501 502 let match_matrix = MatchMatrix::generate_for_map(actual, &self.elements); 503 if let Some(unmatchable_explanation) = 504 match_matrix.explain_unmatchable(self.requirements) 505 { 506 return unmatchable_explanation; 507 } 508 509 let best_match = match_matrix.find_best_match(); 510 511 best_match 512 .get_explanation_for_map(actual, &self.elements, self.requirements) 513 .unwrap_or("whose elements all match".into()) 514 } 515 describe(&self, matcher_result: MatcherResult) -> Description516 fn describe(&self, matcher_result: MatcherResult) -> Description { 517 format!( 518 "{} elements matching in any order:\n{}", 519 if matcher_result.into() { "contains" } else { "doesn't contain" }, 520 self.elements 521 .iter() 522 .map(|(key_matcher, value_matcher)| format!( 523 "{} => {}", 524 key_matcher.describe(MatcherResult::Match), 525 value_matcher.describe(MatcherResult::Match) 526 )) 527 .collect::<Description>() 528 .indent() 529 ) 530 .into() 531 } 532 } 533 534 /// The requirements of the mapping between matchers and actual values by 535 /// which [`UnorderedElemetnsAre`] is deemed to match its input. 536 /// 537 /// **For internal use only. API stablility is not guaranteed!** 538 #[doc(hidden)] 539 #[derive(Clone, Copy)] 540 pub enum Requirements { 541 /// There must be a 1:1 correspondence between the actual values and the 542 /// matchers. 543 PerfectMatch, 544 545 /// The mapping from matched actual values to their corresponding 546 /// matchers must be surjective. 547 Superset, 548 549 /// The mapping from matchers to matched actual values must be 550 /// surjective. 551 Subset, 552 } 553 554 impl Requirements { explain_size_mismatch<ContainerT: ?Sized>( &self, actual: &ContainerT, expected_size: usize, ) -> Option<Description> where for<'b> &'b ContainerT: IntoIterator,555 fn explain_size_mismatch<ContainerT: ?Sized>( 556 &self, 557 actual: &ContainerT, 558 expected_size: usize, 559 ) -> Option<Description> 560 where 561 for<'b> &'b ContainerT: IntoIterator, 562 { 563 let actual_size = count_elements(actual); 564 match self { 565 Requirements::PerfectMatch if actual_size != expected_size => Some( 566 format!("which has size {} (expected {})", actual_size, expected_size).into(), 567 ), 568 569 Requirements::Superset if actual_size < expected_size => Some( 570 format!("which has size {} (expected at least {})", actual_size, expected_size) 571 .into(), 572 ), 573 574 Requirements::Subset if actual_size > expected_size => Some( 575 format!("which has size {} (expected at most {})", actual_size, expected_size) 576 .into(), 577 ), 578 579 _ => None, 580 } 581 } 582 } 583 584 impl Display for Requirements { fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result585 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 586 match self { 587 Requirements::PerfectMatch => { 588 write!(f, "perfect") 589 } 590 Requirements::Superset => { 591 write!(f, "superset") 592 } 593 Requirements::Subset => { 594 write!(f, "subset") 595 } 596 } 597 } 598 } 599 600 /// The bipartite matching graph between actual and expected elements. 601 struct MatchMatrix<const N: usize>(Vec<[MatcherResult; N]>); 602 603 impl<const N: usize> MatchMatrix<N> { generate<'a, T: Debug + 'a, ContainerT: Debug + ?Sized>( actual: &ContainerT, expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N], ) -> Self where for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,604 fn generate<'a, T: Debug + 'a, ContainerT: Debug + ?Sized>( 605 actual: &ContainerT, 606 expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N], 607 ) -> Self 608 where 609 for<'b> &'b ContainerT: IntoIterator<Item = &'b T>, 610 { 611 let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; count_elements(actual)]); 612 for (actual_idx, actual) in actual.into_iter().enumerate() { 613 for (expected_idx, expected) in expected.iter().enumerate() { 614 matrix.0[actual_idx][expected_idx] = expected.matches(actual); 615 } 616 } 617 matrix 618 } 619 generate_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>( actual: &ContainerT, expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N], ) -> Self where for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,620 fn generate_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>( 621 actual: &ContainerT, 622 expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N], 623 ) -> Self 624 where 625 for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>, 626 { 627 let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; count_elements(actual)]); 628 for (actual_idx, (actual_key, actual_value)) in actual.into_iter().enumerate() { 629 for (expected_idx, (expected_key, expected_value)) in expected.iter().enumerate() { 630 matrix.0[actual_idx][expected_idx] = (expected_key.matches(actual_key).into() 631 && expected_value.matches(actual_value).into()) 632 .into(); 633 } 634 } 635 matrix 636 } 637 is_match_for(&self, requirements: Requirements) -> bool638 fn is_match_for(&self, requirements: Requirements) -> bool { 639 match requirements { 640 Requirements::PerfectMatch => { 641 !self.find_unmatchable_elements().has_unmatchable_elements() 642 && self.find_best_match().is_full_match() 643 } 644 Requirements::Superset => { 645 !self.find_unmatched_expected().has_unmatchable_elements() 646 && self.find_best_match().is_superset_match() 647 } 648 Requirements::Subset => { 649 !self.find_unmatched_actual().has_unmatchable_elements() 650 && self.find_best_match().is_subset_match() 651 } 652 } 653 } 654 explain_unmatchable(&self, requirements: Requirements) -> Option<Description>655 fn explain_unmatchable(&self, requirements: Requirements) -> Option<Description> { 656 let unmatchable_elements = match requirements { 657 Requirements::PerfectMatch => self.find_unmatchable_elements(), 658 Requirements::Superset => self.find_unmatched_expected(), 659 Requirements::Subset => self.find_unmatched_actual(), 660 }; 661 unmatchable_elements.get_explanation() 662 } 663 664 // Verifies that each actual matches at least one expected and that 665 // each expected matches at least one actual. 666 // This is a necessary condition but not sufficient. But it is faster 667 // than `find_best_match()`. find_unmatchable_elements(&self) -> UnmatchableElements<N>668 fn find_unmatchable_elements(&self) -> UnmatchableElements<N> { 669 let unmatchable_actual = 670 self.0.iter().map(|row| row.iter().all(|&e| e.is_no_match())).collect(); 671 let mut unmatchable_expected = [false; N]; 672 for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() { 673 *expected = self.0.iter().map(|row| row[col_idx]).all(|e| e.is_no_match()); 674 } 675 UnmatchableElements { unmatchable_actual, unmatchable_expected } 676 } 677 find_unmatched_expected(&self) -> UnmatchableElements<N>678 fn find_unmatched_expected(&self) -> UnmatchableElements<N> { 679 let mut unmatchable_expected = [false; N]; 680 for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() { 681 *expected = self.0.iter().map(|row| row[col_idx]).all(|e| e.is_no_match()); 682 } 683 UnmatchableElements { unmatchable_actual: vec![false; N], unmatchable_expected } 684 } 685 find_unmatched_actual(&self) -> UnmatchableElements<N>686 fn find_unmatched_actual(&self) -> UnmatchableElements<N> { 687 let unmatchable_actual = 688 self.0.iter().map(|row| row.iter().all(|e| e.is_no_match())).collect(); 689 UnmatchableElements { unmatchable_actual, unmatchable_expected: [false; N] } 690 } 691 692 // Verifies that a full match exists. 693 // 694 // Uses the well-known Ford-Fulkerson max flow method to find a maximum 695 // bipartite matching. Flow is considered to be from actual to expected. 696 // There is an implicit source node that is connected to all of the actual 697 // nodes, and an implicit sink node that is connected to all of the 698 // expected nodes. All edges have unit capacity. 699 // 700 // Neither the flow graph nor the residual flow graph are represented 701 // explicitly. Instead, they are implied by the information in `self.0` and 702 // the local `actual_match : [Option<usize>; N]` whose elements are initialized 703 // to `None`. This represents the initial state of the algorithm, 704 // where the flow graph is empty, and the residual flow graph has the 705 // following edges: 706 // - An edge from source to each actual element node 707 // - An edge from each expected element node to sink 708 // - An edge from each actual element node to each expected element node, if 709 // the actual element matches the expected element, i.e. 710 // `matches!(self.0[actual_id][expected_id], Matches)` 711 // 712 // When the `try_augment(...)` method adds a flow, it sets `actual_match[l] = 713 // Some(r)` for some nodes l and r. This induces the following changes: 714 // - The edges (source, l), (l, r), and (r, sink) are added to the flow graph. 715 // - The same three edges are removed from the residual flow graph. 716 // - The reverse edges (l, source), (r, l), and (sink, r) are added to the 717 // residual flow graph, which is a directional graph representing unused 718 // flow capacity. 719 // 720 // When the method augments a flow (changing `actual_match[l]` from `Some(r1)` 721 // to `Some(r2)`), this can be thought of as "undoing" the above steps 722 // with respect to r1 and "redoing" them with respect to r2. 723 // 724 // It bears repeating that the flow graph and residual flow graph are 725 // never represented explicitly, but can be derived by looking at the 726 // information in 'self.0' and in `actual_match`. 727 // 728 // As an optimization, there is a second local `expected_match: [Option<usize>; 729 // N]` which does not provide any new information. Instead, it enables 730 // more efficient queries about edges entering or leaving the expected elements 731 // nodes of the flow or residual flow graphs. The following invariants 732 // are maintained: 733 // 734 // actual_match[a] == None or expected_match[actual_match[a].unwrap()] == 735 // Some(a) 736 // expected_match[r] == None or actual_match[expected_match[e].unwrap()] == 737 // Some(e) 738 // 739 // | [ source ] | 740 // | ||| | 741 // | ||| | 742 // | ||\-> actual_match[0]=Some(1) -\ expected_match[0]=None ---\ | 743 // | || | | | 744 // | |\--> actual_match[1]=None \-> expected_match[1]=Some(0) --\| | 745 // | | || | 746 // | \---> actual_match[2]=Some(2) --> expected_match[2]=Some(2) -\|| | 747 // | ||| | 748 // | elements matchers vvv | 749 // | [ sink ] | 750 // 751 // See Also: 752 // [1] Cormen, et al (2001). "Section 26.2: The Ford-Fulkerson method". 753 // "Introduction to Algorithms (Second ed.)", pp. 651-664. 754 // [2] "Ford-Fulkerson algorithm", Wikipedia, 755 // 'http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm' find_best_match(&self) -> BestMatch<N>756 fn find_best_match(&self) -> BestMatch<N> { 757 let mut actual_match = vec![None; self.0.len()]; 758 let mut expected_match: [Option<usize>; N] = [None; N]; 759 // Searches the residual flow graph for a path from each actual node to 760 // the sink in the residual flow graph, and if one is found, add this path 761 // to the graph. 762 // It's okay to search through the actual nodes once. The 763 // edge from the implicit source node to each previously-visited actual 764 // node will have flow if that actual node has any path to the sink 765 // whatsoever. Subsequent augmentations can only add flow to the 766 // network, and cannot take away that previous flow unit from the source. 767 // Since the source-to-actual edge can only carry one flow unit (or, 768 // each actual element can be matched to only one expected element), there is no 769 // need to visit the actual nodes more than once looking for 770 // augmented paths. The flow is known to be possible or impossible 771 // by looking at the node once. 772 for actual_idx in 0..self.0.len() { 773 assert!(actual_match[actual_idx].is_none()); 774 let mut seen = [false; N]; 775 self.try_augment(actual_idx, &mut seen, &mut actual_match, &mut expected_match); 776 } 777 BestMatch(actual_match) 778 } 779 780 // Perform a depth-first search from actual node `actual_idx` to the sink by 781 // searching for an unassigned expected node. If a path is found, flow 782 // is added to the network by linking the actual and expected vector elements 783 // corresponding each segment of the path. Returns true if a path to 784 // sink was found, which means that a unit of flow was added to the 785 // network. The 'seen' array elements correspond to expected nodes and are 786 // marked to eliminate cycles from the search. 787 // 788 // Actual nodes will only be explored at most once because they 789 // are accessible from at most one expected node in the residual flow 790 // graph. 791 // 792 // Note that `actual_match[actual_idx]` is the only element of `actual_match` 793 // that `try_augment(...)` will potentially transition from `None` to 794 // `Some(...)`. Any other `actual_match` element holding `None` before 795 // `try_augment(...)` will be holding it when `try_augment(...)` 796 // returns. 797 // try_augment( &self, actual_idx: usize, seen: &mut [bool; N], actual_match: &mut [Option<usize>], expected_match: &mut [Option<usize>; N], ) -> bool798 fn try_augment( 799 &self, 800 actual_idx: usize, 801 seen: &mut [bool; N], 802 actual_match: &mut [Option<usize>], 803 expected_match: &mut [Option<usize>; N], 804 ) -> bool { 805 for expected_idx in 0..N { 806 if seen[expected_idx] { 807 continue; 808 } 809 if self.0[actual_idx][expected_idx].is_no_match() { 810 continue; 811 } 812 // There is an edge between `actual_idx` and `expected_idx`. 813 seen[expected_idx] = true; 814 // Next a search is performed to determine whether 815 // this edge is a dead end or leads to the sink. 816 // 817 // `expected_match[expected_idx].is_none()` means that there is residual flow 818 // from expected node at index expected_idx to the sink, so we 819 // can use that to finish this flow path and return success. 820 // 821 // Otherwise, we look for a residual flow starting from 822 // `expected_match[expected_idx].unwrap()` by calling 823 // ourselves recursively to see if this ultimately leads to 824 // sink. 825 if expected_match[expected_idx].is_none() 826 || self.try_augment( 827 expected_match[expected_idx].unwrap(), 828 seen, 829 actual_match, 830 expected_match, 831 ) 832 { 833 // We found a residual flow from source to sink. We thus need to add the new 834 // edge to the current flow. 835 // Note: this also remove the potential flow that existed by overwriting the 836 // value in the `expected_match` and `actual_match`. 837 expected_match[expected_idx] = Some(actual_idx); 838 actual_match[actual_idx] = Some(expected_idx); 839 return true; 840 } 841 } 842 false 843 } 844 } 845 846 /// The list of elements that do not match any element in the corresponding 847 /// set. 848 /// These lists are represented as fixed sized bit set to avoid 849 /// allocation. 850 /// TODO(bjacotg) Use BitArr!(for N) once generic_const_exprs is stable. 851 struct UnmatchableElements<const N: usize> { 852 unmatchable_actual: Vec<bool>, 853 unmatchable_expected: [bool; N], 854 } 855 856 impl<const N: usize> UnmatchableElements<N> { has_unmatchable_elements(&self) -> bool857 fn has_unmatchable_elements(&self) -> bool { 858 self.unmatchable_actual.iter().any(|b| *b) 859 || self.unmatchable_expected.iter().any(|b| *b) 860 } 861 get_explanation(&self) -> Option<Description>862 fn get_explanation(&self) -> Option<Description> { 863 let unmatchable_actual = self.unmatchable_actual(); 864 let actual_idx = unmatchable_actual 865 .iter() 866 .map(|idx| format!("#{}", idx)) 867 .collect::<Vec<_>>() 868 .join(", "); 869 let unmatchable_expected = self.unmatchable_expected(); 870 let expected_idx = unmatchable_expected 871 .iter() 872 .map(|idx| format!("#{}", idx)) 873 .collect::<Vec<_>>() 874 .join(", "); 875 match (unmatchable_actual.len(), unmatchable_expected.len()) { 876 (0, 0) => None, 877 (1, 0) => { 878 Some(format!("whose element {actual_idx} does not match any expected elements").into()) 879 } 880 (_, 0) => { 881 Some(format!("whose elements {actual_idx} do not match any expected elements",).into()) 882 } 883 (0, 1) => Some(format!( 884 "which has no element matching the expected element {expected_idx}" 885 ).into()), 886 (0, _) => Some(format!( 887 "which has no elements matching the expected elements {expected_idx}" 888 ).into()), 889 (1, 1) => Some(format!( 890 "whose element {actual_idx} does not match any expected elements and no elements match the expected element {expected_idx}" 891 ).into()), 892 (_, 1) => Some(format!( 893 "whose elements {actual_idx} do not match any expected elements and no elements match the expected element {expected_idx}" 894 ).into()), 895 (1, _) => Some(format!( 896 "whose element {actual_idx} does not match any expected elements and no elements match the expected elements {expected_idx}" 897 ).into()), 898 (_, _) => Some(format!( 899 "whose elements {actual_idx} do not match any expected elements and no elements match the expected elements {expected_idx}" 900 ).into()), 901 } 902 } 903 unmatchable_actual(&self) -> Vec<usize>904 fn unmatchable_actual(&self) -> Vec<usize> { 905 self.unmatchable_actual 906 .iter() 907 .enumerate() 908 .filter_map(|(idx, b)| if *b { Some(idx) } else { None }) 909 .collect() 910 } 911 unmatchable_expected(&self) -> Vec<usize>912 fn unmatchable_expected(&self) -> Vec<usize> { 913 self.unmatchable_expected 914 .iter() 915 .enumerate() 916 .filter_map(|(idx, b)| if *b { Some(idx) } else { None }) 917 .collect() 918 } 919 } 920 921 /// The representation of a match between actual and expected. 922 /// The value at idx represents to which expected the actual at idx is 923 /// matched with. For example, `BestMatch([Some(0), None, Some(1)])` 924 /// means: 925 /// * The 0th element in actual matches the 0th element in expected. 926 /// * The 1st element in actual does not match. 927 /// * The 2nd element in actual matches the 1st element in expected. 928 struct BestMatch<const N: usize>(Vec<Option<usize>>); 929 930 impl<const N: usize> BestMatch<N> { is_full_match(&self) -> bool931 fn is_full_match(&self) -> bool { 932 self.0.iter().all(|o| o.is_some()) 933 } 934 is_subset_match(&self) -> bool935 fn is_subset_match(&self) -> bool { 936 self.is_full_match() 937 } 938 is_superset_match(&self) -> bool939 fn is_superset_match(&self) -> bool { 940 self.get_unmatched_expected().is_empty() 941 } 942 get_matches(&self) -> impl Iterator<Item = (usize, usize)> + '_943 fn get_matches(&self) -> impl Iterator<Item = (usize, usize)> + '_ { 944 self.0.iter().enumerate().filter_map(|(actual_idx, maybe_expected_idx)| { 945 maybe_expected_idx.map(|expected_idx| (actual_idx, expected_idx)) 946 }) 947 } 948 get_unmatched_actual(&self) -> impl Iterator<Item = usize> + '_949 fn get_unmatched_actual(&self) -> impl Iterator<Item = usize> + '_ { 950 self.0 951 .iter() 952 .enumerate() 953 .filter(|&(_, o)| o.is_none()) 954 .map(|(actual_idx, _)| actual_idx) 955 } 956 get_unmatched_expected(&self) -> Vec<usize>957 fn get_unmatched_expected(&self) -> Vec<usize> { 958 let matched_expected: HashSet<_> = self.0.iter().flatten().collect(); 959 (0..N).filter(|expected_idx| !matched_expected.contains(expected_idx)).collect() 960 } 961 get_explanation<'a, T: Debug, ContainerT: Debug + ?Sized>( &self, actual: &ContainerT, expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N], requirements: Requirements, ) -> Option<Description> where for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,962 fn get_explanation<'a, T: Debug, ContainerT: Debug + ?Sized>( 963 &self, 964 actual: &ContainerT, 965 expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N], 966 requirements: Requirements, 967 ) -> Option<Description> 968 where 969 for<'b> &'b ContainerT: IntoIterator<Item = &'b T>, 970 { 971 let actual: Vec<_> = actual.into_iter().collect(); 972 if self.is_full_match() { 973 return None; 974 } 975 let mut error_message = 976 format!("which does not have a {requirements} match with the expected elements."); 977 978 error_message.push_str("\n The best match found was: "); 979 980 let matches = self.get_matches().map(|(actual_idx, expected_idx)|{ 981 format!( 982 "Actual element {:?} at index {actual_idx} matched expected element `{}` at index {expected_idx}.", 983 actual[actual_idx], 984 expected[expected_idx].describe(MatcherResult::Match), 985 )}); 986 987 let unmatched_actual = self.get_unmatched_actual().map(|actual_idx| { 988 format!( 989 "Actual element {:#?} at index {actual_idx} did not match any remaining expected element.", 990 actual[actual_idx] 991 ) 992 }); 993 994 let unmatched_expected = self.get_unmatched_expected().into_iter().map(|expected_idx|{format!( 995 "Expected element `{}` at index {expected_idx} did not match any remaining actual element.", 996 expected[expected_idx].describe(MatcherResult::Match) 997 )}); 998 999 let best_match = matches 1000 .chain(unmatched_actual) 1001 .chain(unmatched_expected) 1002 .collect::<Description>() 1003 .indent(); 1004 Some(format!( 1005 "which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}" 1006 ).into()) 1007 } 1008 get_explanation_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>( &self, actual: &ContainerT, expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N], requirements: Requirements, ) -> Option<Description> where for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,1009 fn get_explanation_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>( 1010 &self, 1011 actual: &ContainerT, 1012 expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N], 1013 requirements: Requirements, 1014 ) -> Option<Description> 1015 where 1016 for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>, 1017 { 1018 let actual: Vec<_> = actual.into_iter().collect(); 1019 if self.is_full_match() { 1020 return None; 1021 } 1022 let mut error_message = 1023 format!("which does not have a {requirements} match with the expected elements."); 1024 1025 error_message.push_str("\n The best match found was: "); 1026 1027 let matches = self.get_matches() 1028 .map(|(actual_idx, expected_idx)| { 1029 format!( 1030 "Actual element {:?} => {:?} at index {actual_idx} matched expected element `{}` => `{}` at index {expected_idx}.", 1031 actual[actual_idx].0, 1032 actual[actual_idx].1, 1033 expected[expected_idx].0.describe(MatcherResult::Match), 1034 expected[expected_idx].1.describe(MatcherResult::Match), 1035 ) 1036 }); 1037 1038 let unmatched_actual = self.get_unmatched_actual() 1039 .map(|actual_idx| { 1040 format!( 1041 "Actual element {:#?} => {:#?} at index {actual_idx} did not match any remaining expected element.", 1042 actual[actual_idx].0, 1043 actual[actual_idx].1, 1044 ) 1045 }); 1046 1047 let unmatched_expected = self.get_unmatched_expected() 1048 .into_iter() 1049 .map(|expected_idx| { 1050 format!( 1051 "Expected element `{}` => `{}` at index {expected_idx} did not match any remaining actual element.", 1052 expected[expected_idx].0.describe(MatcherResult::Match), 1053 expected[expected_idx].1.describe(MatcherResult::Match), 1054 ) 1055 }); 1056 1057 let best_match = matches 1058 .chain(unmatched_actual) 1059 .chain(unmatched_expected) 1060 .collect::<Description>() 1061 .indent(); 1062 Some(format!( 1063 "which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}" 1064 ).into()) 1065 } 1066 } 1067 } 1068 1069 #[cfg(test)] 1070 mod tests { 1071 use super::internal::UnorderedElementsOfMapAreMatcher; 1072 use crate::matcher::{Matcher, MatcherResult}; 1073 use crate::prelude::*; 1074 use indoc::indoc; 1075 use std::collections::HashMap; 1076 1077 #[test] has_correct_description_for_map() -> Result<()>1078 fn has_correct_description_for_map() -> Result<()> { 1079 // UnorderedElementsAreMatcher maintains references to the matchers, so the 1080 // constituent matchers must live longer. Inside a verify_that! macro, the 1081 // compiler takes care of that, but when the matcher is created separately, 1082 // we must create the constitute matchers separately so that they 1083 // aren't dropped too early. 1084 let matchers = ((eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))); 1085 let matcher: UnorderedElementsOfMapAreMatcher<HashMap<i32, &str>, _, _, 3> = unordered_elements_are![ 1086 (matchers.0.0, matchers.0.1), 1087 (matchers.1.0, matchers.1.1), 1088 (matchers.2.0, matchers.2.1) 1089 ]; 1090 verify_that!( 1091 Matcher::describe(&matcher, MatcherResult::Match), 1092 displays_as(eq(indoc!( 1093 " 1094 contains elements matching in any order: 1095 is equal to 2 => is equal to \"Two\" 1096 is equal to 1 => is equal to \"One\" 1097 is equal to 3 => is equal to \"Three\"" 1098 ))) 1099 ) 1100 } 1101 1102 #[test] unordered_elements_are_description_no_full_match_with_map() -> Result<()>1103 fn unordered_elements_are_description_no_full_match_with_map() -> Result<()> { 1104 // UnorderedElementsAreMatcher maintains references to the matchers, so the 1105 // constituent matchers must live longer. Inside a verify_that! macro, the 1106 // compiler takes care of that, but when the matcher is created separately, 1107 // we must create the constitute matchers separately so that they 1108 // aren't dropped too early. 1109 let matchers = ((anything(), eq(1)), (anything(), eq(2)), (anything(), eq(2))); 1110 let matcher: UnorderedElementsOfMapAreMatcher<HashMap<u32, u32>, _, _, 3> = unordered_elements_are![ 1111 (matchers.0.0, matchers.0.1), 1112 (matchers.1.0, matchers.1.1), 1113 (matchers.2.0, matchers.2.1), 1114 ]; 1115 let value: HashMap<u32, u32> = HashMap::from_iter([(0, 1), (1, 1), (2, 2)]); 1116 verify_that!( 1117 matcher.explain_match(&value), 1118 displays_as(contains_regex( 1119 "Actual element 2 => 2 at index [0-2] matched expected element `is anything` => `is equal to 2` at index [0-2]." 1120 )).and(displays_as(contains_regex( 1121 "Actual element [0-1] => [0-1] at index [0-2] did not match any remaining expected element." 1122 ))).and(displays_as(contains_substring( 1123 "Expected element `is anything` => `is equal to 2` at index 2 did not match any remaining actual element." 1124 ))) 1125 ) 1126 } 1127 } 1128