Selaa lähdekoodia

Introduce errors.Stacktrace (#48)

* Introduce errors.Stacktrace

Introduce a new type to replace the unnamed return value from
`Stacktrace()`. `Stacktrace` implements `fmt.Formatter` and currently
replicates the default behaviour of printing a `[]Frame`.

In a future PR the values of `%+v` and `%#v` will be extended to
implement a more readable stacktrace.
Dave Cheney 9 vuotta sitten
vanhempi
commit
a5b220e0c6
3 muutettua tiedostoa jossa 114 lisäystä ja 28 poistoa
  1. 1 1
      example_test.go
  2. 20 1
      stack.go
  3. 93 26
      stack_test.go

+ 1 - 1
example_test.go

@@ -71,7 +71,7 @@ func ExampleErrorf() {
 
 func Example_stacktrace() {
 	type Stacktrace interface {
-		Stacktrace() []errors.Frame
+		Stacktrace() errors.Stacktrace
 	}
 
 	err, ok := errors.Cause(fn()).(Stacktrace)

+ 20 - 1
stack.go

@@ -76,10 +76,29 @@ func (f Frame) Format(s fmt.State, verb rune) {
 	}
 }
 
+// Stacktrace is stack of Frames from innermost (newest) to outermost (oldest).
+type Stacktrace []Frame
+
+func (st Stacktrace) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		switch {
+		case s.Flag('+'):
+			fmt.Fprintf(s, "%+v", []Frame(st))
+		case s.Flag('#'):
+			fmt.Fprintf(s, "%#v", []Frame(st))
+		default:
+			fmt.Fprintf(s, "%v", []Frame(st))
+		}
+	case 's':
+		fmt.Fprintf(s, "%s", []Frame(st))
+	}
+}
+
 // stack represents a stack of program counters.
 type stack []uintptr
 
-func (s *stack) Stacktrace() []Frame {
+func (s *stack) Stacktrace() Stacktrace {
 	f := make([]Frame, len(*s))
 	for i := 0; i < len(f); i++ {
 		f[i] = Frame((*s)[i])

+ 93 - 26
stack_test.go

@@ -170,56 +170,123 @@ func TestTrimGOPATH(t *testing.T) {
 }
 
 func TestStacktrace(t *testing.T) {
-	type fileline struct {
-		file string
-		line int
-	}
 	tests := []struct {
 		err  error
-		want []fileline
+		want []string
 	}{{
-		New("ooh"), []fileline{
-			{"github.com/pkg/errors/stack_test.go", 181},
+		New("ooh"), []string{
+			"github.com/pkg/errors/stack_test.go:177",
 		},
 	}, {
-		Wrap(New("ooh"), "ahh"), []fileline{
-			{"github.com/pkg/errors/stack_test.go", 185}, // this is the stack of Wrap, not New
+		Wrap(New("ooh"), "ahh"), []string{
+			"github.com/pkg/errors/stack_test.go:181", // this is the stack of Wrap, not New
 		},
 	}, {
-		Cause(Wrap(New("ooh"), "ahh")), []fileline{
-			{"github.com/pkg/errors/stack_test.go", 189}, // this is the stack of New
+		Cause(Wrap(New("ooh"), "ahh")), []string{
+			"github.com/pkg/errors/stack_test.go:185", // this is the stack of New
 		},
 	}, {
-		func() error { return New("ooh") }(), []fileline{
-			{"github.com/pkg/errors/stack_test.go", 193}, // this is the stack of New
-			{"github.com/pkg/errors/stack_test.go", 193}, // this is the stack of New's caller
+		func() error { return New("ooh") }(), []string{
+			"github.com/pkg/errors/stack_test.go:189", // this is the stack of New
+			"github.com/pkg/errors/stack_test.go:189", // this is the stack of New's caller
 		},
 	}, {
 		Cause(func() error {
 			return func() error {
 				return Errorf("hello %s", fmt.Sprintf("world"))
 			}()
-		}()), []fileline{
-			{"github.com/pkg/errors/stack_test.go", 200}, // this is the stack of Errorf
-			{"github.com/pkg/errors/stack_test.go", 201}, // this is the stack of Errorf's caller
-			{"github.com/pkg/errors/stack_test.go", 202}, // this is the stack of Errorf's caller's caller
+		}()), []string{
+			"github.com/pkg/errors/stack_test.go:196", // this is the stack of Errorf
+			"github.com/pkg/errors/stack_test.go:197", // this is the stack of Errorf's caller
+			"github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller
 		},
 	}}
-	for _, tt := range tests {
+	for i, tt := range tests {
 		x, ok := tt.err.(interface {
-			Stacktrace() []Frame
+			Stacktrace() Stacktrace
 		})
 		if !ok {
-			t.Errorf("expected %#v to implement Stacktrace() []Frame", tt.err)
+			t.Errorf("expected %#v to implement Stacktrace() Stacktrace", tt.err)
 			continue
 		}
 		st := x.Stacktrace()
-		for i, want := range tt.want {
-			frame := st[i]
-			file, line := fmt.Sprintf("%+s", frame), frame.line()
-			if file != want.file || line != want.line {
-				t.Errorf("frame %d: expected %s:%d, got %s:%d", i, want.file, want.line, file, line)
+		for j, want := range tt.want {
+			frame := st[j]
+			got := fmt.Sprintf("%+v", frame)
+			if got != want {
+				t.Errorf("test %d: frame %d: got %q, want %q", i, j, got, want)
 			}
 		}
 	}
 }
+
+func stacktrace() Stacktrace {
+	const depth = 8
+	var pcs [depth]uintptr
+	n := runtime.Callers(1, pcs[:])
+	var st stack = pcs[0:n]
+	return st.Stacktrace()
+}
+
+func TestStacktraceFormat(t *testing.T) {
+	tests := []struct {
+		Stacktrace
+		format string
+		want   string
+	}{{
+		nil,
+		"%s",
+		"[]",
+	}, {
+		nil,
+		"%v",
+		"[]",
+	}, {
+		nil,
+		"%+v",
+		"[]",
+	}, {
+		nil,
+		"%#v",
+		"[]errors.Frame(nil)",
+	}, {
+		make(Stacktrace, 0),
+		"%s",
+		"[]",
+	}, {
+		make(Stacktrace, 0),
+		"%v",
+		"[]",
+	}, {
+		make(Stacktrace, 0),
+		"%+v",
+		"[]",
+	}, {
+		make(Stacktrace, 0),
+		"%#v",
+		"[]errors.Frame{}",
+	}, {
+		stacktrace()[:2],
+		"%s",
+		"[stack_test.go stack_test.go]",
+	}, {
+		stacktrace()[:2],
+		"%v",
+		"[stack_test.go:226 stack_test.go:273]",
+	}, {
+		stacktrace()[:2],
+		"%+v",
+		"[github.com/pkg/errors/stack_test.go:226 github.com/pkg/errors/stack_test.go:277]",
+	}, {
+		stacktrace()[:2],
+		"%#v",
+		"[]errors.Frame{stack_test.go:226, stack_test.go:281}",
+	}}
+
+	for i, tt := range tests {
+		got := fmt.Sprintf(tt.format, tt.Stacktrace)
+		if got != tt.want {
+			t.Errorf("test %d: got: %q, want: %q", i+1, got, tt.want)
+		}
+	}
+}