aboutsummaryrefslogtreecommitdiff
path: root/cmp
diff options
context:
space:
mode:
authorJoe Tsai <joetsai@digital-static.net>2017-07-20 15:59:54 -0700
committerGitHub <noreply@github.com>2017-07-20 15:59:54 -0700
commit18107e6c56edb2d51f965f7d68e59404f0daee54 (patch)
tree40c1cf2b0a678b8cc4ab021370f080fc5315ceb2 /cmp
parentf9054c6a605e1bb9318d701929105b0fd8a7cac8 (diff)
downloadgo-cmp-18107e6c56edb2d51f965f7d68e59404f0daee54.tar.gz
Move general reflect logic to internal/value (#15)
This internal package has the API: Format(reflect.Value, useStringer bool) string SortKeys(vs []reflect.Value) []reflect.Value While moving Format and SortKeys, we also tweak the formatting logic to print the type on named primitive types.
Diffstat (limited to 'cmp')
-rw-r--r--cmp/compare.go3
-rw-r--r--cmp/compare_test.go14
-rw-r--r--cmp/internal/value/format.go258
-rw-r--r--cmp/internal/value/format_test.go91
-rw-r--r--cmp/internal/value/sort.go111
-rw-r--r--cmp/internal/value/sort_test.go (renamed from cmp/reporter_test.go)109
-rw-r--r--cmp/reporter.go339
7 files changed, 492 insertions, 433 deletions
diff --git a/cmp/compare.go b/cmp/compare.go
index 2294579..4fff3fd 100644
--- a/cmp/compare.go
+++ b/cmp/compare.go
@@ -31,6 +31,7 @@ import (
"reflect"
"github.com/google/go-cmp/cmp/internal/diff"
+ "github.com/google/go-cmp/cmp/internal/value"
)
// BUG: Maps with keys containing NaN values cannot be properly compared due to
@@ -442,7 +443,7 @@ func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) {
step := &mapIndex{pathStep: pathStep{t.Elem()}}
s.curPath.push(step)
defer s.curPath.pop()
- for _, k := range sortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
+ for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
step.key = k
vvx := vx.MapIndex(k)
vvy := vy.MapIndex(k)
diff --git a/cmp/compare_test.go b/cmp/compare_test.go
index 11c30c2..d7d80d5 100644
--- a/cmp/compare_test.go
+++ b/cmp/compare_test.go
@@ -1438,8 +1438,8 @@ func project1Tests() []test {
-: "southbay2"
+: "southbay"
*{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.State:
- -: 6
- +: 5
+ -: testprotos.Goat_States(6)
+ +: testprotos.Goat_States(5)
{teststructs.Eagle}.Slaps[0].Immutable.MildSlap:
-: false
+: true
@@ -1652,8 +1652,8 @@ func project3Tests() []test {
-: &teststructs.MockTable{state: []string{"a", "c"}}
+: &teststructs.MockTable{state: []string{"a", "b", "c"}}
{teststructs.Dirt}.Discord:
- -: 554
- +: 500
+ -: teststructs.DiscordState(554)
+ +: teststructs.DiscordState(500)
λ({teststructs.Dirt}.Proto):
-: "blah"
+: "proto"
@@ -1751,10 +1751,10 @@ func project4Tests() []test {
-: 0x04
+: 0x03
{teststructs.Cartel}.poisons[0].poisonType:
- -: 1
- +: 5
+ -: testprotos.PoisonType(1)
+ +: testprotos.PoisonType(5)
{teststructs.Cartel}.poisons[1->?]:
- -: &teststructs.Poison{poisonType: 2, manufactuer: "acme2"}
+ -: &teststructs.Poison{poisonType: testprotos.PoisonType(2), manufactuer: "acme2"}
+: <non-existent>`,
}}
}
diff --git a/cmp/internal/value/format.go b/cmp/internal/value/format.go
new file mode 100644
index 0000000..e26395d
--- /dev/null
+++ b/cmp/internal/value/format.go
@@ -0,0 +1,258 @@
+// 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 value
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+// formatFakePointers controls whether to substitute pointer addresses with nil.
+// This is used for deterministic testing.
+var formatFakePointers = false
+
+var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+
+// Format formats the value v as a string.
+//
+// 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 Format(v reflect.Value, useStringer bool) string {
+ return formatAny(v, formatConfig{useStringer, true, true, !formatFakePointers}, 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?
+}
+
+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 formatPrimitive(v.Type(), v.Bool(), conf)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return formatPrimitive(v.Type(), v.Int(), conf)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
+ // Unnamed uints are usually bytes or words, so use hexadecimal.
+ return formatPrimitive(v.Type(), formatHex(v.Uint()), conf)
+ }
+ return formatPrimitive(v.Type(), v.Uint(), conf)
+ case reflect.Float32, reflect.Float64:
+ return formatPrimitive(v.Type(), v.Float(), conf)
+ case reflect.Complex64, reflect.Complex128:
+ return formatPrimitive(v.Type(), v.Complex(), conf)
+ case reflect.String:
+ return formatPrimitive(v.Type(), fmt.Sprintf("%q", v), conf)
+ 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 formatPrimitive(t reflect.Type, v interface{}, conf formatConfig) string {
+ if conf.printType && t.PkgPath() != "" {
+ return fmt.Sprintf("%v(%v)", t, v)
+ }
+ return fmt.Sprintf("%v", v)
+}
+
+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
+}
+
+// isExported reports whether the identifier is exported.
+func isExported(id string) bool {
+ r, _ := utf8.DecodeRuneInString(id)
+ return unicode.IsUpper(r)
+}
diff --git a/cmp/internal/value/format_test.go b/cmp/internal/value/format_test.go
new file mode 100644
index 0000000..c44884f
--- /dev/null
+++ b/cmp/internal/value/format_test.go
@@ -0,0 +1,91 @@
+// 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 value
+
+import (
+ "bytes"
+ "io"
+ "reflect"
+ "testing"
+)
+
+func TestFormat(t *testing.T) {
+ type key struct {
+ a int
+ b string
+ c chan bool
+ }
+
+ tests := []struct {
+ in interface{}
+ want string
+ }{{
+ in: []int{},
+ want: "[]int{}",
+ }, {
+ in: []int(nil),
+ want: "[]int(nil)",
+ }, {
+ in: []int{1, 2, 3, 4, 5},
+ want: "[]int{1, 2, 3, 4, 5}",
+ }, {
+ in: []interface{}{1, true, "hello", struct{ A, B int }{1, 2}},
+ want: "[]interface {}{1, true, \"hello\", struct { A int; B int }{A: 1, B: 2}}",
+ }, {
+ in: []struct{ A, B int }{{1, 2}, {0, 4}, {}},
+ want: "[]struct { A int; B int }{{A: 1, B: 2}, {B: 4}, {}}",
+ }, {
+ in: map[*int]string{new(int): "hello"},
+ want: "map[*int]string{0x00: \"hello\"}",
+ }, {
+ in: map[key]string{{}: "hello"},
+ want: "map[value.key]string{{}: \"hello\"}",
+ }, {
+ in: map[key]string{{a: 5, b: "key", c: make(chan bool)}: "hello"},
+ want: "map[value.key]string{{a: 5, b: \"key\", c: (chan bool)(0x00)}: \"hello\"}",
+ }, {
+ in: map[io.Reader]string{new(bytes.Reader): "hello"},
+ want: "map[io.Reader]string{0x00: \"hello\"}",
+ }, {
+ in: func() interface{} {
+ var a = []interface{}{nil}
+ a[0] = a
+ return a
+ }(),
+ want: "[]interface {}{([]interface {})(0x00)}",
+ }, {
+ in: func() interface{} {
+ type A *A
+ var a A
+ a = &a
+ return a
+ }(),
+ want: "&(value.A)(0x00)",
+ }, {
+ in: func() interface{} {
+ type A map[*A]A
+ a := make(A)
+ a[&a] = a
+ return a
+ }(),
+ want: "value.A{0x00: 0x00}",
+ }, {
+ in: func() interface{} {
+ var a [2]interface{}
+ a[0] = &a
+ return a
+ }(),
+ want: "[2]interface {}{&[2]interface {}{(*[2]interface {})(0x00), interface {}(nil)}, interface {}(nil)}",
+ }}
+
+ formatFakePointers = true
+ defer func() { formatFakePointers = false }()
+ for i, tt := range tests {
+ got := Format(reflect.ValueOf(tt.in), true)
+ if got != tt.want {
+ t.Errorf("test %d, pretty print:\ngot %q\nwant %q", i, got, tt.want)
+ }
+ }
+}
diff --git a/cmp/internal/value/sort.go b/cmp/internal/value/sort.go
new file mode 100644
index 0000000..da00896
--- /dev/null
+++ b/cmp/internal/value/sort.go
@@ -0,0 +1,111 @@
+// 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 value
+
+import (
+ "fmt"
+ "math"
+ "reflect"
+ "sort"
+)
+
+// SortKey sorts a list of map keys, deduplicating keys if necessary.
+// The type of each value must be comparable.
+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] }
+
+// 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()))
+ }
+}
diff --git a/cmp/reporter_test.go b/cmp/internal/value/sort_test.go
index 7310077..d3f254b 100644
--- a/cmp/reporter_test.go
+++ b/cmp/internal/value/sort_test.go
@@ -2,92 +2,16 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
-package cmp
+package value_test
import (
- "bytes"
- "io"
"math"
"reflect"
"testing"
-)
-
-func TestFormatAny(t *testing.T) {
- type key struct {
- a int
- b string
- c chan bool
- }
-
- tests := []struct {
- in interface{}
- want string
- }{{
- in: []int{},
- want: "[]int{}",
- }, {
- in: []int(nil),
- want: "[]int(nil)",
- }, {
- in: []int{1, 2, 3, 4, 5},
- want: "[]int{1, 2, 3, 4, 5}",
- }, {
- in: []interface{}{1, true, "hello", struct{ A, B int }{1, 2}},
- want: "[]interface {}{1, true, \"hello\", struct { A int; B int }{A: 1, B: 2}}",
- }, {
- in: []struct{ A, B int }{{1, 2}, {0, 4}, {}},
- want: "[]struct { A int; B int }{{A: 1, B: 2}, {B: 4}, {}}",
- }, {
- in: map[*int]string{new(int): "hello"},
- want: "map[*int]string{0x00: \"hello\"}",
- }, {
- in: map[key]string{{}: "hello"},
- want: "map[cmp.key]string{{}: \"hello\"}",
- }, {
- in: map[key]string{{a: 5, b: "key", c: make(chan bool)}: "hello"},
- want: "map[cmp.key]string{{a: 5, b: \"key\", c: (chan bool)(0x00)}: \"hello\"}",
- }, {
- in: map[io.Reader]string{new(bytes.Reader): "hello"},
- want: "map[io.Reader]string{0x00: \"hello\"}",
- }, {
- in: func() interface{} {
- var a = []interface{}{nil}
- a[0] = a
- return a
- }(),
- want: "[]interface {}{([]interface {})(0x00)}",
- }, {
- in: func() interface{} {
- type A *A
- var a A
- a = &a
- return a
- }(),
- want: "&(cmp.A)(0x00)",
- }, {
- in: func() interface{} {
- type A map[*A]A
- a := make(A)
- a[&a] = a
- return a
- }(),
- want: "cmp.A{0x00: 0x00}",
- }, {
- in: func() interface{} {
- var a [2]interface{}
- a[0] = &a
- return a
- }(),
- want: "[2]interface {}{&[2]interface {}{(*[2]interface {})(0x00), interface {}(nil)}, interface {}(nil)}",
- }}
- for i, tt := range tests {
- got := formatAny(reflect.ValueOf(tt.in), formatConfig{true, true, true, false}, nil)
- if got != tt.want {
- t.Errorf("test %d, pretty print:\ngot %q\nwant %q", i, got, tt.want)
- }
- }
-}
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/internal/value"
+)
func TestSortKeys(t *testing.T) {
type (
@@ -101,14 +25,14 @@ func TestSortKeys(t *testing.T) {
EmptyStruct struct{}
)
- opts := []Option{
- Comparer(func(x, y float64) bool {
+ opts := []cmp.Option{
+ cmp.Comparer(func(x, y float64) bool {
if math.IsNaN(x) && math.IsNaN(y) {
return true
}
return x == y
}),
- Comparer(func(x, y complex128) bool {
+ cmp.Comparer(func(x, y complex128) bool {
rx, ix, ry, iy := real(x), imag(x), real(y), imag(y)
if math.IsNaN(rx) && math.IsNaN(ry) {
rx, ry = 0, 0
@@ -118,11 +42,11 @@ func TestSortKeys(t *testing.T) {
}
return rx == ry && ix == iy
}),
- Comparer(func(x, y chan bool) bool { return true }),
- Comparer(func(x, y chan int) bool { return true }),
- Comparer(func(x, y chan float64) bool { return true }),
- Comparer(func(x, y chan interface{}) bool { return true }),
- Comparer(func(x, y *int) bool { return true }),
+ cmp.Comparer(func(x, y chan bool) bool { return true }),
+ cmp.Comparer(func(x, y chan int) bool { return true }),
+ cmp.Comparer(func(x, y chan float64) bool { return true }),
+ cmp.Comparer(func(x, y chan interface{}) bool { return true }),
+ cmp.Comparer(func(x, y *int) bool { return true }),
}
tests := []struct {
@@ -188,7 +112,8 @@ func TestSortKeys(t *testing.T) {
[2]int{2, 3}, [2]int{2, 4}, [2]int{4, 0}, MyArray([2]int{2, 4}),
make(chan bool), make(chan bool), make(chan int), make(chan interface{}),
new(int), new(int),
- MyString("abc"), MyString("abcd"), MyString("abcde"), "abc", "abcd", "abcde", "bar", "foo",
+ "abc", "abcd", "abcde", "bar", "foo",
+ MyString("abc"), MyString("abcd"), MyString("abcde"),
EmptyStruct{},
MyStruct{"alpha", [2]int{3, 3}, make(chan float64)},
MyStruct{"bravo", [2]int{2, 3}, make(chan float64)},
@@ -217,11 +142,11 @@ func TestSortKeys(t *testing.T) {
for i, tt := range tests {
keys := append(reflect.ValueOf(tt.in).MapKeys(), reflect.ValueOf(tt.in).MapKeys()...)
var got []interface{}
- for _, k := range sortKeys(keys) {
+ for _, k := range value.SortKeys(keys) {
got = append(got, k.Interface())
}
- if !Equal(got, tt.want, opts...) {
- t.Errorf("test %d, output mismatch:\ngot %#v\nwant %#v", i, got, tt.want)
+ if d := cmp.Diff(tt.want, got, opts...); d != "" {
+ t.Errorf("test %d, output mismatch (-want +got):\n%s", i, d)
}
}
}
diff --git a/cmp/reporter.go b/cmp/reporter.go
index 3f3454b..faebc05 100644
--- a/cmp/reporter.go
+++ b/cmp/reporter.go
@@ -6,14 +6,11 @@ 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?
+ "github.com/google/go-cmp/cmp/internal/value"
+)
type defaultReporter struct {
Option
@@ -34,12 +31,12 @@ func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) {
const maxLines = 256
r.ndiffs++
if r.nbytes < maxBytes && r.nlines < maxLines {
- sx := prettyPrint(x, true)
- sy := prettyPrint(y, true)
+ sx := value.Format(x, true)
+ sy := value.Format(y, true)
if sx == sy {
// Stringer is not helpful, so rely on more exact formatting.
- sx = prettyPrint(x, false)
- sy = prettyPrint(y, false)
+ sx = value.Format(x, false)
+ sy = value.Format(y, false)
}
s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
r.diffs = append(r.diffs, s)
@@ -55,327 +52,3 @@ func (r *defaultReporter) String() string {
}
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] }