| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- package messagediff
- import (
- "fmt"
- "reflect"
- "sort"
- "strings"
- "unsafe"
- )
- // PrettyDiff does a deep comparison and returns the nicely formated results.
- func PrettyDiff(a, b interface{}) (string, bool) {
- d, equal := DeepDiff(a, b)
- var dstr []string
- for path, added := range d.Added {
- dstr = append(dstr, fmt.Sprintf("added: %s = %#v\n", path.String(), added))
- }
- for path, removed := range d.Removed {
- dstr = append(dstr, fmt.Sprintf("removed: %s = %#v\n", path.String(), removed))
- }
- for path, modified := range d.Modified {
- dstr = append(dstr, fmt.Sprintf("modified: %s = %#v\n", path.String(), modified))
- }
- sort.Strings(dstr)
- return strings.Join(dstr, ""), equal
- }
- // DeepDiff does a deep comparison and returns the results.
- func DeepDiff(a, b interface{}) (*Diff, bool) {
- d := newDiff()
- return d, d.diff(reflect.ValueOf(a), reflect.ValueOf(b), nil)
- }
- func newDiff() *Diff {
- return &Diff{
- Added: make(map[*Path]interface{}),
- Removed: make(map[*Path]interface{}),
- Modified: make(map[*Path]interface{}),
- visited: make(map[visit]bool),
- }
- }
- func (d *Diff) diff(aVal, bVal reflect.Value, path Path) bool {
- // The array underlying `path` could be modified in subsequent
- // calls. Make sure we have a local copy.
- localPath := make(Path, len(path))
- copy(localPath, path)
- // Validity checks. Should only trigger if nil is one of the original arguments.
- if !aVal.IsValid() && !bVal.IsValid() {
- return true
- }
- if !bVal.IsValid() {
- d.Modified[&localPath] = nil
- return false
- } else if !aVal.IsValid() {
- d.Modified[&localPath] = bVal.Interface()
- return false
- }
- if aVal.Type() != bVal.Type() {
- d.Modified[&localPath] = bVal.Interface()
- return false
- }
- kind := aVal.Kind()
- // Borrowed from the reflect package to handle recursive data structures.
- hard := func(k reflect.Kind) bool {
- switch k {
- case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
- return true
- }
- return false
- }
- if aVal.CanAddr() && bVal.CanAddr() && hard(kind) {
- addr1 := unsafe.Pointer(aVal.UnsafeAddr())
- addr2 := unsafe.Pointer(bVal.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 := aVal.Type()
- v := visit{addr1, addr2, typ}
- if d.visited[v] {
- return true
- }
- // Remember for later.
- d.visited[v] = true
- }
- // End of borrowed code.
- equal := true
- switch kind {
- case reflect.Map, reflect.Ptr, reflect.Func, reflect.Chan, reflect.Slice:
- if aVal.IsNil() && bVal.IsNil() {
- return true
- }
- if aVal.IsNil() || bVal.IsNil() {
- d.Modified[&localPath] = bVal.Interface()
- return false
- }
- }
- switch kind {
- case reflect.Array, reflect.Slice:
- aLen := aVal.Len()
- bLen := bVal.Len()
- for i := 0; i < min(aLen, bLen); i++ {
- localPath := append(localPath, SliceIndex(i))
- if eq := d.diff(aVal.Index(i), bVal.Index(i), localPath); !eq {
- equal = false
- }
- }
- if aLen > bLen {
- for i := bLen; i < aLen; i++ {
- localPath := append(localPath, SliceIndex(i))
- d.Removed[&localPath] = aVal.Index(i).Interface()
- equal = false
- }
- } else if aLen < bLen {
- for i := aLen; i < bLen; i++ {
- localPath := append(localPath, SliceIndex(i))
- d.Added[&localPath] = bVal.Index(i).Interface()
- equal = false
- }
- }
- case reflect.Map:
- for _, key := range aVal.MapKeys() {
- aI := aVal.MapIndex(key)
- bI := bVal.MapIndex(key)
- localPath := append(localPath, MapKey{key.Interface()})
- if !bI.IsValid() {
- d.Removed[&localPath] = aI.Interface()
- equal = false
- } else if eq := d.diff(aI, bI, localPath); !eq {
- equal = false
- }
- }
- for _, key := range bVal.MapKeys() {
- aI := aVal.MapIndex(key)
- if !aI.IsValid() {
- bI := bVal.MapIndex(key)
- localPath := append(localPath, MapKey{key.Interface()})
- d.Added[&localPath] = bI.Interface()
- equal = false
- }
- }
- case reflect.Struct:
- typ := aVal.Type()
- for i := 0; i < typ.NumField(); i++ {
- index := []int{i}
- field := typ.FieldByIndex(index)
- if field.Tag.Get("testdiff") == "ignore" { // skip fields marked to be ignored
- continue
- }
- localPath := append(localPath, StructField(field.Name))
- aI := unsafeReflectValue(aVal.FieldByIndex(index))
- bI := unsafeReflectValue(bVal.FieldByIndex(index))
- if eq := d.diff(aI, bI, localPath); !eq {
- equal = false
- }
- }
- case reflect.Ptr:
- equal = d.diff(aVal.Elem(), bVal.Elem(), localPath)
- default:
- if reflect.DeepEqual(aVal.Interface(), bVal.Interface()) {
- equal = true
- } else {
- d.Modified[&localPath] = bVal.Interface()
- equal = false
- }
- }
- return equal
- }
- func min(a, b int) int {
- if a < b {
- return a
- }
- return b
- }
- // 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.
- // This is borrowed from the reflect package.
- type visit struct {
- a1 unsafe.Pointer
- a2 unsafe.Pointer
- typ reflect.Type
- }
- // Diff represents a change in a struct.
- type Diff struct {
- Added, Removed, Modified map[*Path]interface{}
- visited map[visit]bool
- }
- // Path represents a path to a changed datum.
- type Path []PathNode
- func (p Path) String() string {
- var out string
- for _, n := range p {
- out += n.String()
- }
- return out
- }
- // PathNode represents one step in the path.
- type PathNode interface {
- String() string
- }
- // StructField is a path element representing a field of a struct.
- type StructField string
- func (n StructField) String() string {
- return fmt.Sprintf(".%s", string(n))
- }
- // MapKey is a path element representing a key of a map.
- type MapKey struct {
- Key interface{}
- }
- func (n MapKey) String() string {
- return fmt.Sprintf("[%#v]", n.Key)
- }
- // SliceIndex is a path element representing a index of a slice.
- type SliceIndex int
- func (n SliceIndex) String() string {
- return fmt.Sprintf("[%d]", n)
- }
|