Skip to content
Open
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
18 changes: 13 additions & 5 deletions cmd/sentinel/cmd/sentinel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}
}
Expand Down Expand Up @@ -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{}
Expand All @@ -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
}
243 changes: 243 additions & 0 deletions internal/util/deepequalverbose.go
Original file line number Diff line number Diff line change
@@ -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)
}