Skip to content
This repository was archived by the owner on Jun 27, 2023. It is now read-only.

Commit af3c326

Browse files
committed
MOCK-545 add InAnyOrder matcher
1 parent f36d14a commit af3c326

File tree

2 files changed

+242
-0
lines changed

2 files changed

+242
-0
lines changed

gomock/matchers.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,66 @@ func (m lenMatcher) String() string {
207207
return fmt.Sprintf("has length %d", m.i)
208208
}
209209

210+
type inAnyOrderMatcher struct {
211+
x interface{}
212+
}
213+
214+
func (m inAnyOrderMatcher) Matches(x interface{}) bool {
215+
given, ok := m.prepareValue(x)
216+
if !ok {
217+
return false
218+
}
219+
wanted, ok := m.prepareValue(m.x)
220+
if !ok {
221+
return false
222+
}
223+
224+
usedFromGiven := make([]bool, given.Len())
225+
foundFromWanted := make([]bool, wanted.Len())
226+
for i := 0; i < wanted.Len(); i++ {
227+
wantedMatcher := Eq(wanted.Index(i).Interface())
228+
for j := 0; j < given.Len(); j++ {
229+
if usedFromGiven[j] {
230+
continue
231+
}
232+
if wantedMatcher.Matches(given.Index(j).Interface()) {
233+
foundFromWanted[i] = true
234+
usedFromGiven[j] = true
235+
break
236+
}
237+
}
238+
}
239+
240+
missingFromWanted := 0
241+
for _, found := range foundFromWanted {
242+
if !found {
243+
missingFromWanted++
244+
}
245+
}
246+
extraInGiven := 0
247+
for _, used := range usedFromGiven {
248+
if !used {
249+
extraInGiven++
250+
}
251+
}
252+
253+
return extraInGiven == 0 && missingFromWanted == 0
254+
}
255+
256+
func (m inAnyOrderMatcher) prepareValue(x interface{}) (reflect.Value, bool) {
257+
xValue := reflect.ValueOf(x)
258+
switch xValue.Kind() {
259+
case reflect.Slice, reflect.Array, reflect.String:
260+
return xValue, true
261+
default:
262+
return reflect.Value{}, false
263+
}
264+
}
265+
266+
func (m inAnyOrderMatcher) String() string {
267+
return fmt.Sprintf("has the same elements as %v", m.x)
268+
}
269+
210270
// Constructors
211271

212272
// All returns a composite Matcher that returns true if and only all of the
@@ -266,3 +326,12 @@ func AssignableToTypeOf(x interface{}) Matcher {
266326
}
267327
return assignableToTypeOfMatcher{reflect.TypeOf(x)}
268328
}
329+
330+
// InAnyOrder is a Matcher that returns true for collections of the same elements ignoring the order.
331+
//
332+
// Example usage:
333+
// InAnyOrder([]int{1, 2, 3}).Matches([]int{1, 3, 2}) // returns true
334+
// InAnyOrder([]int{1, 2, 3}).Matches([]int{1, 2}) // returns false
335+
func InAnyOrder(x interface{}) Matcher {
336+
return inAnyOrderMatcher{x}
337+
}

