Merge "Add options to reflectutil DeepEqual"
diff --git a/runtimes/google/lib/reflectutil/all_test.go b/runtimes/google/lib/reflectutil/all_test.go
index eb1c5a5..eebf63e 100644
--- a/runtimes/google/lib/reflectutil/all_test.go
+++ b/runtimes/google/lib/reflectutil/all_test.go
@@ -52,7 +52,7 @@
*intPtr1b = 1
}
-func TestSharingDeepEqual(t *testing.T) {
+func TestDeepEqual(t *testing.T) {
tests := []struct {
a, b interface{}
expect bool
@@ -88,9 +88,9 @@
{abIntPtr{intPtr1a, intPtr1b}, abIntPtr{intPtr1a, intPtr1a}, false},
}
for _, test := range tests {
- actual := SharingDeepEqual(test.a, test.b)
+ actual := DeepEqual(test.a, test.b, &DeepEqualOpts{})
if actual != test.expect {
- t.Errorf("SharingDeepEqual(%#v, %#v) != %v", test.a, test.b, test.expect)
+ t.Errorf("DeepEqual(%#v, %#v) != %v", test.a, test.b, test.expect)
}
}
}
@@ -319,3 +319,45 @@
}
}
}
+
+func TestOptionSliceEqNilEmpty(t *testing.T) {
+ tests := []struct {
+ first interface{}
+ second interface{}
+ resultWithoutOption bool
+ resultWithOption bool
+ }{
+ {
+ []int{}, []int{}, true, true,
+ },
+ {
+ []int(nil), []int(nil), true, true,
+ },
+ {
+ []int{}, []int(nil), false, true,
+ },
+ {
+ []([]int){([]int)(nil)}, []([]int){[]int{}}, false, true,
+ },
+ }
+
+ for _, nilEqOpt := range []bool{true, false} {
+ for _, test := range tests {
+ options := &DeepEqualOpts{
+ SliceEqNilEmpty: nilEqOpt,
+ }
+
+ result := DeepEqual(test.first, test.second, options)
+
+ if nilEqOpt {
+ if result != test.resultWithOption {
+ t.Errorf("Unexpected result with SliceEqNilEmpty option: inputs %#v and %#v. Got %v, expected: %v", test.first, test.second, result, test.resultWithOption)
+ }
+ } else {
+ if result != test.resultWithoutOption {
+ t.Errorf("Unexpected result without SliceEqNilEmpty option: inputs %#v and %#v. Got %v, expected: %v", test.first, test.second, result, test.resultWithoutOption)
+ }
+ }
+ }
+ }
+}
diff --git a/runtimes/google/lib/reflectutil/deepequal.go b/runtimes/google/lib/reflectutil/deepequal.go
index d2b9381..242f888 100644
--- a/runtimes/google/lib/reflectutil/deepequal.go
+++ b/runtimes/google/lib/reflectutil/deepequal.go
@@ -5,13 +5,21 @@
"reflect"
)
-// SharingDeepEqual is similar to reflect.DeepEqual, except that it also
+// Equal is similar to reflect.DeepEqual, except that it also
// considers the sharing structure for pointers. When reflect.DeepEqual
// encounters pointers it just compares the dereferenced values; we also keep
// track of the pointers themselves and require that if a pointer appears
// multiple places in a, it appears in the same places in b.
-func SharingDeepEqual(a, b interface{}) bool {
- return deepEqual(reflect.ValueOf(a), reflect.ValueOf(b), &orderInfo{})
+func DeepEqual(a, b interface{}, options *DeepEqualOpts) bool {
+ return deepEqual(reflect.ValueOf(a), reflect.ValueOf(b), &orderInfo{}, options)
+}
+
+// TODO(bprosnitz) Implement the debuggable deep equal option.
+// TODO(bprosnitz) Add an option to turn pointer sharing on/off
+
+// DeepEqualOpts represents the options configuration for DeepEqual.
+type DeepEqualOpts struct {
+ SliceEqNilEmpty bool
}
// orderInfo tracks pointer ordering information. As we encounter new pointers
@@ -45,7 +53,7 @@
return true, false
}
-func deepEqual(a, b reflect.Value, info *orderInfo) bool {
+func deepEqual(a, b reflect.Value, info *orderInfo, options *DeepEqualOpts) bool {
// We only consider sharing via explicit pointers, and ignore sharing via
// slices, maps or pointers to internal data.
if !a.IsValid() || !b.IsValid() {
@@ -68,26 +76,28 @@
// equal, otherwise we'll have an infinite loop for cyclic values.
return true
}
- return deepEqual(a.Elem(), b.Elem(), info)
+ return deepEqual(a.Elem(), b.Elem(), info, options)
case reflect.Array:
if a.Len() != b.Len() {
return false
}
for ix := 0; ix < a.Len(); ix++ {
- if !deepEqual(a.Index(ix), b.Index(ix), info) {
+ if !deepEqual(a.Index(ix), b.Index(ix), info, options) {
return false
}
}
return true
case reflect.Slice:
- if a.IsNil() || b.IsNil() {
- return a.IsNil() == b.IsNil()
+ if !options.SliceEqNilEmpty {
+ if a.IsNil() || b.IsNil() {
+ return a.IsNil() == b.IsNil()
+ }
}
if a.Len() != b.Len() {
return false
}
for ix := 0; ix < a.Len(); ix++ {
- if !deepEqual(a.Index(ix), b.Index(ix), info) {
+ if !deepEqual(a.Index(ix), b.Index(ix), info, options) {
return false
}
}
@@ -100,14 +110,14 @@
return false
}
for _, key := range a.MapKeys() {
- if !deepEqual(a.MapIndex(key), b.MapIndex(key), info) {
+ if !deepEqual(a.MapIndex(key), b.MapIndex(key), info, options) {
return false
}
}
return true
case reflect.Struct:
for fx := 0; fx < a.NumField(); fx++ {
- if !deepEqual(a.Field(fx), b.Field(fx), info) {
+ if !deepEqual(a.Field(fx), b.Field(fx), info, options) {
return false
}
}
@@ -116,7 +126,7 @@
if a.IsNil() || b.IsNil() {
return a.IsNil() == b.IsNil()
}
- return deepEqual(a.Elem(), b.Elem(), info)
+ return deepEqual(a.Elem(), b.Elem(), info, options)
// Ideally we would add a default clause here that would just return
// a.Interface() == b.Interface(), but that panics if we're dealing with