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))
+}