mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 06:27:05 +00:00
migrate k8s.io/apimachinery/pkg/util/diff to cmp
This commit is contained in:
parent
0e224ad3f3
commit
1e9ce46f0a
@ -584,6 +584,12 @@ func (q *Quantity) Neg() {
|
|||||||
q.d.Dec.Neg(q.d.Dec)
|
q.d.Dec.Neg(q.d.Dec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal checks equality of two Quantities. This is useful for testing with
|
||||||
|
// cmp.Equal.
|
||||||
|
func (q Quantity) Equal(v Quantity) bool {
|
||||||
|
return q.Cmp(v) == 0
|
||||||
|
}
|
||||||
|
|
||||||
// int64QuantityExpectedBytes is the expected width in bytes of the canonical string representation
|
// int64QuantityExpectedBytes is the expected width in bytes of the canonical string representation
|
||||||
// of most Quantity values.
|
// of most Quantity values.
|
||||||
const int64QuantityExpectedBytes = 18
|
const int64QuantityExpectedBytes = 18
|
||||||
|
@ -18,8 +18,8 @@ go_library(
|
|||||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/diff",
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/diff",
|
||||||
importpath = "k8s.io/apimachinery/pkg/util/diff",
|
importpath = "k8s.io/apimachinery/pkg/util/diff",
|
||||||
deps = [
|
deps = [
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
|
||||||
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
||||||
|
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,16 +18,12 @@ package diff
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StringDiff diffs a and b and returns a human readable diff.
|
// StringDiff diffs a and b and returns a human readable diff.
|
||||||
@ -50,233 +46,29 @@ func StringDiff(a, b string) string {
|
|||||||
return string(out)
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectDiff writes the two objects out as JSON and prints out the identical part of
|
func legacyDiff(a, b interface{}) string {
|
||||||
// the objects followed by the remaining part of 'a' and finally the remaining part of 'b'.
|
return cmp.Diff(a, b)
|
||||||
// For debugging tests.
|
}
|
||||||
|
|
||||||
|
// ObjectDiff prints the diff of two go objects and fails if the objects
|
||||||
|
// contain unhandled unexported fields.
|
||||||
|
// DEPRECATED: use github.com/google/go-cmp/cmp.Diff
|
||||||
func ObjectDiff(a, b interface{}) string {
|
func ObjectDiff(a, b interface{}) string {
|
||||||
ab, err := json.Marshal(a)
|
return legacyDiff(a, b)
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("a: %v", err))
|
|
||||||
}
|
|
||||||
bb, err := json.Marshal(b)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("b: %v", err))
|
|
||||||
}
|
|
||||||
return StringDiff(string(ab), string(bb))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectGoPrintDiff is like ObjectDiff, but uses go-spew to print the objects,
|
// ObjectGoPrintDiff prints the diff of two go objects and fails if the objects
|
||||||
// which shows absolutely everything by recursing into every single pointer
|
// contain unhandled unexported fields.
|
||||||
// (go's %#v formatters OTOH stop at a certain point). This is needed when you
|
// DEPRECATED: use github.com/google/go-cmp/cmp.Diff
|
||||||
// can't figure out why reflect.DeepEqual is returning false and nothing is
|
|
||||||
// showing you differences. This will.
|
|
||||||
func ObjectGoPrintDiff(a, b interface{}) string {
|
func ObjectGoPrintDiff(a, b interface{}) string {
|
||||||
s := spew.ConfigState{DisableMethods: true}
|
return legacyDiff(a, b)
|
||||||
return StringDiff(
|
|
||||||
s.Sprintf("%#v", a),
|
|
||||||
s.Sprintf("%#v", b),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectReflectDiff returns a multi-line formatted diff between two objects
|
// ObjectReflectDiff prints the diff of two go objects and fails if the objects
|
||||||
// of equal type. If an object with private fields is passed you will
|
// contain unhandled unexported fields.
|
||||||
// only see string comparison for those fields. Otherwise this presents the
|
// DEPRECATED: use github.com/google/go-cmp/cmp.Diff
|
||||||
// most human friendly diff of two structs of equal type in this package.
|
|
||||||
func ObjectReflectDiff(a, b interface{}) string {
|
func ObjectReflectDiff(a, b interface{}) string {
|
||||||
if a == nil && b == nil {
|
return legacyDiff(a, b)
|
||||||
return "<no diffs>"
|
|
||||||
}
|
|
||||||
if a == nil {
|
|
||||||
return fmt.Sprintf("a is nil and b is not-nil")
|
|
||||||
}
|
|
||||||
if b == nil {
|
|
||||||
return fmt.Sprintf("a is not-nil and b is nil")
|
|
||||||
}
|
|
||||||
vA, vB := reflect.ValueOf(a), reflect.ValueOf(b)
|
|
||||||
if vA.Type() != vB.Type() {
|
|
||||||
return fmt.Sprintf("type A %T and type B %T do not match", a, b)
|
|
||||||
}
|
|
||||||
diffs := objectReflectDiff(field.NewPath("object"), vA, vB)
|
|
||||||
if len(diffs) == 0 {
|
|
||||||
return "<no diffs>"
|
|
||||||
}
|
|
||||||
out := []string{""}
|
|
||||||
for _, d := range diffs {
|
|
||||||
elidedA, elidedB := limit(d.a, d.b, 80)
|
|
||||||
out = append(out,
|
|
||||||
fmt.Sprintf("%s:", d.path),
|
|
||||||
fmt.Sprintf(" a: %s", elidedA),
|
|
||||||
fmt.Sprintf(" b: %s", elidedB),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return strings.Join(out, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// limit:
|
|
||||||
// 1. stringifies aObj and bObj
|
|
||||||
// 2. elides identical prefixes if either is too long
|
|
||||||
// 3. elides remaining content from the end if either is too long
|
|
||||||
func limit(aObj, bObj interface{}, max int) (string, string) {
|
|
||||||
elidedPrefix := ""
|
|
||||||
elidedASuffix := ""
|
|
||||||
elidedBSuffix := ""
|
|
||||||
a, b := fmt.Sprintf("%#v", aObj), fmt.Sprintf("%#v", bObj)
|
|
||||||
|
|
||||||
if aObj != nil && bObj != nil {
|
|
||||||
if aType, bType := fmt.Sprintf("%T", aObj), fmt.Sprintf("%T", bObj); aType != bType {
|
|
||||||
a = fmt.Sprintf("%s (%s)", a, aType)
|
|
||||||
b = fmt.Sprintf("%s (%s)", b, bType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
switch {
|
|
||||||
case len(a) > max && len(a) > 4 && len(b) > 4 && a[:4] == b[:4]:
|
|
||||||
// a is too long, b has data, and the first several characters are the same
|
|
||||||
elidedPrefix = "..."
|
|
||||||
a = a[2:]
|
|
||||||
b = b[2:]
|
|
||||||
|
|
||||||
case len(b) > max && len(b) > 4 && len(a) > 4 && a[:4] == b[:4]:
|
|
||||||
// b is too long, a has data, and the first several characters are the same
|
|
||||||
elidedPrefix = "..."
|
|
||||||
a = a[2:]
|
|
||||||
b = b[2:]
|
|
||||||
|
|
||||||
case len(a) > max:
|
|
||||||
a = a[:max]
|
|
||||||
elidedASuffix = "..."
|
|
||||||
|
|
||||||
case len(b) > max:
|
|
||||||
b = b[:max]
|
|
||||||
elidedBSuffix = "..."
|
|
||||||
|
|
||||||
default:
|
|
||||||
// both are short enough
|
|
||||||
return elidedPrefix + a + elidedASuffix, elidedPrefix + b + elidedBSuffix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func public(s string) bool {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s[:1] == strings.ToUpper(s[:1])
|
|
||||||
}
|
|
||||||
|
|
||||||
type diff struct {
|
|
||||||
path *field.Path
|
|
||||||
a, b interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type orderedDiffs []diff
|
|
||||||
|
|
||||||
func (d orderedDiffs) Len() int { return len(d) }
|
|
||||||
func (d orderedDiffs) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
|
||||||
func (d orderedDiffs) Less(i, j int) bool {
|
|
||||||
a, b := d[i].path.String(), d[j].path.String()
|
|
||||||
if a < b {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func objectReflectDiff(path *field.Path, a, b reflect.Value) []diff {
|
|
||||||
switch a.Type().Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
var changes []diff
|
|
||||||
for i := 0; i < a.Type().NumField(); i++ {
|
|
||||||
if !public(a.Type().Field(i).Name) {
|
|
||||||
if reflect.DeepEqual(a.Interface(), b.Interface()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return []diff{{path: path, a: fmt.Sprintf("%#v", a), b: fmt.Sprintf("%#v", b)}}
|
|
||||||
}
|
|
||||||
if sub := objectReflectDiff(path.Child(a.Type().Field(i).Name), a.Field(i), b.Field(i)); len(sub) > 0 {
|
|
||||||
changes = append(changes, sub...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changes
|
|
||||||
case reflect.Ptr, reflect.Interface:
|
|
||||||
if a.IsNil() || b.IsNil() {
|
|
||||||
switch {
|
|
||||||
case a.IsNil() && b.IsNil():
|
|
||||||
return nil
|
|
||||||
case a.IsNil():
|
|
||||||
return []diff{{path: path, a: nil, b: b.Interface()}}
|
|
||||||
default:
|
|
||||||
return []diff{{path: path, a: a.Interface(), b: nil}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return objectReflectDiff(path, a.Elem(), b.Elem())
|
|
||||||
case reflect.Chan:
|
|
||||||
if !reflect.DeepEqual(a.Interface(), b.Interface()) {
|
|
||||||
return []diff{{path: path, a: a.Interface(), b: b.Interface()}}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case reflect.Slice:
|
|
||||||
lA, lB := a.Len(), b.Len()
|
|
||||||
l := lA
|
|
||||||
if lB < lA {
|
|
||||||
l = lB
|
|
||||||
}
|
|
||||||
if lA == lB && lA == 0 {
|
|
||||||
if a.IsNil() != b.IsNil() {
|
|
||||||
return []diff{{path: path, a: a.Interface(), b: b.Interface()}}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var diffs []diff
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
if !reflect.DeepEqual(a.Index(i), b.Index(i)) {
|
|
||||||
diffs = append(diffs, objectReflectDiff(path.Index(i), a.Index(i), b.Index(i))...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := l; i < lA; i++ {
|
|
||||||
diffs = append(diffs, diff{path: path.Index(i), a: a.Index(i), b: nil})
|
|
||||||
}
|
|
||||||
for i := l; i < lB; i++ {
|
|
||||||
diffs = append(diffs, diff{path: path.Index(i), a: nil, b: b.Index(i)})
|
|
||||||
}
|
|
||||||
return diffs
|
|
||||||
case reflect.Map:
|
|
||||||
if reflect.DeepEqual(a.Interface(), b.Interface()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
aKeys := make(map[interface{}]interface{})
|
|
||||||
for _, key := range a.MapKeys() {
|
|
||||||
aKeys[key.Interface()] = a.MapIndex(key).Interface()
|
|
||||||
}
|
|
||||||
var missing []diff
|
|
||||||
for _, key := range b.MapKeys() {
|
|
||||||
if _, ok := aKeys[key.Interface()]; ok {
|
|
||||||
delete(aKeys, key.Interface())
|
|
||||||
if reflect.DeepEqual(a.MapIndex(key).Interface(), b.MapIndex(key).Interface()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
missing = append(missing, objectReflectDiff(path.Key(fmt.Sprintf("%s", key.Interface())), a.MapIndex(key), b.MapIndex(key))...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
missing = append(missing, diff{path: path.Key(fmt.Sprintf("%s", key.Interface())), a: nil, b: b.MapIndex(key).Interface()})
|
|
||||||
}
|
|
||||||
for key, value := range aKeys {
|
|
||||||
missing = append(missing, diff{path: path.Key(fmt.Sprintf("%s", key)), a: value, b: nil})
|
|
||||||
}
|
|
||||||
if len(missing) == 0 {
|
|
||||||
missing = append(missing, diff{path: path, a: a.Interface(), b: b.Interface()})
|
|
||||||
}
|
|
||||||
sort.Sort(orderedDiffs(missing))
|
|
||||||
return missing
|
|
||||||
default:
|
|
||||||
if reflect.DeepEqual(a.Interface(), b.Interface()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !a.CanInterface() {
|
|
||||||
return []diff{{path: path, a: fmt.Sprintf("%#v", a), b: fmt.Sprintf("%#v", b)}}
|
|
||||||
}
|
|
||||||
return []diff{{path: path, a: a.Interface(), b: b.Interface()}}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectGoPrintSideBySide prints a and b as textual dumps side by side,
|
// ObjectGoPrintSideBySide prints a and b as textual dumps side by side,
|
||||||
|
@ -20,92 +20,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestObjectReflectDiff(t *testing.T) {
|
|
||||||
type struct1 struct{ A []int }
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
a, b interface{}
|
|
||||||
out string
|
|
||||||
}{
|
|
||||||
"both nil": {
|
|
||||||
a: interface{}(nil),
|
|
||||||
b: interface{}(nil),
|
|
||||||
},
|
|
||||||
"a nil": {
|
|
||||||
a: interface{}(nil),
|
|
||||||
b: "test",
|
|
||||||
out: "a is nil and b is not-nil",
|
|
||||||
},
|
|
||||||
"b nil": {
|
|
||||||
a: "test",
|
|
||||||
b: interface{}(nil),
|
|
||||||
out: "a is not-nil and b is nil",
|
|
||||||
},
|
|
||||||
"map": {
|
|
||||||
a: map[string]int{},
|
|
||||||
b: map[string]int{},
|
|
||||||
},
|
|
||||||
"detect nil map": {
|
|
||||||
a: map[string]int(nil),
|
|
||||||
b: map[string]int{},
|
|
||||||
out: `
|
|
||||||
object:
|
|
||||||
a: map[string]int(nil)
|
|
||||||
b: map[string]int{}`,
|
|
||||||
},
|
|
||||||
"detect map changes": {
|
|
||||||
a: map[string]int{"test": 1, "other": 2},
|
|
||||||
b: map[string]int{"test": 2, "third": 3},
|
|
||||||
out: `
|
|
||||||
object[other]:
|
|
||||||
a: 2
|
|
||||||
b: <nil>
|
|
||||||
object[test]:
|
|
||||||
a: 1
|
|
||||||
b: 2
|
|
||||||
object[third]:
|
|
||||||
a: <nil>
|
|
||||||
b: 3`,
|
|
||||||
},
|
|
||||||
"nil slice": {a: struct1{A: nil}, b: struct1{A: nil}},
|
|
||||||
"empty slice": {a: struct1{A: []int{}}, b: struct1{A: []int{}}},
|
|
||||||
"detect slice changes 1": {a: struct1{A: []int{1}}, b: struct1{A: []int{2}}, out: `
|
|
||||||
object.A[0]:
|
|
||||||
a: 1
|
|
||||||
b: 2`,
|
|
||||||
},
|
|
||||||
"detect slice changes 2": {a: struct1{A: []int{}}, b: struct1{A: []int{2}}, out: `
|
|
||||||
object.A[0]:
|
|
||||||
a: <nil>
|
|
||||||
b: 2`,
|
|
||||||
},
|
|
||||||
"detect slice changes 3": {a: struct1{A: []int{1}}, b: struct1{A: []int{}}, out: `
|
|
||||||
object.A[0]:
|
|
||||||
a: 1
|
|
||||||
b: <nil>`,
|
|
||||||
},
|
|
||||||
"detect nil vs empty slices": {a: struct1{A: nil}, b: struct1{A: []int{}}, out: `
|
|
||||||
object.A:
|
|
||||||
a: []int(nil)
|
|
||||||
b: []int{}`,
|
|
||||||
},
|
|
||||||
"display type differences": {a: []interface{}{int64(1)}, b: []interface{}{uint64(1)}, out: `
|
|
||||||
object[0]:
|
|
||||||
a: 1 (int64)
|
|
||||||
b: 0x1 (uint64)`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, test := range testCases {
|
|
||||||
expect := test.out
|
|
||||||
if len(expect) == 0 {
|
|
||||||
expect = "<no diffs>"
|
|
||||||
}
|
|
||||||
if actual := ObjectReflectDiff(test.a, test.b); actual != expect {
|
|
||||||
t.Errorf("%s: unexpected output: %s", name, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringDiff(t *testing.T) {
|
func TestStringDiff(t *testing.T) {
|
||||||
diff := StringDiff("aaabb", "aaacc")
|
diff := StringDiff("aaabb", "aaacc")
|
||||||
expect := "aaa\n\nA: bb\n\nB: cc\n\n"
|
expect := "aaa\n\nA: bb\n\nB: cc\n\n"
|
||||||
@ -113,50 +27,3 @@ func TestStringDiff(t *testing.T) {
|
|||||||
t.Errorf("diff returned %v", diff)
|
t.Errorf("diff returned %v", diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLimit(t *testing.T) {
|
|
||||||
testcases := []struct {
|
|
||||||
a interface{}
|
|
||||||
b interface{}
|
|
||||||
expectA string
|
|
||||||
expectB string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
a: `short a`,
|
|
||||||
b: `short b`,
|
|
||||||
expectA: `"short a"`,
|
|
||||||
expectB: `"short b"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: `short a`,
|
|
||||||
b: `long b needs truncating`,
|
|
||||||
expectA: `"short a"`,
|
|
||||||
expectB: `"long b ne...`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: `long a needs truncating`,
|
|
||||||
b: `long b needs truncating`,
|
|
||||||
expectA: `...g a needs ...`,
|
|
||||||
expectB: `...g b needs ...`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: `long common prefix with different stuff at the end of a`,
|
|
||||||
b: `long common prefix with different stuff at the end of b`,
|
|
||||||
expectA: `...end of a"`,
|
|
||||||
expectB: `...end of b"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: `long common prefix with different stuff at the end of a`,
|
|
||||||
b: `long common prefix with different stuff at the end of b which continues`,
|
|
||||||
expectA: `...of a"`,
|
|
||||||
expectB: `...of b which...`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testcases {
|
|
||||||
a, b := limit(tc.a, tc.b, 10)
|
|
||||||
if a != tc.expectA || b != tc.expectB {
|
|
||||||
t.Errorf("limit(%q, %q)\n\texpected: %s, %s\n\tgot: %s, %s", tc.a, tc.b, tc.expectA, tc.expectB, a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user