Add gomock matcher utilities

pull/773/head
Eugene Kim 6 years ago
parent b89875607e
commit 18255f411e
  1. 35
      gomock_matchers/path.go
  2. 51
      gomock_matchers/path_test.go
  3. 39
      gomock_matchers/slice.go
  4. 114
      gomock_matchers/slice_test.go
  5. 53
      gomock_matchers/struct.go
  6. 113
      gomock_matchers/struct_test.go
  7. 12
      gomock_matchers/util.go

@ -0,0 +1,35 @@
package matchers
import (
"fmt"
"path"
"strings"
)
// Path is a pathname matcher.
//
// A value matches if it is the same as the matcher pattern or has the matcher
// pattern as a trailing component. For example,
// a pattern "abc/def" matches "abc/def" itself, "omg/abc/def",
// but not "abc/def/wtf", "abc/omg/def", or "xabc/def".
//
// Both the pattern and the value are sanitized using path.Clean() before use.
//
// The empty pattern "" matches only the empty value "".
type Path string
// Matches returns whether x is the matching pathname itself or ends with the
// matching pathname, inside another directory.
func (p Path) Matches(x interface{}) bool {
if s, ok := x.(string); ok {
p1 := path.Clean(string(p))
s = path.Clean(s)
return s == p1 || strings.HasSuffix(s, "/"+p1)
}
return false
}
// String returns the string representation of this pathname matcher.
func (p Path) String() string {
return fmt.Sprintf("<path suffix %#v>", path.Clean(string(p)))
}

