diff --git a/deep.go b/deep.go index 28f475c..0714249 100644 --- a/deep.go +++ b/deep.go @@ -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 { ///////////////////////////////////////////////////////////////////// @@ -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} diff --git a/equal_method_test.go b/equal_method_test.go new file mode 100644 index 0000000..4058c14 --- /dev/null +++ b/equal_method_test.go @@ -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") + } +}