29
29
/// The actual value must be a container implementing [`IntoIterator`]. This
30
30
/// includes standard containers, slices (when dereferenced) and arrays.
31
31
///
32
+ /// This can also match against [`HashMap`][std::collections::HashMap] and
33
+ /// similar collections. The arguments are a sequence of pairs of matchers
34
+ /// corresponding to the keys and their respective values.
35
+ ///
36
+ /// ```
37
+ /// let value: HashMap<u32, &'static str> =
38
+ /// HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]);
39
+ /// verify_that!(
40
+ /// value,
41
+ /// unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
42
+ /// )
43
+ /// ```
44
+ ///
32
45
/// This matcher does not support matching directly against an [`Iterator`]. To
33
46
/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`].
34
47
///
57
70
/// [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html
58
71
#[ macro_export]
59
72
macro_rules! unordered_elements_are {
73
+ ( $( , ) ?) => { {
74
+ #[ cfg( google3) ]
75
+ use $crate:: internal:: { UnorderedElementsAreMatcher , Requirements } ;
76
+ #[ cfg( not( google3) ) ]
77
+ use $crate:: matchers:: unordered_elements_are_matcher:: internal:: {
78
+ UnorderedElementsAreMatcher , Requirements
79
+ } ;
80
+ UnorderedElementsAreMatcher :: new( [ ] , Requirements :: PerfectMatch )
81
+ } } ;
82
+
83
+ // TODO: Consider an alternative map-like syntax here similar to that used in
84
+ // https://crates.io/crates/maplit.
85
+ ( $( ( $key_matcher: expr, $value_matcher: expr) ) ,* $( , ) ?) => { {
86
+ #[ cfg( google3) ]
87
+ use $crate:: internal:: { UnorderedElementsOfMapAreMatcher , Requirements } ;
88
+ #[ cfg( not( google3) ) ]
89
+ use $crate:: matchers:: unordered_elements_are_matcher:: internal:: {
90
+ UnorderedElementsOfMapAreMatcher , Requirements
91
+ } ;
92
+ UnorderedElementsOfMapAreMatcher :: new(
93
+ [ $( ( & $key_matcher, & $value_matcher) ) ,* ] ,
94
+ Requirements :: PerfectMatch
95
+ )
96
+ } } ;
97
+
60
98
( $( $matcher: expr) ,* $( , ) ?) => { {
61
99
#[ cfg( google3) ]
62
- use $crate:: internal:: { UnorderedElementsAre , Requirements } ;
100
+ use $crate:: internal:: { UnorderedElementsAreMatcher , Requirements } ;
63
101
#[ cfg( not( google3) ) ]
64
- use $crate:: matchers:: unordered_elements_are_matcher:: internal:: { UnorderedElementsAre , Requirements } ;
65
- UnorderedElementsAre :: new( [ $( & $matcher) ,* ] , Requirements :: PerfectMatch )
66
- } }
102
+ use $crate:: matchers:: unordered_elements_are_matcher:: internal:: {
103
+ UnorderedElementsAreMatcher , Requirements
104
+ } ;
105
+ UnorderedElementsAreMatcher :: new( [ $( & $matcher) ,* ] , Requirements :: PerfectMatch )
106
+ } } ;
67
107
}
68
108
69
109
/// Matches a container containing elements matched by the given matchers.
@@ -113,10 +153,12 @@ macro_rules! unordered_elements_are {
113
153
macro_rules! contains_each {
114
154
( $( $matcher: expr) ,* $( , ) ?) => { {
115
155
#[ cfg( google3) ]
116
- use $crate:: internal:: { UnorderedElementsAre , Requirements } ;
156
+ use $crate:: internal:: { UnorderedElementsAreMatcher , Requirements } ;
117
157
#[ cfg( not( google3) ) ]
118
- use $crate:: matchers:: unordered_elements_are_matcher:: internal:: { UnorderedElementsAre , Requirements } ;
119
- UnorderedElementsAre :: new( [ $( & $matcher) ,* ] , Requirements :: Superset )
158
+ use $crate:: matchers:: unordered_elements_are_matcher:: internal:: {
159
+ UnorderedElementsAreMatcher , Requirements
160
+ } ;
161
+ UnorderedElementsAreMatcher :: new( [ $( & $matcher) ,* ] , Requirements :: Superset )
120
162
} }
121
163
}
122
164
@@ -169,10 +211,12 @@ macro_rules! contains_each {
169
211
macro_rules! is_contained_in {
170
212
( $( $matcher: expr) ,* $( , ) ?) => { {
171
213
#[ cfg( google3) ]
172
- use $crate:: internal:: { UnorderedElementsAre , Requirements } ;
214
+ use $crate:: internal:: { UnorderedElementsAreMatcher , Requirements } ;
173
215
#[ cfg( not( google3) ) ]
174
- use $crate:: matchers:: unordered_elements_are_matcher:: internal:: { UnorderedElementsAre , Requirements } ;
175
- UnorderedElementsAre :: new( [ $( & $matcher) ,* ] , Requirements :: Subset )
216
+ use $crate:: matchers:: unordered_elements_are_matcher:: internal:: {
217
+ UnorderedElementsAreMatcher , Requirements
218
+ } ;
219
+ UnorderedElementsAreMatcher :: new( [ $( & $matcher) ,* ] , Requirements :: Subset )
176
220
} }
177
221
}
178
222
@@ -200,12 +244,12 @@ pub mod internal {
200
244
///
201
245
/// **For internal use only. API stablility is not guaranteed!**
202
246
#[ doc( hidden) ]
203
- pub struct UnorderedElementsAre < ' a , T : Debug , const N : usize > {
247
+ pub struct UnorderedElementsAreMatcher < ' a , T : Debug , const N : usize > {
204
248
elements : [ & ' a dyn Matcher < T > ; N ] ,
205
249
requirements : Requirements ,
206
250
}
207
251
208
- impl < ' a , T : Debug , const N : usize > UnorderedElementsAre < ' a , T , N > {
252
+ impl < ' a , T : Debug , const N : usize > UnorderedElementsAreMatcher < ' a , T , N > {
209
253
pub fn new ( elements : [ & ' a dyn Matcher < T > ; N ] , requirements : Requirements ) -> Self {
210
254
Self { elements, requirements }
211
255
}
@@ -214,14 +258,14 @@ pub mod internal {
214
258
// This matcher performs the checks in three different steps in both `matches`
215
259
// and `explain_match`. This is useful for performance but also to produce
216
260
// an actionable error message.
217
- // 1. `UnorderedElementsAre ` verifies that both collections have the same
261
+ // 1. `UnorderedElementsAreMatcher ` verifies that both collections have the same
218
262
// size
219
- // 2. `UnorderedElementsAre ` verifies that each actual element matches at least
220
- // one expected element and vice versa.
221
- // 3. `UnorderedElementsAre ` verifies that a perfect matching exists using
222
- // Ford-Fulkerson.
263
+ // 2. `UnorderedElementsAreMatcher ` verifies that each actual element matches at
264
+ // least one expected element and vice versa.
265
+ // 3. `UnorderedElementsAreMatcher ` verifies that a perfect matching exists
266
+ // using Ford-Fulkerson.
223
267
impl < ' a , T : Debug , ContainerT : Debug + ?Sized , const N : usize > Matcher < ContainerT >
224
- for UnorderedElementsAre < ' a , T , N >
268
+ for UnorderedElementsAreMatcher < ' a , T , N >
225
269
where
226
270
for < ' b > & ' b ContainerT : IntoIterator < Item = & ' b T > ,
227
271
{
@@ -266,6 +310,76 @@ pub mod internal {
266
310
}
267
311
}
268
312
313
+ /// This is the analogue to [UnorderedElementsAreMatcher] for maps and
314
+ /// map-like collections.
315
+ ///
316
+ /// **For internal use only. API stablility is not guaranteed!**
317
+ #[ doc( hidden) ]
318
+ pub struct UnorderedElementsOfMapAreMatcher < ' a , KeyT : Debug , ValueT : Debug , const N : usize > {
319
+ elements : [ ( & ' a dyn Matcher < KeyT > , & ' a dyn Matcher < ValueT > ) ; N ] ,
320
+ requirements : Requirements ,
321
+ }
322
+
323
+ impl < ' a , KeyT : Debug , ValueT : Debug , const N : usize >
324
+ UnorderedElementsOfMapAreMatcher < ' a , KeyT , ValueT , N >
325
+ {
326
+ pub fn new (
327
+ elements : [ ( & ' a dyn Matcher < KeyT > , & ' a dyn Matcher < ValueT > ) ; N ] ,
328
+ requirements : Requirements ,
329
+ ) -> Self {
330
+ Self { elements, requirements }
331
+ }
332
+ }
333
+
334
+ impl < ' a , KeyT : Debug , ValueT : Debug , ContainerT : Debug + ?Sized , const N : usize >
335
+ Matcher < ContainerT > for UnorderedElementsOfMapAreMatcher < ' a , KeyT , ValueT , N >
336
+ where
337
+ for < ' b > & ' b ContainerT : IntoIterator < Item = ( & ' b KeyT , & ' b ValueT ) > ,
338
+ {
339
+ fn matches ( & self , actual : & ContainerT ) -> MatcherResult {
340
+ let match_matrix = MatchMatrix :: generate_for_map ( actual, & self . elements ) ;
341
+ match_matrix. is_match_for ( self . requirements ) . into ( )
342
+ }
343
+
344
+ fn explain_match ( & self , actual : & ContainerT ) -> MatchExplanation {
345
+ if let Some ( size_mismatch_explanation) =
346
+ self . requirements . explain_size_mismatch ( actual, N )
347
+ {
348
+ return size_mismatch_explanation;
349
+ }
350
+
351
+ let match_matrix = MatchMatrix :: generate_for_map ( actual, & self . elements ) ;
352
+ if let Some ( unmatchable_explanation) =
353
+ match_matrix. explain_unmatchable ( self . requirements )
354
+ {
355
+ return unmatchable_explanation;
356
+ }
357
+
358
+ let best_match = match_matrix. find_best_match ( ) ;
359
+ MatchExplanation :: create (
360
+ best_match
361
+ . get_explanation_for_map ( actual, & self . elements , self . requirements )
362
+ . unwrap_or ( "whose elements all match" . to_string ( ) ) ,
363
+ )
364
+ }
365
+
366
+ fn describe ( & self , matcher_result : MatcherResult ) -> String {
367
+ format ! (
368
+ "{} elements matching in any order:\n {}" ,
369
+ if matcher_result. into( ) { "contains" } else { "doesn't contain" } ,
370
+ self . elements
371
+ . iter( )
372
+ . map( |( key_matcher, value_matcher) | format!(
373
+ "{} => {}" ,
374
+ key_matcher. describe( MatcherResult :: Matches ) ,
375
+ value_matcher. describe( MatcherResult :: Matches )
376
+ ) )
377
+ . collect:: <Description >( )
378
+ . indent( )
379
+ )
380
+ }
381
+ }
382
+
269
383
/// The requirements of the mapping between matchers and actual values by
270
384
/// which [`UnorderedElemetnsAre`] is deemed to match its input.
271
385
///
@@ -360,6 +474,25 @@ pub mod internal {
360
474
matrix
361
475
}
362
476
477
+ fn generate_for_map < ' a , KeyT : Debug , ValueT : Debug , ContainerT : Debug + ?Sized > (
478
+ actual : & ContainerT ,
479
+ expected : & [ ( & ' a dyn Matcher < KeyT > , & ' a dyn Matcher < ValueT > ) ; N ] ,
480
+ ) -> Self
481
+ where
482
+ for < ' b > & ' b ContainerT : IntoIterator < Item = ( & ' b KeyT , & ' b ValueT ) > ,
483
+ {
484
+ let mut matrix =
485
+ MatchMatrix ( vec ! [ [ MatcherResult :: DoesNotMatch ; N ] ; count_elements( actual) ] ) ;
486
+ for ( actual_idx, ( actual_key, actual_value) ) in actual. into_iter ( ) . enumerate ( ) {
487
+ for ( expected_idx, ( expected_key, expected_value) ) in expected. iter ( ) . enumerate ( ) {
488
+ matrix. 0 [ actual_idx] [ expected_idx] = ( expected_key. matches ( actual_key) . into ( )
489
+ && expected_value. matches ( actual_value) . into ( ) )
490
+ . into ( ) ;
491
+ }
492
+ }
493
+ matrix
494
+ }
495
+
363
496
fn is_match_for ( & self , requirements : Requirements ) -> bool {
364
497
match requirements {
365
498
Requirements :: PerfectMatch => {
@@ -730,5 +863,63 @@ pub mod internal {
730
863
"which does not have a {requirements} match with the expected elements. The best match found was:\n {best_match}"
731
864
) )
732
865
}
866
+
867
+ fn get_explanation_for_map < ' a , KeyT : Debug , ValueT : Debug , ContainerT : Debug + ?Sized > (
868
+ & self ,
869
+ actual : & ContainerT ,
870
+ expected : & [ ( & ' a dyn Matcher < KeyT > , & ' a dyn Matcher < ValueT > ) ; N ] ,
871
+ requirements : Requirements ,
872
+ ) -> Option < String >
873
+ where
874
+ for < ' b > & ' b ContainerT : IntoIterator < Item = ( & ' b KeyT , & ' b ValueT ) > ,
875
+ {
876
+ let actual: Vec < _ > = actual. into_iter ( ) . collect ( ) ;
877
+ if self . is_full_match ( ) {
878
+ return None ;
879
+ }
880
+ let mut error_message =
881
+ format ! ( "which does not have a {requirements} match with the expected elements." ) ;
882
+
883
+ error_message. push_str ( "\n The best match found was: " ) ;
884
+
885
+ let matches = self . get_matches ( )
886
+ . map ( |( actual_idx, expected_idx) | {
887
+ format ! (
888
+ "Actual element {:?} => {:?} at index {actual_idx} matched expected element `{}` => `{}` at index {expected_idx}." ,
889
+ actual[ actual_idx] . 0 ,
890
+ actual[ actual_idx] . 1 ,
891
+ expected[ expected_idx] . 0 . describe( MatcherResult :: Matches ) ,
892
+ expected[ expected_idx] . 1 . describe( MatcherResult :: Matches ) ,
893
+ )
894
+ } ) ;
895
+
896
+ let unmatched_actual = self . get_unmatched_actual ( )
897
+ . map ( |actual_idx| {
898
+ format ! (
899
+ "Actual element {:#?} => {:#?} at index {actual_idx} did not match any remaining expected element." ,
900
+ actual[ actual_idx] . 0 ,
901
+ actual[ actual_idx] . 1 ,
902
+ )
903
+ } ) ;
904
+
905
+ let unmatched_expected = self . get_unmatched_expected ( )
906
+ . into_iter ( )
907
+ . map ( |expected_idx| {
908
+ format ! (
909
+ "Expected element `{}` => `{}` at index {expected_idx} did not match any remaining actual element." ,
910
+ expected[ expected_idx] . 0 . describe( MatcherResult :: Matches ) ,
911
+ expected[ expected_idx] . 1 . describe( MatcherResult :: Matches ) ,
912
+ )
913
+ } ) ;
914
+
915
+ let best_match = matches
916
+ . chain ( unmatched_actual)
917
+ . chain ( unmatched_expected)
918
+ . collect :: < Description > ( )
919
+ . indent ( ) ;
920
+ Some ( format ! (
921
+ "which does not have a {requirements} match with the expected elements. The best match found was:\n {best_match}"
922
+ ) )
923
+ }
733
924
}
734
925
}
0 commit comments