diff --git a/cmd/sentinel/cmd/sentinel_test.go b/cmd/sentinel/cmd/sentinel_test.go index d94971e5c..ed576ee1e 100644 --- a/cmd/sentinel/cmd/sentinel_test.go +++ b/cmd/sentinel/cmd/sentinel_test.go @@ -27,6 +27,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/sorintlab/stolon/internal/cluster" "github.com/sorintlab/stolon/internal/common" + "github.com/sorintlab/stolon/internal/util" ) var curUID int @@ -4992,8 +4993,11 @@ func TestUpdateCluster(t *testing.T) { } else { if err != nil { t.Errorf("unexpected error: %v", err) - } else if !testEqualCD(outcd, tt.outcd) { - t.Errorf("wrong outcd: got:\n%s\nwant:\n%s", spew.Sdump(outcd), spew.Sdump(tt.outcd)) + } else { + equal, diff := testEqualCD(outcd, tt.outcd) + if !equal { + t.Errorf("wrong outcd: got:\n%s\nwant:\n%s\nfield path: %v\n got: %v\n want: %v ", spew.Sdump(outcd), spew.Sdump(tt.outcd), diff.Prefix, diff.V1, diff.V2) + } } } } @@ -5088,7 +5092,7 @@ func testRandFn(i int) int { return 0 } -func testEqualCD(cd1, cd2 *cluster.ClusterData) bool { +func testEqualCD(cd1, cd2 *cluster.ClusterData) (bool, util.DeepEqualDiff) { // ignore times for _, cd := range []*cluster.ClusterData{cd1, cd2} { cd.Cluster.ChangeTime = time.Time{} @@ -5100,6 +5104,10 @@ func testEqualCD(cd1, cd2 *cluster.ClusterData) bool { } cd.Proxy.ChangeTime = time.Time{} } - return reflect.DeepEqual(cd1, cd2) - + equal, diff := util.DeepEqualVerbose(cd1, cd2) + // sanity check + if equal != reflect.DeepEqual(cd1, cd2) { + panic("bug in DeepEqualVerbose") + } + return equal, diff } diff --git a/internal/util/deepequalverbose.go b/internal/util/deepequalverbose.go new file mode 100644 index 000000000..c7d77edc0 --- /dev/null +++ b/internal/util/deepequalverbose.go @@ -0,0 +1,243 @@ +// Copyright 2019 Sorint.lab +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This is slightly modified deepequal.go from the standard Go library. It +// additionally returns DeepEqualDiff struct describing found inequality. + +package util + +import "fmt" +import "unsafe" +import "reflect" + +// During deepValueEqual, must keep track of checks that are +// in progress. The comparison algorithm assumes that all +// checks in progress are true when it reencounters them. +// Visited comparisons are stored in a map indexed by visit. +type visit struct { + a1 unsafe.Pointer + a2 unsafe.Pointer + typ reflect.Type +} + +type DeepEqualDiff struct { + Prefix string + V1 string + V2 string +} + +// less typing... +type ctx DeepEqualDiff + +// Tests for deep equality using reflected types. The map argument tracks +// comparisons that have already been seen, which allows short circuiting on +// recursive types. +func deepValueEqual(ctx *ctx, v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { + if !v1.IsValid() || !v2.IsValid() { + return v1.IsValid() == v2.IsValid() + } + if v1.Type() != v2.Type() { + return false + } + + // if depth > 10 { panic("deepValueEqual") } // for debugging + + // We want to avoid putting more in the visited map than we need to. + // For any possible reference cycle that might be encountered, + // hard(t) needs to return true for at least one of the types in the cycle. + hard := func(k reflect.Kind) bool { + switch k { + case reflect.Map, reflect.Slice, reflect.Ptr, reflect.Interface: + return true + } + return false + } + + if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) { + addr1 := unsafe.Pointer(v1.UnsafeAddr()) + addr2 := unsafe.Pointer(v2.UnsafeAddr()) + if uintptr(addr1) > uintptr(addr2) { + // Canonicalize order to reduce number of entries in visited. + // Assumes non-moving garbage collector. + addr1, addr2 = addr2, addr1 + } + + // Short circuit if references are already seen. + typ := v1.Type() + v := visit{addr1, addr2, typ} + if visited[v] { + return true + } + + // Remember for later. + visited[v] = true + } + + switch v1.Kind() { + case reflect.Array: + for i := 0; i < v1.Len(); i++ { + oldPrefix := ctx.Prefix + ctx.Prefix = ctx.Prefix + fmt.Sprintf("[%v]", i) + if !deepValueEqual(ctx, v1.Index(i), v2.Index(i), visited, depth+1) { + return false + } + ctx.Prefix = oldPrefix + } + return true + case reflect.Slice: + if v1.IsNil() != v2.IsNil() { + return false + } + if v1.Len() != v2.Len() { + return false + } + if v1.Pointer() == v2.Pointer() { + return true + } + for i := 0; i < v1.Len(); i++ { + oldPrefix := ctx.Prefix + ctx.Prefix = ctx.Prefix + fmt.Sprintf("[%v]", i) + if !deepValueEqual(ctx, v1.Index(i), v2.Index(i), visited, depth+1) { + return false + } + ctx.Prefix = oldPrefix + } + return true + case reflect.Interface: + if v1.IsNil() || v2.IsNil() { + return v1.IsNil() == v2.IsNil() + } + return deepValueEqual(ctx, v1.Elem(), v2.Elem(), visited, depth+1) + case reflect.Ptr: + if v1.Pointer() == v2.Pointer() { + return true + } + return deepValueEqual(ctx, v1.Elem(), v2.Elem(), visited, depth+1) + case reflect.Struct: + for i, n := 0, v1.NumField(); i < n; i++ { + // skip unexported fields + if !v1.Field(i).CanSet() { + continue + } + oldPrefix := ctx.Prefix + ctx.Prefix = ctx.Prefix + fmt.Sprintf("->%v", v1.Type().Field(i).Name) + if !deepValueEqual(ctx, v1.Field(i), v2.Field(i), visited, depth+1) { + return false + } + ctx.Prefix = oldPrefix + } + return true + case reflect.Map: + if v1.IsNil() != v2.IsNil() { + return false + } + if v1.Len() != v2.Len() { + return false + } + if v1.Pointer() == v2.Pointer() { + return true + } + for _, k := range v1.MapKeys() { + val1 := v1.MapIndex(k) + val2 := v2.MapIndex(k) + oldPrefix := ctx.Prefix + ctx.Prefix = ctx.Prefix + fmt.Sprintf("[%v]", k.Interface()) + if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(ctx, val1, val2, visited, depth+1) { + return false + } + ctx.Prefix = oldPrefix + } + return true + case reflect.Func: + if v1.IsNil() && v2.IsNil() { + return true + } + // Can't do better than this: + return false + default: + // Normal equality suffices + // NOTE: this will crash and burn for unexported fields + ctx.V1 = fmt.Sprintf("%v", v1.Interface()) + ctx.V2 = fmt.Sprintf("%v", v2.Interface()) + return reflect.DeepEqual(v1.Interface(), v2.Interface()) + } +} + +// DeepEqual reports whether x and y are ``deeply equal,'' defined as follows. +// Two values of identical type are deeply equal if one of the following cases applies. +// Values of distinct types are never deeply equal. +// +// Array values are deeply equal when their corresponding elements are deeply equal. +// +// Struct values are deeply equal if their corresponding fields, +// both exported and unexported, are deeply equal. +// +// Func values are deeply equal if both are nil; otherwise they are not deeply equal. +// +// Interface values are deeply equal if they hold deeply equal concrete values. +// +// Map values are deeply equal when all of the following are true: +// they are both nil or both non-nil, they have the same length, +// and either they are the same map object or their corresponding keys +// (matched using Go equality) map to deeply equal values. +// +// Pointer values are deeply equal if they are equal using Go's == operator +// or if they point to deeply equal values. +// +// Slice values are deeply equal when all of the following are true: +// they are both nil or both non-nil, they have the same length, +// and either they point to the same initial entry of the same underlying array +// (that is, &x[0] == &y[0]) or their corresponding elements (up to length) are deeply equal. +// Note that a non-nil empty slice and a nil slice (for example, []byte{} and []byte(nil)) +// are not deeply equal. +// +// Other values - numbers, bools, strings, and channels - are deeply equal +// if they are equal using Go's == operator. +// +// In general DeepEqual is a recursive relaxation of Go's == operator. +// However, this idea is impossible to implement without some inconsistency. +// Specifically, it is possible for a value to be unequal to itself, +// either because it is of func type (uncomparable in general) +// or because it is a floating-point NaN value (not equal to itself in floating-point comparison), +// or because it is an array, struct, or interface containing +// such a value. +// On the other hand, pointer values are always equal to themselves, +// even if they point at or contain such problematic values, +// because they compare equal using Go's == operator, and that +// is a sufficient condition to be deeply equal, regardless of content. +// DeepEqual has been defined so that the same short-cut applies +// to slices and maps: if x and y are the same slice or the same map, +// they are deeply equal regardless of content. +// +// As DeepEqual traverses the data values it may find a cycle. The +// second and subsequent times that DeepEqual compares two pointer +// values that have been compared before, it treats the values as +// equal rather than examining the values to which they point. +// This ensures that DeepEqual terminates. +func DeepEqualVerbose(x, y interface{}) (bool, DeepEqualDiff) { + c := ctx{Prefix: ""} + if x == nil || y == nil { + return x == y, DeepEqualDiff(c) + } + v1 := reflect.ValueOf(x) + v2 := reflect.ValueOf(y) + if v1.Type() != v2.Type() { + return false, DeepEqualDiff(c) + } + return deepValueEqual(&c, v1, v2, make(map[visit]bool), 0), DeepEqualDiff(c) +}