407 lines
12 KiB
Go
407 lines
12 KiB
Go
// Copyright 2017, 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.md file.
|
|
|
|
package cmp
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// TODO: Can we leave the interface for a reporter here in the cmp package
|
|
// and somehow extract the implementation of defaultReporter into cmp/report?
|
|
|
|
type defaultReporter struct {
|
|
Option
|
|
diffs []string // List of differences, possibly truncated
|
|
ndiffs int // Total number of differences
|
|
nbytes int // Number of bytes in diffs
|
|
nlines int // Number of lines in diffs
|
|
}
|
|
|
|
var _ reporter = (*defaultReporter)(nil)
|
|
|
|
func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) {
|
|
// TODO: Is there a way to nicely print added/modified/removed elements
|
|
// from a slice? This will most certainly require support from the
|
|
// equality logic, but what would be the right API for this?
|
|
//
|
|
// The current API is equivalent to a Hamming distance for measuring the
|
|
// difference between two sequences of symbols. That is, the only operation
|
|
// we can represent is substitution. The new API would need to handle a
|
|
// Levenshtein distance, such that insertions, deletions, and substitutions
|
|
// are permitted. Furthermore, this will require an algorithm for computing
|
|
// the edit distance. Unfortunately, the time complexity for a minimal
|
|
// edit distance algorithm is not much better than O(n^2).
|
|
// There are approximations for the algorithm that can run much faster.
|
|
// See literature on computing Levenshtein distance.
|
|
//
|
|
// Passing in a pair of x and y is actually good for representing insertion
|
|
// and deletion by the fact that x or y may be an invalid value. However,
|
|
// we may need to pass in two paths px and py, to indicate the paths
|
|
// relative to x and y. Alternative, since we only perform the Levenshtein
|
|
// distance on slices, maybe we alter the SliceIndex type to record
|
|
// two different indexes.
|
|
|
|
// TODO: Perhaps we should coalesce differences on primitive kinds
|
|
// together if the number of differences exceeds some ratio.
|
|
// For example, comparing two SHA256s leads to many byte differences.
|
|
|
|
if eq {
|
|
// TODO: Maybe print some equal results for context?
|
|
return // Ignore equal results
|
|
}
|
|
const maxBytes = 4096
|
|
const maxLines = 256
|
|
r.ndiffs++
|
|
if r.nbytes < maxBytes && r.nlines < maxLines {
|
|
sx := prettyPrint(x, true)
|
|
sy := prettyPrint(y, true)
|
|
if sx == sy {
|
|
// Use of Stringer is not helpful, so rely on more exact formatting.
|
|
sx = prettyPrint(x, false)
|
|
sy = prettyPrint(y, false)
|
|
}
|
|
s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
|
|
r.diffs = append(r.diffs, s)
|
|
r.nbytes += len(s)
|
|
r.nlines += strings.Count(s, "\n")
|
|
}
|
|
}
|
|
|
|
func (r *defaultReporter) String() string {
|
|
s := strings.Join(r.diffs, "")
|
|
if r.ndiffs == len(r.diffs) {
|
|
return s
|
|
}
|
|
return fmt.Sprintf("%s... %d more differences ...", s, len(r.diffs)-r.ndiffs)
|
|
}
|
|
|
|
var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
|
|
|
func prettyPrint(v reflect.Value, useStringer bool) string {
|
|
return formatAny(v, formatConfig{useStringer, true, true, true}, nil)
|
|
}
|
|
|
|
type formatConfig struct {
|
|
useStringer bool // Should the String method be used if available?
|
|
printType bool // Should we print the type before the value?
|
|
followPointers bool // Should we recursively follow pointers?
|
|
realPointers bool // Should we print the real address of pointers?
|
|
}
|
|
|
|
// formatAny prints the value v in a pretty formatted manner.
|
|
// This is similar to fmt.Sprintf("%+v", v) except this:
|
|
// * Prints the type unless it can be elided.
|
|
// * Avoids printing struct fields that are zero.
|
|
// * Prints a nil-slice as being nil, not empty.
|
|
// * Prints map entries in deterministic order.
|
|
func formatAny(v reflect.Value, conf formatConfig, visited map[uintptr]bool) string {
|
|
// TODO: Should this be a multi-line printout in certain situations?
|
|
|
|
if !v.IsValid() {
|
|
return "<non-existent>"
|
|
}
|
|
if conf.useStringer && v.Type().Implements(stringerIface) {
|
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
|
return "<nil>"
|
|
}
|
|
return fmt.Sprintf("%q", v.Interface().(fmt.Stringer).String())
|
|
}
|
|
|
|
switch v.Kind() {
|
|
case reflect.Bool:
|
|
return fmt.Sprint(v.Bool())
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return fmt.Sprint(v.Int())
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
|
|
return formatHex(v.Uint()) // Unnamed uints are usually bytes or words
|
|
}
|
|
return fmt.Sprint(v.Uint()) // Named uints are usually enumerations
|
|
case reflect.Float32, reflect.Float64:
|
|
return fmt.Sprint(v.Float())
|
|
case reflect.Complex64, reflect.Complex128:
|
|
return fmt.Sprint(v.Complex())
|
|
case reflect.String:
|
|
return fmt.Sprintf("%q", v)
|
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
return formatPointer(v, conf)
|
|
case reflect.Ptr:
|
|
if v.IsNil() {
|
|
if conf.printType {
|
|
return fmt.Sprintf("(%v)(nil)", v.Type())
|
|
}
|
|
return "<nil>"
|
|
}
|
|
if visited[v.Pointer()] || !conf.followPointers {
|
|
return formatPointer(v, conf)
|
|
}
|
|
visited = insertPointer(visited, v.Pointer())
|
|
return "&" + formatAny(v.Elem(), conf, visited)
|
|
case reflect.Interface:
|
|
if v.IsNil() {
|
|
if conf.printType {
|
|
return fmt.Sprintf("%v(nil)", v.Type())
|
|
}
|
|
return "<nil>"
|
|
}
|
|
return formatAny(v.Elem(), conf, visited)
|
|
case reflect.Slice:
|
|
if v.IsNil() {
|
|
if conf.printType {
|
|
return fmt.Sprintf("%v(nil)", v.Type())
|
|
}
|
|
return "<nil>"
|
|
}
|
|
if visited[v.Pointer()] {
|
|
return formatPointer(v, conf)
|
|
}
|
|
visited = insertPointer(visited, v.Pointer())
|
|
fallthrough
|
|
case reflect.Array:
|
|
var ss []string
|
|
subConf := conf
|
|
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
|
for i := 0; i < v.Len(); i++ {
|
|
s := formatAny(v.Index(i), subConf, visited)
|
|
ss = append(ss, s)
|
|
}
|
|
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
|
if conf.printType {
|
|
return v.Type().String() + s
|
|
}
|
|
return s
|
|
case reflect.Map:
|
|
if v.IsNil() {
|
|
if conf.printType {
|
|
return fmt.Sprintf("%v(nil)", v.Type())
|
|
}
|
|
return "<nil>"
|
|
}
|
|
if visited[v.Pointer()] {
|
|
return formatPointer(v, conf)
|
|
}
|
|
visited = insertPointer(visited, v.Pointer())
|
|
|
|
var ss []string
|
|
subConf := conf
|
|
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
|
for _, k := range sortKeys(v.MapKeys()) {
|
|
sk := formatAny(k, formatConfig{realPointers: conf.realPointers}, visited)
|
|
sv := formatAny(v.MapIndex(k), subConf, visited)
|
|
ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
|
|
}
|
|
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
|
if conf.printType {
|
|
return v.Type().String() + s
|
|
}
|
|
return s
|
|
case reflect.Struct:
|
|
var ss []string
|
|
subConf := conf
|
|
subConf.printType = true
|
|
for i := 0; i < v.NumField(); i++ {
|
|
vv := v.Field(i)
|
|
if isZero(vv) {
|
|
continue // Elide zero value fields
|
|
}
|
|
name := v.Type().Field(i).Name
|
|
subConf.useStringer = conf.useStringer && isExported(name)
|
|
s := formatAny(vv, subConf, visited)
|
|
ss = append(ss, fmt.Sprintf("%s: %s", name, s))
|
|
}
|
|
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
|
if conf.printType {
|
|
return v.Type().String() + s
|
|
}
|
|
return s
|
|
default:
|
|
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
|
}
|
|
}
|
|
|
|
func formatPointer(v reflect.Value, conf formatConfig) string {
|
|
p := v.Pointer()
|
|
if !conf.realPointers {
|
|
p = 0 // For deterministic printing purposes
|
|
}
|
|
s := formatHex(uint64(p))
|
|
if conf.printType {
|
|
return fmt.Sprintf("(%v)(%s)", v.Type(), s)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func formatHex(u uint64) string {
|
|
var f string
|
|
switch {
|
|
case u <= 0xff:
|
|
f = "0x%02x"
|
|
case u <= 0xffff:
|
|
f = "0x%04x"
|
|
case u <= 0xffffff:
|
|
f = "0x%06x"
|
|
case u <= 0xffffffff:
|
|
f = "0x%08x"
|
|
case u <= 0xffffffffff:
|
|
f = "0x%010x"
|
|
case u <= 0xffffffffffff:
|
|
f = "0x%012x"
|
|
case u <= 0xffffffffffffff:
|
|
f = "0x%014x"
|
|
case u <= 0xffffffffffffffff:
|
|
f = "0x%016x"
|
|
}
|
|
return fmt.Sprintf(f, u)
|
|
}
|
|
|
|
// insertPointer insert p into m, allocating m if necessary.
|
|
func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool {
|
|
if m == nil {
|
|
m = make(map[uintptr]bool)
|
|
}
|
|
m[p] = true
|
|
return m
|
|
}
|
|
|
|
// isZero reports whether v is the zero value.
|
|
// This does not rely on Interface and so can be used on unexported fields.
|
|
func isZero(v reflect.Value) bool {
|
|
switch v.Kind() {
|
|
case reflect.Bool:
|
|
return v.Bool() == false
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return v.Int() == 0
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
return v.Uint() == 0
|
|
case reflect.Float32, reflect.Float64:
|
|
return v.Float() == 0
|
|
case reflect.Complex64, reflect.Complex128:
|
|
return v.Complex() == 0
|
|
case reflect.String:
|
|
return v.String() == ""
|
|
case reflect.UnsafePointer:
|
|
return v.Pointer() == 0
|
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
|
return v.IsNil()
|
|
case reflect.Array:
|
|
for i := 0; i < v.Len(); i++ {
|
|
if !isZero(v.Index(i)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
case reflect.Struct:
|
|
for i := 0; i < v.NumField(); i++ {
|
|
if !isZero(v.Field(i)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isLess is a generic function for sorting arbitrary map keys.
|
|
// The inputs must be of the same type and must be comparable.
|
|
func isLess(x, y reflect.Value) bool {
|
|
switch x.Type().Kind() {
|
|
case reflect.Bool:
|
|
return !x.Bool() && y.Bool()
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return x.Int() < y.Int()
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
return x.Uint() < y.Uint()
|
|
case reflect.Float32, reflect.Float64:
|
|
fx, fy := x.Float(), y.Float()
|
|
return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
|
|
case reflect.Complex64, reflect.Complex128:
|
|
cx, cy := x.Complex(), y.Complex()
|
|
rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
|
|
if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
|
|
return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
|
|
}
|
|
return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
|
|
case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
|
|
return x.Pointer() < y.Pointer()
|
|
case reflect.String:
|
|
return x.String() < y.String()
|
|
case reflect.Array:
|
|
for i := 0; i < x.Len(); i++ {
|
|
if isLess(x.Index(i), y.Index(i)) {
|
|
return true
|
|
}
|
|
if isLess(y.Index(i), x.Index(i)) {
|
|
return false
|
|
}
|
|
}
|
|
return false
|
|
case reflect.Struct:
|
|
for i := 0; i < x.NumField(); i++ {
|
|
if isLess(x.Field(i), y.Field(i)) {
|
|
return true
|
|
}
|
|
if isLess(y.Field(i), x.Field(i)) {
|
|
return false
|
|
}
|
|
}
|
|
return false
|
|
case reflect.Interface:
|
|
vx, vy := x.Elem(), y.Elem()
|
|
if !vx.IsValid() || !vy.IsValid() {
|
|
return !vx.IsValid() && vy.IsValid()
|
|
}
|
|
tx, ty := vx.Type(), vy.Type()
|
|
if tx == ty {
|
|
return isLess(x.Elem(), y.Elem())
|
|
}
|
|
if tx.Kind() != ty.Kind() {
|
|
return vx.Kind() < vy.Kind()
|
|
}
|
|
if tx.String() != ty.String() {
|
|
return tx.String() < ty.String()
|
|
}
|
|
if tx.PkgPath() != ty.PkgPath() {
|
|
return tx.PkgPath() < ty.PkgPath()
|
|
}
|
|
// This can happen in rare situations, so we fallback to just comparing
|
|
// the unique pointer for a reflect.Type. This guarantees deterministic
|
|
// ordering within a program, but it is obviously not stable.
|
|
return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
|
|
default:
|
|
// Must be Func, Map, or Slice; which are not comparable.
|
|
panic(fmt.Sprintf("%T is not comparable", x.Type()))
|
|
}
|
|
}
|
|
|
|
// sortKey sorts a list of map keys, deduplicating keys if necessary.
|
|
func sortKeys(vs []reflect.Value) []reflect.Value {
|
|
if len(vs) == 0 {
|
|
return vs
|
|
}
|
|
|
|
// Sort the map keys.
|
|
sort.Sort(valueSorter(vs))
|
|
|
|
// Deduplicate keys (fails for NaNs).
|
|
vs2 := vs[:1]
|
|
for _, v := range vs[1:] {
|
|
if v.Interface() != vs2[len(vs2)-1].Interface() {
|
|
vs2 = append(vs2, v)
|
|
}
|
|
}
|
|
return vs2
|
|
}
|
|
|
|
// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above.
|
|
type valueSorter []reflect.Value
|
|
|
|
func (vs valueSorter) Len() int { return len(vs) }
|
|
func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) }
|
|
func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|