From b19c9bac1a282539fd34c692f9307cbf5777e57f Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Fri, 25 Mar 2016 11:35:57 -0700 Subject: [PATCH 1/9] Add Sprint Fixes #30. --- pretty.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pretty.go b/pretty.go index d3df868..b91020a 100644 --- a/pretty.go +++ b/pretty.go @@ -81,6 +81,15 @@ func Println(a ...interface{}) (n int, errno error) { return fmt.Println(wrap(a, true)...) } +// Sprint is a convenience wrapper for fmt.Sprintf. +// +// Calling Sprint(x, y) is equivalent to +// fmt.Sprint(Formatter(x), Formatter(y)), but each operand is +// formatted with "%# v". +func Sprint(a ...interface{}) string { + return fmt.Sprint(wrap(a, true)...) +} + // Sprintf is a convenience wrapper for fmt.Sprintf. // // Calling Sprintf(f, x, y) is equivalent to From c49dea8a01a44a81bc4a764d6d986ebe21735bf9 Mon Sep 17 00:00:00 2001 From: Keith Rarick Date: Thu, 7 Jul 2016 14:17:11 -0700 Subject: [PATCH 2/9] diff unexported struct fields Fixes #21. Closes #33 (superseded). --- diff.go | 112 +++++++++++++++++++++++++++++++++------------------ diff_test.go | 88 +++++++++++++++++++++++++++++++++++++++- formatter.go | 10 ++--- pretty.go | 3 +- 4 files changed, 165 insertions(+), 48 deletions(-) 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) printf(f string, a ...interface{}) { 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 @@ func (w diffWriter) diff(av, bv reflect.Value) { 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 @@ func (w diffWriter) diff(av, bv reflect.Value) { 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)) - } - for _, k := range both { - w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) - w.diff(av.MapIndex(k), bv.MapIndex(k)) + case reflect.String: + if a, b := av.String(), bv.String(); a != b { + w.printf("%q != %q", a, b) } - for _, k := range bk { - w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) - w.printf("(missing) != %q", 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)) } - 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 @@ func (d diffWriter) relabel(name string) (d1 diffWriter) { 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 @@ func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) { 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 @@ type S struct { 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 @@ var diffs = []difftest{ {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 []}`}}, + {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 @@ const ( ) type formatter struct { - x interface{} + v reflect.Value force bool quote bool } @@ -30,11 +30,11 @@ type formatter struct { // 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 @@ func (fo formatter) passThrough(f fmt.State, c rune) { 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 @@ import ( "fmt" "io" "log" + "reflect" ) // Errorf is a convenience wrapper for fmt.Errorf. @@ -101,7 +102,7 @@ func Sprintf(format string, a ...interface{}) string { 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 } From 8fb778d21b15745996aef862bf7523d7867b03c1 Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Thu, 7 Jul 2016 23:22:42 +0200 Subject: [PATCH 3/9] Remove unused code (#29) --- formatter.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/formatter.go b/formatter.go index dff7cb3..5efaa94 100644 --- a/formatter.go +++ b/formatter.go @@ -10,10 +10,6 @@ import ( "github.com/kr/text" ) -const ( - limit = 50 -) - type formatter struct { v reflect.Value force bool @@ -319,11 +315,6 @@ func (p *printer) fmtString(s string, quote bool) { io.WriteString(p, s) } -func tryDeepEqual(a, b interface{}) bool { - defer func() { recover() }() - return reflect.DeepEqual(a, b) -} - func writeByte(w io.Writer, b byte) { w.Write([]byte{b}) } From 5da52abd6af87d48ac539569348470a611795ed3 Mon Sep 17 00:00:00 2001 From: Keith Rarick Date: Thu, 7 Jul 2016 14:39:22 -0700 Subject: [PATCH 4/9] add new Printfer interface This is for convenience. The client can now call pretty.Pdiff(logger, a, b) rather than looping over the results of Diff. --- diff.go | 41 ++++++++++++++++++++++++++++++----------- diff_test.go | 10 ++++++++++ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/diff.go b/diff.go index fb9580b..910da56 100644 --- a/diff.go +++ b/diff.go @@ -8,37 +8,56 @@ import ( type sbuf []string -func (s *sbuf) Write(b []byte) (int, error) { - *s = append(*s, string(b)) - return len(b), nil +func (p *sbuf) Printf(format string, a ...interface{}) { + s := fmt.Sprintf(format, a...) + *p = append(*p, s) } // Diff returns a slice where each element describes // a difference between a and b. func Diff(a, b interface{}) (desc []string) { - Fdiff((*sbuf)(&desc), a, b) + Pdiff((*sbuf)(&desc), a, b) return desc } +// wprintfer calls Fprintf on w for each Printf call +// with a trailing newline. +type wprintfer struct{ w io.Writer } + +func (p *wprintfer) Printf(format string, a ...interface{}) { + fmt.Fprintf(p.w, format, a...) +} + // Fdiff writes to w a description of the differences between a and b. func Fdiff(w io.Writer, a, b interface{}) { - diffWriter{w: w}.diff(reflect.ValueOf(a), reflect.ValueOf(b)) + Pdiff(&wprintfer{w}, a, b) +} + +type Printfer interface { + Printf(format string, a ...interface{}) +} + +// Pdiff prints to p a description of the differences between a and b. +// It calls Printf once for each difference, with no trailing newline. +// The standard library log.Logger is a Printfer. +func Pdiff(p Printfer, a, b interface{}) { + diffPrinter{w: p}.diff(reflect.ValueOf(a), reflect.ValueOf(b)) } -type diffWriter struct { - w io.Writer +type diffPrinter struct { + w Printfer l string // label } -func (w diffWriter) printf(f string, a ...interface{}) { +func (w diffPrinter) printf(f string, a ...interface{}) { var l string if w.l != "" { l = w.l + ": " } - fmt.Fprintf(w.w, l+f, a...) + w.w.Printf(l+f, a...) } -func (w diffWriter) diff(av, bv reflect.Value) { +func (w diffPrinter) diff(av, bv reflect.Value) { if !av.IsValid() && bv.IsValid() { w.printf("nil != %# v", formatter{v: bv, quote: true}) return @@ -136,7 +155,7 @@ func (w diffWriter) diff(av, bv reflect.Value) { } } -func (d diffWriter) relabel(name string) (d1 diffWriter) { +func (d diffPrinter) relabel(name string) (d1 diffPrinter) { d1 = d if d.l != "" && name[0] != '[' { d1.l += "." diff --git a/diff_test.go b/diff_test.go index 782858c..64f7e51 100644 --- a/diff_test.go +++ b/diff_test.go @@ -1,6 +1,7 @@ package pretty import ( + "bytes" "fmt" "testing" "unsafe" @@ -138,6 +139,15 @@ func TestDiff(t *testing.T) { } } +func TestFdiff(t *testing.T) { + var buf bytes.Buffer + Fdiff(&buf, 0, 1) + want := "0 != 1" + if got := buf.String(); got != want { + t.Errorf("Fdiff(0, 1) = %q want %q", got, want) + } +} + func diffdiff(t *testing.T, got, exp []string) { minus(t, "unexpected:", got, exp) minus(t, "missing:", exp, got) From fcfa3241d86922a24b44c3cd93de0f846e678bd6 Mon Sep 17 00:00:00 2001 From: Keith Rarick Date: Thu, 7 Jul 2016 14:40:38 -0700 Subject: [PATCH 5/9] add trailing newline in Fdiff after each item Fixes #22. --- diff.go | 2 +- diff_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/diff.go b/diff.go index 910da56..6df2f86 100644 --- a/diff.go +++ b/diff.go @@ -25,7 +25,7 @@ func Diff(a, b interface{}) (desc []string) { type wprintfer struct{ w io.Writer } func (p *wprintfer) Printf(format string, a ...interface{}) { - fmt.Fprintf(p.w, format, a...) + fmt.Fprintf(p.w, format+"\n", a...) } // Fdiff writes to w a description of the differences between a and b. diff --git a/diff_test.go b/diff_test.go index 64f7e51..eab6c47 100644 --- a/diff_test.go +++ b/diff_test.go @@ -142,7 +142,7 @@ func TestDiff(t *testing.T) { func TestFdiff(t *testing.T) { var buf bytes.Buffer Fdiff(&buf, 0, 1) - want := "0 != 1" + want := "0 != 1\n" if got := buf.String(); got != want { t.Errorf("Fdiff(0, 1) = %q want %q", got, want) } From 7497514e37543f8a9dfdb86a97c2e8d175a0f8a0 Mon Sep 17 00:00:00 2001 From: Keith Rarick Date: Thu, 7 Jul 2016 14:47:21 -0700 Subject: [PATCH 6/9] add new Logfer interface This is for convenience. The client can now call pretty.Ldiff(t, a, b) in tests rather than looping over the results of Diff. --- diff.go | 19 +++++++++++++++++++ diff_test.go | 7 +++++++ 2 files changed, 26 insertions(+) diff --git a/diff.go b/diff.go index 6df2f86..b08522d 100644 --- a/diff.go +++ b/diff.go @@ -44,6 +44,25 @@ func Pdiff(p Printfer, a, b interface{}) { diffPrinter{w: p}.diff(reflect.ValueOf(a), reflect.ValueOf(b)) } +type Logfer interface { + Logf(format string, a ...interface{}) +} + +// logprintfer calls Fprintf on w for each Printf call +// with a trailing newline. +type logprintfer struct{ l Logfer } + +func (p *logprintfer) Printf(format string, a ...interface{}) { + p.l.Logf(format, a...) +} + +// Ldiff prints to l a description of the differences between a and b. +// It calls Logf once for each difference, with no trailing newline. +// The standard library testing.T and testing.B are Logfers. +func Ldiff(l Logfer, a, b interface{}) { + Pdiff(&logprintfer{l}, a, b) +} + type diffPrinter struct { w Printfer l string // label diff --git a/diff_test.go b/diff_test.go index eab6c47..c47c9f0 100644 --- a/diff_test.go +++ b/diff_test.go @@ -3,10 +3,17 @@ package pretty import ( "bytes" "fmt" + "log" "testing" "unsafe" ) +var ( + _ Logfer = (*testing.T)(nil) + _ Logfer = (*testing.B)(nil) + _ Printfer = (*log.Logger)(nil) +) + type difftest struct { a interface{} b interface{} From 737b74a46c4bf788349f72cb256fed10aea4d0ac Mon Sep 17 00:00:00 2001 From: Keith Rarick Date: Fri, 8 Jul 2016 14:57:48 -0700 Subject: [PATCH 7/9] diff all map key types --- diff.go | 49 +++++++++++++++++++++++++++++++++++++++++++------ diff_test.go | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/diff.go b/diff.go index b08522d..6aa7f74 100644 --- a/diff.go +++ b/diff.go @@ -185,16 +185,53 @@ func (d diffPrinter) relabel(name string) (d1 diffPrinter) { // 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() { +func keyEqual(av, bv reflect.Value) bool { + if !av.IsValid() && !bv.IsValid() { + return true + } + if !av.IsValid() || !bv.IsValid() || av.Type() != bv.Type() { return false } - switch kind := a.Kind(); kind { - case reflect.Int: - a, b := a.Int(), b.Int() + switch kind := av.Kind(); kind { + case reflect.Bool: + a, b := av.Bool(), bv.Bool() + return a == b + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + a, b := av.Int(), bv.Int() + return a == b + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + a, b := av.Uint(), bv.Uint() + return a == b + case reflect.Float32, reflect.Float64: + a, b := av.Float(), bv.Float() + return a == b + case reflect.Complex64, reflect.Complex128: + a, b := av.Complex(), bv.Complex() return a == b + case reflect.Array: + for i := 0; i < av.Len(); i++ { + if !keyEqual(av.Index(i), bv.Index(i)) { + return false + } + } + return true + case reflect.Chan, reflect.UnsafePointer, reflect.Ptr: + a, b := av.Pointer(), bv.Pointer() + return a == b + case reflect.Interface: + return keyEqual(av.Elem(), bv.Elem()) + case reflect.String: + a, b := av.String(), bv.String() + return a == b + case reflect.Struct: + for i := 0; i < av.NumField(); i++ { + if !keyEqual(av.Field(i), bv.Field(i)) { + return false + } + } + return true default: - panic("invalid map reflect Kind: " + kind.String()) + panic("invalid map key type " + av.Type().String()) } } diff --git a/diff_test.go b/diff_test.go index c47c9f0..a951e4b 100644 --- a/diff_test.go +++ b/diff_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "log" + "reflect" "testing" "unsafe" ) @@ -115,7 +116,6 @@ var diffs = []difftest{ {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))}, @@ -146,6 +146,44 @@ func TestDiff(t *testing.T) { } } +func TestKeyEqual(t *testing.T) { + var emptyInterfaceZero interface{} = 0 + + cases := []interface{}{ + new(bool), + new(int), + new(int8), + new(int16), + new(int32), + new(int64), + new(uint), + new(uint8), + new(uint16), + new(uint32), + new(uint64), + new(uintptr), + new(float32), + new(float64), + new(complex64), + new(complex128), + new([1]int), + new(chan int), + new(unsafe.Pointer), + new(interface{}), + &emptyInterfaceZero, + new(*int), + new(string), + new(struct{ int }), + } + + for _, test := range cases { + rv := reflect.ValueOf(test).Elem() + if !keyEqual(rv, rv) { + t.Errorf("keyEqual(%s, %s) = false want true", rv.Type(), rv.Type()) + } + } +} + func TestFdiff(t *testing.T) { var buf bytes.Buffer Fdiff(&buf, 0, 1) From cfb55aafdaf3ec08f0db22699ab822c50091b1c4 Mon Sep 17 00:00:00 2001 From: Gustavo Niemeyer Date: Tue, 23 Aug 2016 14:07:15 -0300 Subject: [PATCH 8/9] Fix passthrough handling (#37). (#38) --- formatter.go | 4 ++-- formatter_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/formatter.go b/formatter.go index 5efaa94..a317d7b 100644 --- a/formatter.go +++ b/formatter.go @@ -30,7 +30,7 @@ func Formatter(x interface{}) (f fmt.Formatter) { } func (fo formatter) String() string { - return fmt.Sprint(fo.v) // unwrap it + return fmt.Sprint(fo.v.Interface()) // unwrap it } func (fo formatter) passThrough(f fmt.State, c rune) { @@ -47,7 +47,7 @@ func (fo formatter) passThrough(f fmt.State, c rune) { s += fmt.Sprintf(".%d", p) } s += string(c) - fmt.Fprintf(f, s, fo.v) + fmt.Fprintf(f, s, fo.v.Interface()) } func (fo formatter) Format(f fmt.State, c rune) { diff --git a/formatter_test.go b/formatter_test.go index 5f3204e..c8c0b51 100644 --- a/formatter_test.go +++ b/formatter_test.go @@ -13,6 +13,11 @@ type test struct { s string } +type passtest struct { + v interface{} + f, s string +} + type LongStructTypeName struct { longFieldName interface{} otherLongFieldName interface{} @@ -33,8 +38,30 @@ func (f F) Format(s fmt.State, c rune) { fmt.Fprintf(s, "F(%d)", int(f)) } +type Stringer struct { i int } + +func (s *Stringer) String() string { return "foo" } + var long = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +var passthrough = []passtest{ + {1, "%d", "1"}, + {"a", "%s", "a"}, + {&Stringer{}, "%s", "foo"}, +} + +func TestPassthrough(t *testing.T) { + for _, tt := range passthrough { + s := fmt.Sprintf(tt.f, Formatter(tt.v)) + if tt.s != s { + t.Errorf("expected %q", tt.s) + t.Errorf("got %q", s) + t.Errorf("expraw\n%s", tt.s) + t.Errorf("gotraw\n%s", s) + } + } +} + var gosyntax = []test{ {nil, `nil`}, {"", `""`}, From 73f6ac0b30a98e433b289500d779f50c1a6f0712 Mon Sep 17 00:00:00 2001 From: Keith Rarick Date: Sun, 6 May 2018 01:33:45 -0700 Subject: [PATCH 9/9] add go.mod --- go.mod | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1e29533 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module "github.com/kr/pretty" + +require "github.com/kr/text" v0.1.0