From 19ce25c0e9963c7b86dcc8e87d04a3b6f091ca24 Mon Sep 17 00:00:00 2001 From: Valentin Khomutenko Date: Thu, 25 Mar 2021 23:03:54 +0300 Subject: [PATCH 1/3] MOCK-545 add InAnyOrder matcher --- gomock/matchers.go | 69 ++++++++++++++++ gomock/matchers_test.go | 173 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+) diff --git a/gomock/matchers.go b/gomock/matchers.go index 5638efe5..5fdd0b78 100644 --- a/gomock/matchers.go +++ b/gomock/matchers.go @@ -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()) + 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: + 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 @@ -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} +} diff --git a/gomock/matchers_test.go b/gomock/matchers_test.go index 91ec5a44..35a0312d 100644 --- a/gomock/matchers_test.go +++ b/gomock/matchers_test.go @@ -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", + 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) + } + }) + } +} From ae26e92e2e51131d5623fd81652e5a60cae03768 Mon Sep 17 00:00:00 2001 From: Valentin Khomutenko Date: Sat, 17 Apr 2021 00:27:32 +0300 Subject: [PATCH 2/3] MOCK-454 remove strings from types supported by InAnyOrder matcher --- gomock/matchers.go | 6 +++++- gomock/matchers_test.go | 26 +------------------------- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/gomock/matchers.go b/gomock/matchers.go index 5fdd0b78..ab885072 100644 --- a/gomock/matchers.go +++ b/gomock/matchers.go @@ -221,6 +221,10 @@ func (m inAnyOrderMatcher) Matches(x interface{}) bool { return false } + if given.Len() != wanted.Len() { + return false + } + usedFromGiven := make([]bool, given.Len()) foundFromWanted := make([]bool, wanted.Len()) for i := 0; i < wanted.Len(); i++ { @@ -256,7 +260,7 @@ func (m inAnyOrderMatcher) Matches(x interface{}) bool { func (m inAnyOrderMatcher) prepareValue(x interface{}) (reflect.Value, bool) { xValue := reflect.ValueOf(x) switch xValue.Kind() { - case reflect.Slice, reflect.Array, reflect.String: + case reflect.Slice, reflect.Array: return xValue, true default: return reflect.Value{}, false diff --git a/gomock/matchers_test.go b/gomock/matchers_test.go index 35a0312d..0c158c35 100644 --- a/gomock/matchers_test.go +++ b/gomock/matchers_test.go @@ -223,33 +223,9 @@ func TestInAnyOrder(t *testing.T) { wantMatch: false, }, { - name: "match_for_equal_strings", + name: "not_match_for_equal_strings", // matcher shouldn't treat strings as collections 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, }, { From 0ff2aa8fb014704e87c6ab4c159a887000048350 Mon Sep 17 00:00:00 2001 From: Valentin Khomutenko Date: Sat, 15 May 2021 13:12:38 +0300 Subject: [PATCH 3/3] MOCK-545 replaced underscores in test names with spaces --- gomock/matchers_test.go | 44 ++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/gomock/matchers_test.go b/gomock/matchers_test.go index 0c158c35..d50a9e5e 100644 --- a/gomock/matchers_test.go +++ b/gomock/matchers_test.go @@ -151,133 +151,133 @@ func TestInAnyOrder(t *testing.T) { wantMatch bool }{ { - name: "match_for_equal_slices", + name: "match for equal slices", wanted: []int{1, 2, 3}, given: []int{1, 2, 3}, wantMatch: true, }, { - name: "match_for_slices_with_same_elements_of_different_order", + 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", + 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", + 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", + 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", + name: "match for empty slices", wanted: []int{}, given: []int{}, wantMatch: true, }, { - name: "not_match_for_equal_slices_of_different_types", + 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", + 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", + 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", + 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", + 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", + name: "not match for arrays with missing elements", wanted: [3]int{1, 2, 3}, given: [2]int{1, 2}, wantMatch: false, }, { - name: "not_match_for_equal_strings", // matcher shouldn't treat strings as collections + name: "not match for equal strings", // matcher shouldn't treat strings as collections wanted: "123", given: "123", wantMatch: false, }, { - name: "not_match_if_x_type_is_not_iterable", + 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", + name: "not match if in type is not iterable", wanted: []int{123}, given: 123, wantMatch: false, }, { - name: "not_match_if_both_are_not_iterable", + name: "not match if both are not iterable", wanted: 123, given: 123, wantMatch: false, }, { - name: "match_for_equal_slices_with_unhashable_elements", + 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", + 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", + 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", + 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", + 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", + name: "match for equal slices of assignable types", wanted: [][]string{{"a", "b"}}, given: []A{{"a", "b"}}, wantMatch: true,