@ -0,0 +1,51 @@
package matchers
import "testing"
func TestPathMatcher_Matches(t *testing.T) {
tests := []struct {
name string
p Path
x interface{}
want bool
}{
{"EmptyMatchesEmpty", "", "", true},
{"EmptyDoesNotMatchNonEmpty", "", "a", false},
{"EmptyDoesNotMatchNonEmptyEvenWithTrailingSlash", "", "a/", false},
{"NonEmptyDoesNotMatchEmpty", "a", "", false},
{"ExactIsOK", "abc/def", "abc/def", true},
{"SuffixIsOK", "abc/def", "omg/abc/def", true},
{"SubstringIsNotOK", "abc/def", "omg/abc/def/wtf", false},
{"PrefixIsNotOK", "abc/def", "abc/def/wtf", false},
{"InterveningElementIsNotOK", "abc/def", "abc/bbq/def", false},
{"GeneralNonMatch", "abc/def", "omg/wtf", false},
{"UncleanPattern", "abc//def/", "abc/def", true},
{"UncleanArg", "abc/def", "abc//def", true},
{"NonStringArg", "a", 'a', false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.p.Matches(tt.x); got != tt.want {
t.Errorf("Path.Matches() = %v, want %v", got, tt.want)
}
})
}
}
func TestPathMatcher_String(t *testing.T) {
tests := []struct {
name string
p Path
want string
}{
{"General", "abc/def", "<path suffix \"abc/def\">"},
{"Unclean", "abc//def/", "<path suffix \"abc/def\">"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.p.String(); got != tt.want {
t.Errorf("Path.String() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,39 @@
package matchers
import (
"fmt"
"reflect"
)
// Slice is a gomock matcher that matches elements of an array or
// slice against its own members at the corresponding positions.
// Each member item in a Slice may be a regular item or a gomock
// Matcher instance.
type Slice []interface{}
// Matches returns whether x is a slice with matching elements.
func (sm Slice) Matches(x interface{}) bool {
v := reflect.ValueOf(x)
switch v.Kind() {
case reflect.Slice, reflect.Array: // OK
default:
return false
}
l := v.Len()
if l != len(sm) {
return false
}
for i, m := range sm {
m1 := toMatcher(m)
v1 := v.Index(i).Interface()
if !m1.Matches(v1) {
return false
}
}
return true
}
// String returns the string representation of this slice matcher.
func (sm Slice) String() string {
return fmt.Sprint([]interface{}(sm))
}

@ -0,0 +1,114 @@
package matchers
import (
"testing"
"github.com/golang/mock/gomock"
)
func TestSliceMatcher_Matches(t *testing.T) {
tests := []struct {
name string
sm Slice
x interface{}
want bool
}{
{
"EmptyEqEmpty",
Slice{},
[]interface{}{},
true,
},
{
"EmptyNeNotEmpty",
Slice{},
[]interface{}{1},
false,
},
{
"NotEmptyNeEmpty",
Slice{0},
[]interface{}{},
false,
},
{
"CompareRawValuesUsingEqualityHappy",
Slice{1, 2, 3},
[]interface{}{1, 2, 3},
true,
},
{
"CompareRawValuesUsingEqualityUnhappy",
Slice{1, 2, 3},
[]interface{}{1, 20, 3},
false,
},
{
"CompareMatcherUsingItsMatchesHappy",
Slice{gomock.Nil(), gomock.Eq(3)},
[]interface{}{nil, 3},
true,
},
{
"CompareMatcherUsingItsMatchesUnhappy",
Slice{gomock.Nil(), gomock.Eq(3)},
[]interface{}{0, 3},
false,
},
{
"NestedHappy",
Slice{Slice{3}, 30},
[]interface{}{[]interface{}{3}, 30},
true,
},
{
"NestedUnhappy",
Slice{Slice{3}, 30},
[]interface{}{[]interface{}{300}, 30},
false,
},
{
"MatchSliceOfMoreSpecificTypes",
Slice{1, 2, 3},
[]int{1, 2, 3},
true,
},
{
"AcceptArraysToo",
Slice{1, 2, 3},
[...]int{1, 2, 3},
true,
},
{
"RejectString",
Slice{'a', 'b', 'c'},
"abc",
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.sm.Matches(tt.x); got != tt.want {
t.Errorf("Slice.Matches() = %v, want %v", got, tt.want)
}
})
}
}
func TestSliceMatcher_String(t *testing.T) {
tests := []struct {
name string
sm Slice
want string
}{
{"int", []interface{}{3, 5, 7}, "[3 5 7]"},
{"string", []interface{}{"omg", "wtf", "bbq"}, "[omg wtf bbq]"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.sm.String(); got != tt.want {
t.Errorf("Slice.String() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,53 @@
package matchers
import (
"fmt"
"reflect"
"sort"
"strings"
)
// Struct is a struct member matcher.
type Struct map[string]interface{}
// Matches returns whether all specified members match.
func (sm Struct) Matches(x interface{}) bool {
v := reflect.ValueOf(x)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return false
}
for n, m := range sm {
m1 := toMatcher(m)
f := v.FieldByName(n)
if f == (reflect.Value{}) {
return false
}
f1 := f.Interface()
if !m1.Matches(f1) {
return false
}
}
return true
}
func (sm Struct) String() string {
var fields sort.StringSlice
for name := range sm {
fields = append(fields, name)
}
sort.Sort(fields)
for i, name := range fields {
value := sm[name]
var vs string
if _, ok := value.(fmt.Stringer); ok {
vs = fmt.Sprintf("%s", value)
} else {
vs = fmt.Sprintf("%v", value)
}
fields[i] = fmt.Sprintf("%s=%s", name, vs)
}
return "<struct " + strings.Join(fields, " ") + ">"
}

@ -0,0 +1,113 @@
package matchers
import (
"testing"
"github.com/golang/mock/gomock"
)
type stringable int
func (s stringable) String() string {
return "omg"
}
func TestStructMatcher_Matches(t *testing.T) {
type value struct {
A int
B string
}
tests := []struct {
name string
sm Struct
x interface{}
want bool
}{
{
"EmptyMatchesEmpty",
Struct{},
value{},
true,
},
{
"EmptyMatchesAny",
Struct{},
value{A: 3, B: "omg"},
true,
},
{
"EmptyStillDoesNotMatchNonStruct",
Struct{},
0,
false,
},
{
"RegularFieldValuesUseEq1",
Struct{"A": 3, "B": "omg"},
value{A: 3, B: "omg"},
true,
},
{
"RegularFieldValuesUseEq2",
Struct{"A": 3, "B": "omg"},
value{A: 4, B: "omg"},
false,
},
{
"MatchersAreUsedVerbatim1",
Struct{"A": gomock.Not(3), "B": gomock.Eq("omg")},
value{A: 4, B: "omg"},
true,
},
{
"MatchersAreUsedVerbatim2",
Struct{"A": gomock.Not(3), "B": gomock.Eq("omg")},
value{A: 3, B: "omg"},
false,
},
{
"UnspecifiedFieldsAreIgnored",
Struct{"A": 3},
value{A: 3, B: "omg"},
true,
},
{
"MissingFieldsReturnFailure",
Struct{"NOTFOUND": 3},
value{A: 3},
false,
},
{
"DerefsPointer",
Struct{"A": 3},
&value{A: 3},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.sm.Matches(tt.x); got != tt.want {
t.Errorf("Struct.Matches() = %v, want %v", got, tt.want)
}
})
}
}
func TestStructMatcher_String(t *testing.T) {
tests := []struct {
name string
sm Struct
want string
}{
{"UsesStringer", Struct{"A": stringable(0)}, "<struct A=omg>"},
{"ReprIfNotStringable", Struct{"A": nil}, "<struct A=<nil>>"},
{"SortsByKey", Struct{"B": 3, "A": 4}, "<struct A=4 B=3>"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.sm.String(); got != tt.want {
t.Errorf("Struct.String() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,12 @@
package matchers
import (
"github.com/golang/mock/gomock"
)
func toMatcher(v interface{}) gomock.Matcher {
if m, ok := v.(gomock.Matcher); ok {
return m
}
return gomock.Eq(v)
}
Loading…
Cancel
Save