Merge pull request #31 from cespare/sprint
Add Sprint
diff --git a/License b/License
index 480a328..05c783c 100644
--- a/License
+++ b/License
@@ -1,3 +1,5 @@
+The MIT License (MIT)
+
Copyright 2012 Keith Rarick
Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/diff.go b/diff.go
index 64fac64..8fe8e24 100644
--- a/diff.go
+++ b/diff.go
@@ -86,6 +86,16 @@
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()
+ if lenA != lenB {
+ w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB)
+ break
+ }
+ 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 {
diff --git a/diff_test.go b/diff_test.go
index 02d1953..3c388f1 100644
--- a/diff_test.go
+++ b/diff_test.go
@@ -30,8 +30,9 @@
{S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}},
{S{}, S{I: 0}, []string{`I: nil != 0`}},
{S{I: 1}, S{I: "x"}, []string{`I: int != string`}},
- {S{}, S{C: []int{1}}, []string{`C: []int(nil) != []int{1}`}},
- {S{C: []int{}}, S{C: []int{1}}, []string{`C: []int{} != []int{1}`}},
+ {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> []}`}},
}
diff --git a/formatter.go b/formatter.go
index 1161de7..8dacda2 100644
--- a/formatter.go
+++ b/formatter.go
@@ -2,11 +2,12 @@
import (
"fmt"
- "github.com/kr/text"
"io"
"reflect"
"strconv"
"text/tabwriter"
+
+ "github.com/kr/text"
)
const (
@@ -56,7 +57,7 @@
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}
+ p := &printer{tw: w, Writer: w, visited: make(map[visit]int)}
p.printValue(reflect.ValueOf(fo.x), true, fo.quote)
w.Flush()
return
@@ -66,7 +67,9 @@
type printer struct {
io.Writer
- tw *tabwriter.Writer
+ tw *tabwriter.Writer
+ visited map[visit]int
+ depth int
}
func (p *printer) indent() *printer {
@@ -85,7 +88,19 @@
}
}
+// printValue must keep track of already-printed pointer values to avoid
+// infinite recursion.
+type visit struct {
+ v uintptr
+ typ reflect.Type
+}
+
func (p *printer) printValue(v reflect.Value, showType, quote bool) {
+ if p.depth > 10 {
+ io.WriteString(p, "!%v(DEPTH EXCEEDED)")
+ return
+ }
+
switch v.Kind() {
case reflect.Bool:
p.printInline(v, v.Bool(), showType)
@@ -137,6 +152,16 @@
writeByte(p, '}')
case reflect.Struct:
t := v.Type()
+ if v.CanAddr() {
+ addr := v.UnsafeAddr()
+ vis := visit{addr, t}
+ if vd, ok := p.visited[vis]; ok && vd < p.depth {
+ p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false)
+ break // don't print v again
+ }
+ p.visited[vis] = p.depth
+ }
+
if showType {
io.WriteString(p, t.String())
}
@@ -156,7 +181,7 @@
if expand {
writeByte(pp, '\t')
}
- showTypeInStruct = f.Type.Kind() == reflect.Interface
+ showTypeInStruct = labelType(f.Type)
}
pp.printValue(getField(v, i), showTypeInStruct, true)
if expand {
@@ -175,7 +200,9 @@
case e.Kind() == reflect.Invalid:
io.WriteString(p, "nil")
case e.IsValid():
- p.printValue(e, showType, true)
+ pp := *p
+ pp.depth++
+ pp.printValue(e, showType, true)
default:
io.WriteString(p, v.Type().String())
io.WriteString(p, "(nil)")
@@ -220,8 +247,10 @@
io.WriteString(p, v.Type().String())
io.WriteString(p, ")(nil)")
} else {
- writeByte(p, '&')
- p.printValue(e, true, true)
+ pp := *p
+ pp.depth++
+ writeByte(pp, '&')
+ pp.printValue(e, true, true)
}
case reflect.Chan:
x := v.Pointer()
@@ -275,6 +304,14 @@
return false
}
+func labelType(t reflect.Type) bool {
+ switch t.Kind() {
+ case reflect.Interface, reflect.Struct:
+ return true
+ }
+ return false
+}
+
func (p *printer) fmtString(s string, quote bool) {
if quote {
s = strconv.Quote(s)
diff --git a/formatter_test.go b/formatter_test.go
index 303f033..5f3204e 100644
--- a/formatter_test.go
+++ b/formatter_test.go
@@ -3,6 +3,7 @@
import (
"fmt"
"io"
+ "strings"
"testing"
"unsafe"
)
@@ -19,6 +20,7 @@
type SA struct {
t *T
+ v T
}
type T struct {
@@ -43,7 +45,7 @@
{[0]int{}, "[0]int{}"},
{complex(1, 0), "(1+0i)"},
//{make(chan int), "(chan int)(0x1234)"},
- {unsafe.Pointer(uintptr(1)), "unsafe.Pointer(0x1)"},
+ {unsafe.Pointer(uintptr(unsafe.Pointer(&long))), fmt.Sprintf("unsafe.Pointer(0x%02x)", uintptr(unsafe.Pointer(&long)))},
{func(int) {}, "func(int) {...}"},
{map[int]int{1: 1}, "map[int]int{1:1}"},
{int32(1), "int32(1)"},
@@ -55,19 +57,20 @@
},
{F(5), "pretty.F(5)"},
{
- SA{&T{1, 2}},
+ SA{&T{1, 2}, T{3, 4}},
`pretty.SA{
t: &pretty.T{x:1, y:2},
+ v: pretty.T{x:3, y:4},
}`,
},
{
- map[int][]byte{1: []byte{}},
+ map[int][]byte{1: {}},
`map[int][]uint8{
1: {},
}`,
},
{
- map[int]T{1: T{}},
+ map[int]T{1: {}},
`map[int]pretty.T{
1: {},
}`,
@@ -144,3 +147,115 @@
}
}
}
+
+type I struct {
+ i int
+ R interface{}
+}
+
+func (i *I) I() *I { return i.R.(*I) }
+
+func TestCycle(t *testing.T) {
+ type A struct{ *A }
+ v := &A{}
+ v.A = v
+
+ // panics from stack overflow without cycle detection
+ t.Logf("Example cycle:\n%# v", Formatter(v))
+
+ p := &A{}
+ s := fmt.Sprintf("%# v", Formatter([]*A{p, p}))
+ if strings.Contains(s, "CYCLIC") {
+ t.Errorf("Repeated address detected as cyclic reference:\n%s", s)
+ }
+
+ type R struct {
+ i int
+ *R
+ }
+ r := &R{
+ i: 1,
+ R: &R{
+ i: 2,
+ R: &R{
+ i: 3,
+ },
+ },
+ }
+ r.R.R.R = r
+ t.Logf("Example longer cycle:\n%# v", Formatter(r))
+
+ r = &R{
+ i: 1,
+ R: &R{
+ i: 2,
+ R: &R{
+ i: 3,
+ R: &R{
+ i: 4,
+ R: &R{
+ i: 5,
+ R: &R{
+ i: 6,
+ R: &R{
+ i: 7,
+ R: &R{
+ i: 8,
+ R: &R{
+ i: 9,
+ R: &R{
+ i: 10,
+ R: &R{
+ i: 11,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ // here be pirates
+ r.R.R.R.R.R.R.R.R.R.R.R = r
+ t.Logf("Example very long cycle:\n%# v", Formatter(r))
+
+ i := &I{
+ i: 1,
+ R: &I{
+ i: 2,
+ R: &I{
+ i: 3,
+ R: &I{
+ i: 4,
+ R: &I{
+ i: 5,
+ R: &I{
+ i: 6,
+ R: &I{
+ i: 7,
+ R: &I{
+ i: 8,
+ R: &I{
+ i: 9,
+ R: &I{
+ i: 10,
+ R: &I{
+ i: 11,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ iv := i.I().I().I().I().I().I().I().I().I().I()
+ *iv = *i
+ t.Logf("Example long interface cycle:\n%# v", Formatter(i))
+}