|
|
|
|
@ -19,14 +19,15 @@ var (
|
|
|
|
|
// MaxDiff specifies the maximum number of differences to return.
|
|
|
|
|
MaxDiff = 10
|
|
|
|
|
|
|
|
|
|
// MaxDepth specifies the maximum levels of a struct to recurse into.
|
|
|
|
|
MaxDepth = 10
|
|
|
|
|
// MaxDepth specifies the maximum levels of a struct to recurse into,
|
|
|
|
|
// if greater than zero. If zero, there is no limit.
|
|
|
|
|
MaxDepth = 0
|
|
|
|
|
|
|
|
|
|
// LogErrors causes errors to be logged to STDERR when true.
|
|
|
|
|
LogErrors = false
|
|
|
|
|
|
|
|
|
|
// CompareUnexportedFields causes unexported struct fields, like s in
|
|
|
|
|
// T{s int}, to be comparsed when true.
|
|
|
|
|
// T{s int}, to be compared when true.
|
|
|
|
|
CompareUnexportedFields = false
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@ -50,8 +51,9 @@ type cmp struct {
|
|
|
|
|
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
|
|
|
|
|
|
|
|
|
// Equal compares variables a and b, recursing into their structure up to
|
|
|
|
|
// MaxDepth levels deep, and returns a list of differences, or nil if there are
|
|
|
|
|
// none. Some differences may not be found if an error is also returned.
|
|
|
|
|
// MaxDepth levels deep (if greater than zero), and returns a list of differences,
|
|
|
|
|
// or nil if there are none. Some differences may not be found if an error is
|
|
|
|
|
// also returned.
|
|
|
|
|
//
|
|
|
|
|
// If a type has an Equal method, like time.Equal, it is called to check for
|
|
|
|
|
// equality.
|
|
|
|
|
@ -66,7 +68,7 @@ func Equal(a, b interface{}) []string {
|
|
|
|
|
if a == nil && b == nil {
|
|
|
|
|
return nil
|
|
|
|
|
} else if a == nil && b != nil {
|
|
|
|
|
c.saveDiff(b, "<nil pointer>")
|
|
|
|
|
c.saveDiff("<nil pointer>", b)
|
|
|
|
|
} else if a != nil && b == nil {
|
|
|
|
|
c.saveDiff(a, "<nil pointer>")
|
|
|
|
|
}
|
|
|
|
|
@ -82,7 +84,7 @@ func Equal(a, b interface{}) []string {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *cmp) equals(a, b reflect.Value, level int) {
|
|
|
|
|
if level > MaxDepth {
|
|
|
|
|
if MaxDepth > 0 && level > MaxDepth {
|
|
|
|
|
logError(ErrMaxRecursion)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
@ -97,7 +99,7 @@ func (c *cmp) equals(a, b reflect.Value, level int) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If differenet types, they can't be equal
|
|
|
|
|
// If different types, they can't be equal
|
|
|
|
|
aType := a.Type()
|
|
|
|
|
bType := b.Type()
|
|
|
|
|
if aType != bType {
|
|
|
|
|
@ -110,46 +112,37 @@ func (c *cmp) equals(a, b reflect.Value, level int) {
|
|
|
|
|
aKind := a.Kind()
|
|
|
|
|
bKind := b.Kind()
|
|
|
|
|
|
|
|
|
|
// Do a and b have underlying elements? Yes if they're ptr or interface.
|
|
|
|
|
aElem := aKind == reflect.Ptr || aKind == reflect.Interface
|
|
|
|
|
bElem := bKind == reflect.Ptr || bKind == reflect.Interface
|
|
|
|
|
|
|
|
|
|
// If both types implement the error interface, compare the error strings.
|
|
|
|
|
// This must be done before dereferencing because the interface is on a
|
|
|
|
|
// pointer receiver.
|
|
|
|
|
// pointer receiver. Re https://github.com/go-test/deep/issues/31, a/b might
|
|
|
|
|
// be primitive kinds; see TestErrorPrimitiveKind.
|
|
|
|
|
if aType.Implements(errorType) && bType.Implements(errorType) {
|
|
|
|
|
if a.Elem().IsValid() && b.Elem().IsValid() { // both err != nil
|
|
|
|
|
if (!aElem || !a.IsNil()) && (!bElem || !b.IsNil()) {
|
|
|
|
|
aString := a.MethodByName("Error").Call(nil)[0].String()
|
|
|
|
|
bString := b.MethodByName("Error").Call(nil)[0].String()
|
|
|
|
|
if aString != bString {
|
|
|
|
|
c.saveDiff(aString, bString)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Dereference pointers and interface{}
|
|
|
|
|
if aElem, bElem := (aKind == reflect.Ptr || aKind == reflect.Interface),
|
|
|
|
|
(bKind == reflect.Ptr || bKind == reflect.Interface); aElem || bElem {
|
|
|
|
|
|
|
|
|
|
if aElem || bElem {
|
|
|
|
|
if aElem {
|
|
|
|
|
a = a.Elem()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if bElem {
|
|
|
|
|
b = b.Elem()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.equals(a, b, level+1)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Types with an Equal(), like time.Time.
|
|
|
|
|
eqFunc := a.MethodByName("Equal")
|
|
|
|
|
if eqFunc.IsValid() {
|
|
|
|
|
retVals := eqFunc.Call([]reflect.Value{b})
|
|
|
|
|
if !retVals[0].Bool() {
|
|
|
|
|
c.saveDiff(a, b)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch aKind {
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
@ -167,6 +160,29 @@ 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}
|
|
|
|
|
@ -267,12 +283,13 @@ func (c *cmp) equals(a, b reflect.Value, level int) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.Pointer() == b.Pointer() {
|
|
|
|
|
aLen := a.Len()
|
|
|
|
|
bLen := b.Len()
|
|
|
|
|
|
|
|
|
|
if a.Pointer() == b.Pointer() && aLen == bLen {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
aLen := a.Len()
|
|
|
|
|
bLen := b.Len()
|
|
|
|
|
n := aLen
|
|
|
|
|
if bLen > aLen {
|
|
|
|
|
n = bLen
|
|
|
|
|
|