Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions deep.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,28 @@ func (c *cmp) equals(a, b reflect.Value, level int) {
return
}

// Types with an Equal() method, like time.Time or net.IP, only if struct
// field is exported (CanInterface)
if eqFunc := a.MethodByName("Equal"); eqFunc.IsValid() && eqFunc.CanInterface() {
// Handle https://github.com/go-test/deep/issues/15:
// Don't call T.Equal if the method is from an embedded struct, like:
// type Foo struct { time.Time }
// First, we'll encounter Equal(Ttime, time.Time) but if we pass b
// as the 2nd arg we'll panic: "Call using pkg.Foo as type time.Time"
// As far as I can tell, there's no way to see that the method is from
// time.Time not Foo. So we check the type of the 1st (0) arg and skip
// unless it's b type. Later, we'll encounter the time.Time anonymous/
// embedded field and then we'll have Equal(time.Time, time.Time).
funcType := eqFunc.Type()
if funcType.NumIn() == 1 && funcType.In(0) == bType {
retVals := eqFunc.Call([]reflect.Value{b})
if !retVals[0].Bool() {
c.saveDiff(a, b)
}
return
}
}

switch aKind {

/////////////////////////////////////////////////////////////////////
Expand All @@ -221,28 +243,6 @@ func (c *cmp) equals(a, b reflect.Value, level int) {
Iterate through the fields (FirstName, LastName), recurse into their values.
*/

// Types with an Equal() method, like time.Time, only if struct field
// is exported (CanInterface)
if eqFunc := a.MethodByName("Equal"); eqFunc.IsValid() && eqFunc.CanInterface() {
// Handle https://github.com/go-test/deep/issues/15:
// Don't call T.Equal if the method is from an embedded struct, like:
// type Foo struct { time.Time }
// First, we'll encounter Equal(Ttime, time.Time) but if we pass b
// as the 2nd arg we'll panic: "Call using pkg.Foo as type time.Time"
// As far as I can tell, there's no way to see that the method is from
// time.Time not Foo. So we check the type of the 1st (0) arg and skip
// unless it's b type. Later, we'll encounter the time.Time anonymous/
// embedded field and then we'll have Equal(time.Time, time.Time).
funcType := eqFunc.Type()
if funcType.NumIn() == 1 && funcType.In(0) == bType {
retVals := eqFunc.Call([]reflect.Value{b})
if !retVals[0].Bool() {
c.saveDiff(a, b)
}
return
}
}

for i := 0; i < a.NumField(); i++ {
if aType.Field(i).PkgPath != "" && !CompareUnexportedFields {
continue // skip unexported field, e.g. s in type T struct {s string}
Expand Down
38 changes: 38 additions & 0 deletions equal_method_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package deep_test

import (
"net"
"strings"
"testing"

"github.com/go-test/deep"
)

type equalFoldString string

func (s equalFoldString) Equal(other equalFoldString) bool {
return strings.EqualFold(string(s), string(other))
}

func TestEqualMethodOnScalar(t *testing.T) {
if diff := deep.Equal(equalFoldString("FOO"), equalFoldString("foo")); diff != nil {
t.Errorf("case-insensitive strings should be equal: %v", diff)
}
if diff := deep.Equal(equalFoldString("foo"), equalFoldString("bar")); diff == nil {
t.Error("different strings should not be equal")
}
}

func TestEqualMethodOnSlice(t *testing.T) {
a := net.ParseIP("1.2.3.4")
b := a.To4()
if diff := deep.Equal(a, b); diff != nil {
t.Errorf("equivalent IPv4 representations should be equal: %v", diff)
}
if diff := deep.Equal(b, a); diff != nil {
t.Errorf("equivalent IPv4 representations should be equal in reverse: %v", diff)
}
if diff := deep.Equal(a, net.ParseIP("1.2.3.5")); diff == nil {
t.Error("different IP addresses should not be equal")
}
}