diff unexported struct fields
Fixes #21.
Closes #33 (superseded).
diff --git a/diff.go b/diff.go
index 8fe8e24..fb9580b 100644
--- a/diff.go
+++ b/diff.go
@@ -40,11 +40,11 @@
func (w diffWriter) diff(av, bv reflect.Value) {
if !av.IsValid() && bv.IsValid() {
- w.printf("nil != %#v", bv.Interface())
+ w.printf("nil != %# v", formatter{v: bv, quote: true})
return
}
if av.IsValid() && !bv.IsValid() {
- w.printf("%#v != nil", av.Interface())
+ w.printf("%# v != nil", formatter{v: av, quote: true})
return
}
if !av.IsValid() && !bv.IsValid() {
@@ -58,34 +58,61 @@
return
}
- // numeric types, including bool
- if at.Kind() < reflect.Array {
- a, b := av.Interface(), bv.Interface()
- if a != b {
- w.printf("%#v != %#v", a, b)
+ switch kind := at.Kind(); kind {
+ case reflect.Bool:
+ if a, b := av.Bool(), bv.Bool(); a != b {
+ w.printf("%v != %v", a, b)
}
- return
- }
-
- switch at.Kind() {
- case reflect.String:
- a, b := av.Interface(), bv.Interface()
- if a != b {
- w.printf("%q != %q", a, b)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if a, b := av.Int(), bv.Int(); a != b {
+ w.printf("%d != %d", a, b)
+ }
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ if a, b := av.Uint(), bv.Uint(); a != b {
+ w.printf("%d != %d", a, b)
+ }
+ case reflect.Float32, reflect.Float64:
+ if a, b := av.Float(), bv.Float(); a != b {
+ w.printf("%v != %v", a, b)
+ }
+ case reflect.Complex64, reflect.Complex128:
+ if a, b := av.Complex(), bv.Complex(); a != b {
+ w.printf("%v != %v", a, b)
+ }
+ case reflect.Array:
+ n := av.Len()
+ for i := 0; i < n; i++ {
+ w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
+ }
+ case reflect.Chan, reflect.Func, reflect.UnsafePointer:
+ if a, b := av.Pointer(), bv.Pointer(); a != b {
+ w.printf("%#x != %#x", a, b)
+ }
+ case reflect.Interface:
+ w.diff(av.Elem(), bv.Elem())
+ case reflect.Map:
+ ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys())
+ for _, k := range ak {
+ w := w.relabel(fmt.Sprintf("[%#v]", k))
+ w.printf("%q != (missing)", av.MapIndex(k))
+ }
+ for _, k := range both {
+ w := w.relabel(fmt.Sprintf("[%#v]", k))
+ w.diff(av.MapIndex(k), bv.MapIndex(k))
+ }
+ for _, k := range bk {
+ w := w.relabel(fmt.Sprintf("[%#v]", k))
+ w.printf("(missing) != %q", bv.MapIndex(k))
}
case reflect.Ptr:
switch {
case av.IsNil() && !bv.IsNil():
- w.printf("nil != %v", bv.Interface())
+ w.printf("nil != %# v", formatter{v: bv, quote: true})
case !av.IsNil() && bv.IsNil():
- w.printf("%v != nil", av.Interface())
+ w.printf("%# v != nil", formatter{v: av, quote: true})
case !av.IsNil() && !bv.IsNil():
w.diff(av.Elem(), bv.Elem())
}
- case reflect.Struct:
- for i := 0; i < av.NumField(); i++ {
- w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i))
- }
case reflect.Slice:
lenA := av.Len()
lenB := bv.Len()
@@ -96,26 +123,16 @@
for i := 0; i < lenA; i++ {
w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
}
- case reflect.Map:
- ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys())
- for _, k := range ak {
- w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
- w.printf("%q != (missing)", av.MapIndex(k))
+ case reflect.String:
+ if a, b := av.String(), bv.String(); a != b {
+ w.printf("%q != %q", a, b)
}
- for _, k := range both {
- w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
- w.diff(av.MapIndex(k), bv.MapIndex(k))
+ case reflect.Struct:
+ for i := 0; i < av.NumField(); i++ {
+ w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i))
}
- for _, k := range bk {
- w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
- w.printf("(missing) != %q", bv.MapIndex(k))
- }
- case reflect.Interface:
- w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface()))
default:
- if !reflect.DeepEqual(av.Interface(), bv.Interface()) {
- w.printf("%# v != %# v", Formatter(av.Interface()), Formatter(bv.Interface()))
- }
+ panic("unknown reflect Kind: " + kind.String())
}
}
@@ -128,11 +145,26 @@
return d1
}
+// keyEqual compares a and b for equality.
+// Both a and b must be valid map keys.
+func keyEqual(a, b reflect.Value) bool {
+ if a.Type() != b.Type() {
+ return false
+ }
+ switch kind := a.Kind(); kind {
+ case reflect.Int:
+ a, b := a.Int(), b.Int()
+ return a == b
+ default:
+ panic("invalid map reflect Kind: " + kind.String())
+ }
+}
+
func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) {
for _, av := range a {
inBoth := false
for _, bv := range b {
- if reflect.DeepEqual(av.Interface(), bv.Interface()) {
+ if keyEqual(av, bv) {
inBoth = true
both = append(both, av)
break
@@ -145,7 +177,7 @@
for _, bv := range b {
inBoth := false
for _, av := range a {
- if reflect.DeepEqual(av.Interface(), bv.Interface()) {
+ if keyEqual(av, bv) {
inBoth = true
break
}
diff --git a/diff_test.go b/diff_test.go
index 3c388f1..782858c 100644
--- a/diff_test.go
+++ b/diff_test.go
@@ -1,7 +1,9 @@
package pretty
import (
+ "fmt"
"testing"
+ "unsafe"
)
type difftest struct {
@@ -17,6 +19,20 @@
C []int
}
+type (
+ N struct{ N int }
+ E interface{}
+)
+
+var (
+ c0 = make(chan int)
+ c1 = make(chan int)
+ f0 = func() {}
+ f1 = func() {}
+ i0 = 0
+ i1 = 1
+)
+
var diffs = []difftest{
{a: nil, b: nil},
{a: S{A: 1}, b: S{A: 1}},
@@ -28,12 +44,80 @@
{S{}, S{A: 1}, []string{`A: 0 != 1`}},
{new(S), &S{A: 1}, []string{`A: 0 != 1`}},
{S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}},
- {S{}, S{I: 0}, []string{`I: nil != 0`}},
+ {S{}, S{I: 0}, []string{`I: nil != int(0)`}},
{S{I: 1}, S{I: "x"}, []string{`I: int != string`}},
{S{}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}},
{S{C: []int{}}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}},
{S{C: []int{1, 2, 3}}, S{C: []int{1, 2, 4}}, []string{`C[2]: 3 != 4`}},
- {S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &{0 <nil> <nil> []}`}},
+ {S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &pretty.S{}`}},
+
+ // unexported fields of every reflect.Kind (both equal and unequal)
+ {struct{ x bool }{false}, struct{ x bool }{false}, nil},
+ {struct{ x bool }{false}, struct{ x bool }{true}, []string{`x: false != true`}},
+ {struct{ x int }{0}, struct{ x int }{0}, nil},
+ {struct{ x int }{0}, struct{ x int }{1}, []string{`x: 0 != 1`}},
+ {struct{ x int8 }{0}, struct{ x int8 }{0}, nil},
+ {struct{ x int8 }{0}, struct{ x int8 }{1}, []string{`x: 0 != 1`}},
+ {struct{ x int16 }{0}, struct{ x int16 }{0}, nil},
+ {struct{ x int16 }{0}, struct{ x int16 }{1}, []string{`x: 0 != 1`}},
+ {struct{ x int32 }{0}, struct{ x int32 }{0}, nil},
+ {struct{ x int32 }{0}, struct{ x int32 }{1}, []string{`x: 0 != 1`}},
+ {struct{ x int64 }{0}, struct{ x int64 }{0}, nil},
+ {struct{ x int64 }{0}, struct{ x int64 }{1}, []string{`x: 0 != 1`}},
+ {struct{ x uint }{0}, struct{ x uint }{0}, nil},
+ {struct{ x uint }{0}, struct{ x uint }{1}, []string{`x: 0 != 1`}},
+ {struct{ x uint8 }{0}, struct{ x uint8 }{0}, nil},
+ {struct{ x uint8 }{0}, struct{ x uint8 }{1}, []string{`x: 0 != 1`}},
+ {struct{ x uint16 }{0}, struct{ x uint16 }{0}, nil},
+ {struct{ x uint16 }{0}, struct{ x uint16 }{1}, []string{`x: 0 != 1`}},
+ {struct{ x uint32 }{0}, struct{ x uint32 }{0}, nil},
+ {struct{ x uint32 }{0}, struct{ x uint32 }{1}, []string{`x: 0 != 1`}},
+ {struct{ x uint64 }{0}, struct{ x uint64 }{0}, nil},
+ {struct{ x uint64 }{0}, struct{ x uint64 }{1}, []string{`x: 0 != 1`}},
+ {struct{ x uintptr }{0}, struct{ x uintptr }{0}, nil},
+ {struct{ x uintptr }{0}, struct{ x uintptr }{1}, []string{`x: 0 != 1`}},
+ {struct{ x float32 }{0}, struct{ x float32 }{0}, nil},
+ {struct{ x float32 }{0}, struct{ x float32 }{1}, []string{`x: 0 != 1`}},
+ {struct{ x float64 }{0}, struct{ x float64 }{0}, nil},
+ {struct{ x float64 }{0}, struct{ x float64 }{1}, []string{`x: 0 != 1`}},
+ {struct{ x complex64 }{0}, struct{ x complex64 }{0}, nil},
+ {struct{ x complex64 }{0}, struct{ x complex64 }{1}, []string{`x: (0+0i) != (1+0i)`}},
+ {struct{ x complex128 }{0}, struct{ x complex128 }{0}, nil},
+ {struct{ x complex128 }{0}, struct{ x complex128 }{1}, []string{`x: (0+0i) != (1+0i)`}},
+ {struct{ x [1]int }{[1]int{0}}, struct{ x [1]int }{[1]int{0}}, nil},
+ {struct{ x [1]int }{[1]int{0}}, struct{ x [1]int }{[1]int{1}}, []string{`x[0]: 0 != 1`}},
+ {struct{ x chan int }{c0}, struct{ x chan int }{c0}, nil},
+ {struct{ x chan int }{c0}, struct{ x chan int }{c1}, []string{fmt.Sprintf("x: %p != %p", c0, c1)}},
+ {struct{ x func() }{f0}, struct{ x func() }{f0}, nil},
+ {struct{ x func() }{f0}, struct{ x func() }{f1}, []string{fmt.Sprintf("x: %p != %p", f0, f1)}},
+ {struct{ x interface{} }{0}, struct{ x interface{} }{0}, nil},
+ {struct{ x interface{} }{0}, struct{ x interface{} }{1}, []string{`x: 0 != 1`}},
+ {struct{ x interface{} }{0}, struct{ x interface{} }{""}, []string{`x: int != string`}},
+ {struct{ x interface{} }{0}, struct{ x interface{} }{nil}, []string{`x: int(0) != nil`}},
+ {struct{ x interface{} }{nil}, struct{ x interface{} }{0}, []string{`x: nil != int(0)`}},
+ {struct{ x map[int]int }{map[int]int{0: 0}}, struct{ x map[int]int }{map[int]int{0: 0}}, nil},
+ {struct{ x map[int]int }{map[int]int{0: 0}}, struct{ x map[int]int }{map[int]int{0: 1}}, []string{`x[0]: 0 != 1`}},
+ {struct{ x *int }{new(int)}, struct{ x *int }{new(int)}, nil},
+ {struct{ x *int }{&i0}, struct{ x *int }{&i1}, []string{`x: 0 != 1`}},
+ {struct{ x *int }{nil}, struct{ x *int }{&i0}, []string{`x: nil != &int(0)`}},
+ {struct{ x *int }{&i0}, struct{ x *int }{nil}, []string{`x: &int(0) != nil`}},
+ {struct{ x []int }{[]int{0}}, struct{ x []int }{[]int{0}}, nil},
+ {struct{ x []int }{[]int{0}}, struct{ x []int }{[]int{1}}, []string{`x[0]: 0 != 1`}},
+ {struct{ x string }{"a"}, struct{ x string }{"a"}, nil},
+ {struct{ x string }{"a"}, struct{ x string }{"b"}, []string{`x: "a" != "b"`}},
+ {struct{ x N }{N{0}}, struct{ x N }{N{0}}, nil},
+ {struct{ x N }{N{0}}, struct{ x N }{N{1}}, []string{`x.N: 0 != 1`}},
+
+ {
+ struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(0))},
+ struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(0))},
+ nil,
+ },
+ {
+ struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(0))},
+ struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(1))},
+ []string{`x: 0x0 != 0x1`},
+ },
}
func TestDiff(t *testing.T) {
diff --git a/formatter.go b/formatter.go
index 8dacda2..dff7cb3 100644
--- a/formatter.go
+++ b/formatter.go
@@ -15,7 +15,7 @@
)
type formatter struct {
- x interface{}
+ v reflect.Value
force bool
quote bool
}
@@ -30,11 +30,11 @@
// format x according to the usual rules of package fmt.
// In particular, if x satisfies fmt.Formatter, then x.Format will be called.
func Formatter(x interface{}) (f fmt.Formatter) {
- return formatter{x: x, quote: true}
+ return formatter{v: reflect.ValueOf(x), quote: true}
}
func (fo formatter) String() string {
- return fmt.Sprint(fo.x) // unwrap it
+ return fmt.Sprint(fo.v) // unwrap it
}
func (fo formatter) passThrough(f fmt.State, c rune) {
@@ -51,14 +51,14 @@
s += fmt.Sprintf(".%d", p)
}
s += string(c)
- fmt.Fprintf(f, s, fo.x)
+ fmt.Fprintf(f, s, fo.v)
}
func (fo formatter) Format(f fmt.State, c rune) {
if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') {
w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0)
p := &printer{tw: w, Writer: w, visited: make(map[visit]int)}
- p.printValue(reflect.ValueOf(fo.x), true, fo.quote)
+ p.printValue(fo.v, true, fo.quote)
w.Flush()
return
}
diff --git a/pretty.go b/pretty.go
index b91020a..49423ec 100644
--- a/pretty.go
+++ b/pretty.go
@@ -11,6 +11,7 @@
"fmt"
"io"
"log"
+ "reflect"
)
// Errorf is a convenience wrapper for fmt.Errorf.
@@ -101,7 +102,7 @@
func wrap(a []interface{}, force bool) []interface{} {
w := make([]interface{}, len(a))
for i, x := range a {
- w[i] = formatter{x: x, force: force}
+ w[i] = formatter{v: reflect.ValueOf(x), force: force}
}
return w
}