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

feat: add InAnyOrder matcher #546

Merged
merged 4 commits into from May 17, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 69 additions & 0 deletions gomock/matchers.go
Expand Up @@ -207,6 +207,66 @@ func (m lenMatcher) String() string {
return fmt.Sprintf("has length %d", m.i)
}

type inAnyOrderMatcher struct {
x interface{}
}

func (m inAnyOrderMatcher) Matches(x interface{}) bool {
given, ok := m.prepareValue(x)
if !ok {
return false
}
wanted, ok := m.prepareValue(m.x)
if !ok {
return false
}

usedFromGiven := make([]bool, given.Len())
vvkh marked this conversation as resolved.
Show resolved Hide resolved
foundFromWanted := make([]bool, wanted.Len())
for i := 0; i < wanted.Len(); i++ {
wantedMatcher := Eq(wanted.Index(i).Interface())
for j := 0; j < given.Len(); j++ {
if usedFromGiven[j] {
continue
}
if wantedMatcher.Matches(given.Index(j).Interface()) {
foundFromWanted[i] = true
usedFromGiven[j] = true
break
}
}
}

missingFromWanted := 0
for _, found := range foundFromWanted {
if !found {
missingFromWanted++
}
}
extraInGiven := 0
for _, used := range usedFromGiven {
if !used {
extraInGiven++
}
}

return extraInGiven == 0 && missingFromWanted == 0
}

func (m inAnyOrderMatcher) prepareValue(x interface{}) (reflect.Value, bool) {
xValue := reflect.ValueOf(x)
switch xValue.Kind() {
case reflect.Slice, reflect.Array, reflect.String:
vvkh marked this conversation as resolved.
Show resolved Hide resolved
return xValue, true
default:
return reflect.Value{}, false
}
}

func (m inAnyOrderMatcher) String() string {
return fmt.Sprintf("has the same elements as %v", m.x)
}

// Constructors

// All returns a composite Matcher that returns true if and only all of the
Expand Down Expand Up @@ -266,3 +326,12 @@ func AssignableToTypeOf(x interface{}) Matcher {
}
return assignableToTypeOfMatcher{reflect.TypeOf(x)}
}

// InAnyOrder is a Matcher that returns true for collections of the same elements ignoring the order.
//
// Example usage:
// InAnyOrder([]int{1, 2, 3}).Matches([]int{1, 3, 2}) // returns true
// InAnyOrder([]int{1, 2, 3}).Matches([]int{1, 2}) // returns false
func InAnyOrder(x interface{}) Matcher {
return inAnyOrderMatcher{x}
}
173 changes: 173 additions & 0 deletions gomock/matchers_test.go
Expand Up @@ -142,3 +142,176 @@ func TestAssignableToTypeOfMatcher(t *testing.T) {
t.Errorf(`AssignableToTypeOf(context.Context) should not match ctxWithValue`)
}
}

func TestInAnyOrder(t *testing.T) {
tests := []struct {
name string
wanted interface{}
given interface{}
wantMatch bool
}{
{
name: "match_for_equal_slices",
vvkh marked this conversation as resolved.
Show resolved Hide resolved
wanted: []int{1, 2, 3},
given: []int{1, 2, 3},
wantMatch: true,
},
{
name: "match_for_slices_with_same_elements_of_different_order",
wanted: []int{1, 2, 3},
given: []int{1, 3, 2},
wantMatch: true,
},
{
name: "not_match_for_slices_with_different_elements",
wanted: []int{1, 2, 3},
given: []int{1, 2, 4},
wantMatch: false,
},
{
name: "not_match_for_slices_with_missing_elements",
wanted: []int{1, 2, 3},
given: []int{1, 2},
wantMatch: false,
},
{
name: "not_match_for_slices_with_extra_elements",
wanted: []int{1, 2, 3},
given: []int{1, 2, 3, 4},
wantMatch: false,
},
{
name: "match_for_empty_slices",
wanted: []int{},
given: []int{},
wantMatch: true,
},
{
name: "not_match_for_equal_slices_of_different_types",
wanted: []float64{1, 2, 3},
given: []int{1, 2, 3},
wantMatch: false,
},
{
name: "match_for_equal_arrays",
wanted: [3]int{1, 2, 3},
given: [3]int{1, 2, 3},
wantMatch: true,
},
{
name: "match_for_equal_arrays_of_different_order",
wanted: [3]int{1, 2, 3},
given: [3]int{1, 3, 2},
wantMatch: true,
},
{
name: "not_match_for_arrays_of_different_elements",
wanted: [3]int{1, 2, 3},
given: [3]int{1, 2, 4},
wantMatch: false,
},
{
name: "not_match_for_arrays_with_extra_elements",
wanted: [3]int{1, 2, 3},
given: [4]int{1, 2, 3, 4},
wantMatch: false,
},
{
name: "not_match_for_arrays_with_missing_elements",
wanted: [3]int{1, 2, 3},
given: [2]int{1, 2},
wantMatch: false,
},
{
name: "match_for_equal_strings",
wanted: "123",
given: "123",
wantMatch: true,
},
{
name: "match_for_equal_strings_of_different_order",
wanted: "123",
given: "132",
wantMatch: true,
},
{
name: "not_match_for_strings_of_different_elements",
wanted: "123",
given: "124",
wantMatch: false,
},
{
name: "not_match_for_strings_with_extra_elements",
wanted: "123",
given: "1234",
wantMatch: false,
},
{
name: "not_match_for_string_with_missing_elements",
wanted: "123",
given: "12",
wantMatch: false,
},
{
name: "not_match_if_x_type_is_not_iterable",
wanted: 123,
given: []int{123},
wantMatch: false,
},
{
name: "not_match_if_in_type_is_not_iterable",
wanted: []int{123},
given: 123,
wantMatch: false,
},
{
name: "not_match_if_both_are_not_iterable",
wanted: 123,
given: 123,
wantMatch: false,
},
{
name: "match_for_equal_slices_with_unhashable_elements",
wanted: [][]int{{1}, {1, 2}, {1, 2, 3}},
given: [][]int{{1}, {1, 2}, {1, 2, 3}},
wantMatch: true,
},
{
name: "match_for_equal_slices_with_unhashable_elements_of_different_order",
wanted: [][]int{{1}, {1, 2, 3}, {1, 2}},
given: [][]int{{1}, {1, 2}, {1, 2, 3}},
wantMatch: true,
},
{
name: "not_match_for_different_slices_with_unhashable_elements",
wanted: [][]int{{1}, {1, 2, 3}, {1, 2}},
given: [][]int{{1}, {1, 2, 4}, {1, 3}},
wantMatch: false,
},
{
name: "not_match_for_unhashable_missing_elements",
wanted: [][]int{{1}, {1, 2}, {1, 2, 3}},
given: [][]int{{1}, {1, 2}},
wantMatch: false,
},
{
name: "not_match_for_unhashable_extra_elements",
wanted: [][]int{{1}, {1, 2}},
given: [][]int{{1}, {1, 2}, {1, 2, 3}},
wantMatch: false,
},
{
name: "match_for_equal_slices_of_assignable_types",
wanted: [][]string{{"a", "b"}},
given: []A{{"a", "b"}},
wantMatch: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := gomock.InAnyOrder(tt.wanted).Matches(tt.given); got != tt.wantMatch {
t.Errorf("got = %v, wantMatch %v", got, tt.wantMatch)
}
})
}
}