gomock/matchers_test.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,176 @@ func TestAssignableToTypeOfMatcher(t *testing.T) {
142142
t.Errorf(`AssignableToTypeOf(context.Context) should not match ctxWithValue`)
143143
}
144144
}
145+
146+
func TestInAnyOrder(t *testing.T) {
147+
tests := []struct {
148+
name string
149+
wanted interface{}
150+
given interface{}
151+
wantMatch bool
152+
}{
153+
{
154+
name: "match_for_equal_slices",
155+
wanted: []int{1, 2, 3},
156+
given: []int{1, 2, 3},
157+
wantMatch: true,
158+
},
159+
{
160+
name: "match_for_slices_with_same_elements_of_different_order",
161+
wanted: []int{1, 2, 3},
162+
given: []int{1, 3, 2},
163+
wantMatch: true,
164+
},
165+
{
166+
name: "not_match_for_slices_with_different_elements",
167+
wanted: []int{1, 2, 3},
168+
given: []int{1, 2, 4},
169+
wantMatch: false,
170+
},
171+
{
172+
name: "not_match_for_slices_with_missing_elements",
173+
wanted: []int{1, 2, 3},
174+
given: []int{1, 2},
175+
wantMatch: false,
176+
},
177+
{
178+
name: "not_match_for_slices_with_extra_elements",
179+
wanted: []int{1, 2, 3},
180+
given: []int{1, 2, 3, 4},
181+
wantMatch: false,
182+
},
183+
{
184+
name: "match_for_empty_slices",
185+
wanted: []int{},
186+
given: []int{},
187+
wantMatch: true,
188+
},
189+
{
190+
name: "not_match_for_equal_slices_of_different_types",
191+
wanted: []float64{1, 2, 3},
192+
given: []int{1, 2, 3},
193+
wantMatch: false,
194+
},
195+
{
196+
name: "match_for_equal_arrays",
197+
wanted: [3]int{1, 2, 3},
198+
given: [3]int{1, 2, 3},
199+
wantMatch: true,
200+
},
201+
{
202+
name: "match_for_equal_arrays_of_different_order",
203+
wanted: [3]int{1, 2, 3},
204+
given: [3]int{1, 3, 2},
205+
wantMatch: true,
206+
},
207+
{
208+
name: "not_match_for_arrays_of_different_elements",
209+
wanted: [3]int{1, 2, 3},
210+
given: [3]int{1, 2, 4},
211+
wantMatch: false,
212+
},
213+
{
214+
name: "not_match_for_arrays_with_extra_elements",
215+
wanted: [3]int{1, 2, 3},
216+
given: [4]int{1, 2, 3, 4},
217+
wantMatch: false,
218+
},
219+
{
220+
name: "not_match_for_arrays_with_missing_elements",
221+
wanted: [3]int{1, 2, 3},
222+
given: [2]int{1, 2},
223+
wantMatch: false,
224+
},
225+
{
226+
name: "match_for_equal_strings",
227+
wanted: "123",
228+
given: "123",
229+
wantMatch: true,
230+
},
231+
{
232+
name: "match_for_equal_strings_of_different_order",
233+
wanted: "123",
234+
given: "132",
235+
wantMatch: true,
236+
},
237+
{
238+
name: "not_match_for_strings_of_different_elements",
239+
wanted: "123",
240+
given: "124",
241+
wantMatch: false,
242+
},
243+
{
244+
name: "not_match_for_strings_with_extra_elements",
245+
wanted: "123",
246+
given: "1234",
247+
wantMatch: false,
248+
},
249+
{
250+
name: "not_match_for_string_with_missing_elements",
251+
wanted: "123",
252+
given: "12",
253+
wantMatch: false,
254+
},
255+
{
256+
name: "not_match_if_x_type_is_not_iterable",
257+
wanted: 123,
258+
given: []int{123},
259+
wantMatch: false,
260+
},
261+
{
262+
name: "not_match_if_in_type_is_not_iterable",
263+
wanted: []int{123},
264+
given: 123,
265+
wantMatch: false,
266+
},
267+
{
268+
name: "not_match_if_both_are_not_iterable",
269+
wanted: 123,
270+
given: 123,
271+
wantMatch: false,
272+
},
273+
{
274+
name: "match_for_equal_slices_with_unhashable_elements",
275+
wanted: [][]int{{1}, {1, 2}, {1, 2, 3}},
276+
given: [][]int{{1}, {1, 2}, {1, 2, 3}},
277+
wantMatch: true,
278+
},
279+
{
280+
name: "match_for_equal_slices_with_unhashable_elements_of_different_order",
281+
wanted: [][]int{{1}, {1, 2, 3}, {1, 2}},
282+
given: [][]int{{1}, {1, 2}, {1, 2, 3}},
283+
wantMatch: true,
284+
},
285+
{
286+
name: "not_match_for_different_slices_with_unhashable_elements",
287+
wanted: [][]int{{1}, {1, 2, 3}, {1, 2}},
288+
given: [][]int{{1}, {1, 2, 4}, {1, 3}},
289+
wantMatch: false,
290+
},
291+
{
292+
name: "not_match_for_unhashable_missing_elements",
293+
wanted: [][]int{{1}, {1, 2}, {1, 2, 3}},
294+
given: [][]int{{1}, {1, 2}},
295+
wantMatch: false,
296+
},
297+
{
298+
name: "not_match_for_unhashable_extra_elements",
299+
wanted: [][]int{{1}, {1, 2}},
300+
given: [][]int{{1}, {1, 2}, {1, 2, 3}},
301+
wantMatch: false,
302+
},
303+
{
304+
name: "match_for_equal_slices_of_assignable_types",
305+
wanted: [][]string{{"a", "b"}},
306+
given: []A{{"a", "b"}},
307+
wantMatch: true,
308+
},
309+
}
310+
for _, tt := range tests {
311+
t.Run(tt.name, func(t *testing.T) {
312+
if got := gomock.InAnyOrder(tt.wanted).Matches(tt.given); got != tt.wantMatch {
313+
t.Errorf("got = %v, wantMatch %v", got, tt.wantMatch)
314+
}
315+
})
316+
}
317+
}

0 commit comments

Comments
 (0)