123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- /*
- Copyright 2014 The Kubernetes Authors.
- 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.
- */
- package diff
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "reflect"
- "sort"
- "strings"
- "text/tabwriter"
- "github.com/davecgh/go-spew/spew"
- "k8s.io/apimachinery/pkg/util/validation/field"
- )
- // StringDiff diffs a and b and returns a human readable diff.
- func StringDiff(a, b string) string {
- ba := []byte(a)
- bb := []byte(b)
- out := []byte{}
- i := 0
- for ; i < len(ba) && i < len(bb); i++ {
- if ba[i] != bb[i] {
- break
- }
- out = append(out, ba[i])
- }
- out = append(out, []byte("\n\nA: ")...)
- out = append(out, ba[i:]...)
- out = append(out, []byte("\n\nB: ")...)
- out = append(out, bb[i:]...)
- out = append(out, []byte("\n\n")...)
- return string(out)
- }
- // ObjectDiff writes the two objects out as JSON and prints out the identical part of
- // the objects followed by the remaining part of 'a' and finally the remaining part of 'b'.
- // For debugging tests.
- func ObjectDiff(a, b interface{}) string {
- ab, err := json.Marshal(a)
- 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,
- // which shows absolutely everything by recursing into every single pointer
- // (go's %#v formatters OTOH stop at a certain point). This is needed when you
- // can't figure out why reflect.DeepEqual is returning false and nothing is
- // showing you differences. This will.
- func ObjectGoPrintDiff(a, b interface{}) string {
- s := spew.ConfigState{DisableMethods: true}
- return StringDiff(
- s.Sprintf("%#v", a),
- s.Sprintf("%#v", b),
- )
- }
- func ObjectReflectDiff(a, b interface{}) string {
- 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,
- // enabling easy visual scanning for mismatches.
- func ObjectGoPrintSideBySide(a, b interface{}) string {
- s := spew.ConfigState{
- Indent: " ",
- // Extra deep spew.
- DisableMethods: true,
- }
- sA := s.Sdump(a)
- sB := s.Sdump(b)
- linesA := strings.Split(sA, "\n")
- linesB := strings.Split(sB, "\n")
- width := 0
- for _, s := range linesA {
- l := len(s)
- if l > width {
- width = l
- }
- }
- for _, s := range linesB {
- l := len(s)
- if l > width {
- width = l
- }
- }
- buf := &bytes.Buffer{}
- w := tabwriter.NewWriter(buf, width, 0, 1, ' ', 0)
- max := len(linesA)
- if len(linesB) > max {
- max = len(linesB)
- }
- for i := 0; i < max; i++ {
- var a, b string
- if i < len(linesA) {
- a = linesA[i]
- }
- if i < len(linesB) {
- b = linesB[i]
- }
- fmt.Fprintf(w, "%s\t%s\n", a, b)
- }
- w.Flush()
- return buf.String()
- }
|