Просмотр исходного кода

Extended stacktrace output (#49)

* Extended stacktrace output

* Make format tests less sensitive to sourcecode prefix

* use testFormatRegexp for all Sprintf tests

* replace output test with sample output

* work around the different fn.Name format in go 1.4

* fix windows tests when there is a drive letter in the source path
Dave Cheney 9 лет назад
Родитель
Сommit
7896481a53
5 измененных файлов с 194 добавлено и 66 удалено
  1. 3 1
      errors.go
  2. 75 9
      example_test.go
  3. 70 15
      format_test.go
  4. 4 2
      stack.go
  5. 42 39
      stack_test.go

+ 3 - 1
errors.go

@@ -87,7 +87,9 @@ func (e _error) Format(s fmt.State, verb rune) {
 	switch verb {
 	case 'v':
 		if s.Flag('+') {
-			fmt.Fprintf(s, "%+v: ", e.Stacktrace()[0])
+			io.WriteString(s, e.msg)
+			fmt.Fprintf(s, "%+v", e.Stacktrace())
+			return
 		}
 		fallthrough
 	case 's':

+ 75 - 9
example_test.go

@@ -17,7 +17,22 @@ func ExampleNew_printf() {
 	err := errors.New("whoops")
 	fmt.Printf("%+v", err)
 
-	// Output: github.com/pkg/errors/example_test.go:17: whoops
+	// Example output:
+	// whoops
+	// github.com/pkg/errors_test.ExampleNew_printf
+	//         /home/dfc/src/github.com/pkg/errors/example_test.go:17
+	// testing.runExample
+	//         /home/dfc/go/src/testing/example.go:114
+	// testing.RunExamples
+	//         /home/dfc/go/src/testing/example.go:38
+	// testing.(*M).Run
+	//         /home/dfc/go/src/testing/testing.go:744
+	// main.main
+	//         /github.com/pkg/errors/_test/_testmain.go:106
+	// runtime.main
+	//         /home/dfc/go/src/runtime/proc.go:183
+	// runtime.goexit
+	//         /home/dfc/go/src/runtime/asm_amd64.s:2059
 }
 
 func ExampleWrap() {
@@ -44,14 +59,34 @@ func ExampleCause() {
 	// error
 }
 
-func ExampleCause_printf() {
+func ExampleWrap_extended() {
 	err := fn()
 	fmt.Printf("%+v\n", err)
 
-	// Output: github.com/pkg/errors/example_test.go:32: error
-	// github.com/pkg/errors/example_test.go:33: inner
-	// github.com/pkg/errors/example_test.go:34: middle
-	// github.com/pkg/errors/example_test.go:35: outer
+	// Example output:
+	// error
+	// github.com/pkg/errors_test.fn
+	//         /home/dfc/src/github.com/pkg/errors/example_test.go:47
+	// github.com/pkg/errors_test.ExampleCause_printf
+	//         /home/dfc/src/github.com/pkg/errors/example_test.go:63
+	// testing.runExample
+	//         /home/dfc/go/src/testing/example.go:114
+	// testing.RunExamples
+	//         /home/dfc/go/src/testing/example.go:38
+	// testing.(*M).Run
+	//         /home/dfc/go/src/testing/testing.go:744
+	// main.main
+	//         /github.com/pkg/errors/_test/_testmain.go:104
+	// runtime.main
+	//         /home/dfc/go/src/runtime/proc.go:183
+	// runtime.goexit
+	//         /home/dfc/go/src/runtime/asm_amd64.s:2059
+	// github.com/pkg/errors_test.fn
+	// 	  /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner
+	// github.com/pkg/errors_test.fn
+	//        /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle
+	// github.com/pkg/errors_test.fn
+	//      /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer
 }
 
 func ExampleWrapf() {
@@ -62,11 +97,26 @@ func ExampleWrapf() {
 	// Output: oh noes #2: whoops
 }
 
-func ExampleErrorf() {
+func ExampleErrorf_extended() {
 	err := errors.Errorf("whoops: %s", "foo")
 	fmt.Printf("%+v", err)
 
-	// Output: github.com/pkg/errors/example_test.go:66: whoops: foo
+	// Example output:
+	// whoops: foo
+	// github.com/pkg/errors_test.ExampleErrorf
+	//         /home/dfc/src/github.com/pkg/errors/example_test.go:101
+	// testing.runExample
+	//         /home/dfc/go/src/testing/example.go:114
+	// testing.RunExamples
+	//         /home/dfc/go/src/testing/example.go:38
+	// testing.(*M).Run
+	//         /home/dfc/go/src/testing/testing.go:744
+	// main.main
+	//         /github.com/pkg/errors/_test/_testmain.go:102
+	// runtime.main
+	//         /home/dfc/go/src/runtime/proc.go:183
+	// runtime.goexit
+	//         /home/dfc/go/src/runtime/asm_amd64.s:2059
 }
 
 func Example_stacktrace() {
@@ -82,5 +132,21 @@ func Example_stacktrace() {
 	st := err.Stacktrace()
 	fmt.Printf("%+v", st[0:2]) // top two frames
 
-	// Output: [github.com/pkg/errors/example_test.go:32 github.com/pkg/errors/example_test.go:77]
+	// Example output:
+	// github.com/pkg/errors_test.fn
+	//	/home/dfc/src/github.com/pkg/errors/example_test.go:47
+	// github.com/pkg/errors_test.Example_stacktrace
+	//	/home/dfc/src/github.com/pkg/errors/example_test.go:127
+}
+
+func ExampleCause_printf() {
+	err := errors.Wrap(func() error {
+		return func() error {
+			return errors.Errorf("hello %s", fmt.Sprintf("world"))
+		}()
+	}(), "failed")
+
+	fmt.Printf("%v", err)
+
+	// Output: failed: hello world
 }

+ 70 - 15
format_test.go

@@ -3,16 +3,17 @@ package errors
 import (
 	"fmt"
 	"io"
+	"regexp"
+	"strings"
 	"testing"
 )
 
-func TestFormat(t *testing.T) {
+func TestFormatNew(t *testing.T) {
 	tests := []struct {
 		error
 		format string
 		want   string
 	}{{
-
 		New("error"),
 		"%s",
 		"error",
@@ -23,8 +24,22 @@ func TestFormat(t *testing.T) {
 	}, {
 		New("error"),
 		"%+v",
-		"github.com/pkg/errors/format_test.go:24: error",
-	}, {
+		"error\n" +
+			"github.com/pkg/errors.TestFormatNew\n" +
+			"\t.+/github.com/pkg/errors/format_test.go:25",
+	}}
+
+	for _, tt := range tests {
+		testFormatRegexp(t, tt.error, tt.format, tt.want)
+	}
+}
+
+func TestFormatErrorf(t *testing.T) {
+	tests := []struct {
+		error
+		format string
+		want   string
+	}{{
 		Errorf("%s", "error"),
 		"%s",
 		"error",
@@ -35,8 +50,22 @@ func TestFormat(t *testing.T) {
 	}, {
 		Errorf("%s", "error"),
 		"%+v",
-		"github.com/pkg/errors/format_test.go:36: error",
-	}, {
+		"error\n" +
+			"github.com/pkg/errors.TestFormatErrorf\n" +
+			"\t.+/github.com/pkg/errors/format_test.go:51",
+	}}
+
+	for _, tt := range tests {
+		testFormatRegexp(t, tt.error, tt.format, tt.want)
+	}
+}
+
+func TestFormatWrap(t *testing.T) {
+	tests := []struct {
+		error
+		format string
+		want   string
+	}{{
 		Wrap(New("error"), "error2"),
 		"%s",
 		"error2: error",
@@ -47,13 +76,26 @@ func TestFormat(t *testing.T) {
 	}, {
 		Wrap(New("error"), "error2"),
 		"%+v",
-		"github.com/pkg/errors/format_test.go:48: error\n" +
-			"github.com/pkg/errors/format_test.go:48: error2",
+		"error\n" +
+			"github.com/pkg/errors.TestFormatWrap\n" +
+			"\t.+/github.com/pkg/errors/format_test.go:77",
 	}, {
 		Wrap(io.EOF, "error"),
 		"%s",
 		"error: EOF",
-	}, {
+	}}
+
+	for _, tt := range tests {
+		testFormatRegexp(t, tt.error, tt.format, tt.want)
+	}
+}
+
+func TestFormatWrapf(t *testing.T) {
+	tests := []struct {
+		error
+		format string
+		want   string
+	}{{
 		Wrapf(New("error"), "error%d", 2),
 		"%s",
 		"error2: error",
@@ -65,7 +107,8 @@ func TestFormat(t *testing.T) {
 		Wrap(io.EOF, "error"),
 		"%+v",
 		"EOF\n" +
-			"github.com/pkg/errors/format_test.go:65: error",
+			"github.com/pkg/errors.TestFormatWrapf\n" +
+			"\t.+/github.com/pkg/errors/format_test.go:107: error",
 	}, {
 		Wrapf(New("error"), "error%d", 2),
 		"%v",
@@ -73,14 +116,26 @@ func TestFormat(t *testing.T) {
 	}, {
 		Wrapf(New("error"), "error%d", 2),
 		"%+v",
-		"github.com/pkg/errors/format_test.go:74: error\n" +
-			"github.com/pkg/errors/format_test.go:74: error2",
+		"error\n" +
+			"github.com/pkg/errors.TestFormatWrapf\n" +
+			"\t.+/github.com/pkg/errors/format_test.go:117",
 	}}
 
 	for _, tt := range tests {
-		got := fmt.Sprintf(tt.format, tt.error)
-		if got != tt.want {
-			t.Errorf("fmt.Sprintf(%q, err): got: %q, want: %q", tt.format, got, tt.want)
+		testFormatRegexp(t, tt.error, tt.format, tt.want)
+	}
+}
+
+func testFormatRegexp(t *testing.T, arg interface{}, format, want string) {
+	got := fmt.Sprintf(format, arg)
+	lines := strings.SplitN(got, "\n", -1)
+	for i, w := range strings.SplitN(want, "\n", -1) {
+		match, err := regexp.MatchString(w, lines[i])
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !match {
+			t.Errorf("fmt.Sprintf(%q, err): got: %q, want: %q", format, got, want)
 		}
 	}
 }

+ 4 - 2
stack.go

@@ -59,7 +59,7 @@ func (f Frame) Format(s fmt.State, verb rune) {
 				io.WriteString(s, "unknown")
 			} else {
 				file, _ := fn.FileLine(pc)
-				io.WriteString(s, trimGOPATH(fn.Name(), file))
+				fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
 			}
 		default:
 			io.WriteString(s, path.Base(f.file()))
@@ -84,7 +84,9 @@ func (st Stacktrace) Format(s fmt.State, verb rune) {
 	case 'v':
 		switch {
 		case s.Flag('+'):
-			fmt.Fprintf(s, "%+v", []Frame(st))
+			for _, f := range st {
+				fmt.Fprintf(s, "\n%+v", f)
+			}
 		case s.Flag('#'):
 			fmt.Fprintf(s, "%#v", []Frame(st))
 		default:

+ 42 - 39
stack_test.go

@@ -65,7 +65,8 @@ func TestFrameFormat(t *testing.T) {
 	}, {
 		Frame(initpc),
 		"%+s",
-		"github.com/pkg/errors/stack_test.go",
+		"github.com/pkg/errors.init\n" +
+			"\t.+/github.com/pkg/errors/stack_test.go",
 	}, {
 		Frame(0),
 		"%s",
@@ -92,7 +93,7 @@ func TestFrameFormat(t *testing.T) {
 			return x.ptr()
 		}(),
 		"%n",
-		"(*X).ptr",
+		`\(\*X\).ptr`,
 	}, {
 		func() Frame {
 			var x X
@@ -111,7 +112,8 @@ func TestFrameFormat(t *testing.T) {
 	}, {
 		Frame(initpc),
 		"%+v",
-		"github.com/pkg/errors/stack_test.go:9",
+		"github.com/pkg/errors.init\n" +
+			"\t.+/github.com/pkg/errors/stack_test.go:9",
 	}, {
 		Frame(0),
 		"%v",
@@ -119,11 +121,7 @@ func TestFrameFormat(t *testing.T) {
 	}}
 
 	for _, tt := range tests {
-		got := fmt.Sprintf(tt.format, tt.Frame)
-		want := tt.want
-		if want != got {
-			t.Errorf("%v %q: want: %q, got: %q", tt.Frame, tt.format, want, got)
-		}
+		testFormatRegexp(t, tt.Frame, tt.format, tt.want)
 	}
 }
 
@@ -175,20 +173,25 @@ func TestStacktrace(t *testing.T) {
 		want []string
 	}{{
 		New("ooh"), []string{
-			"github.com/pkg/errors/stack_test.go:177",
+			"github.com/pkg/errors.TestStacktrace\n" +
+				"\t.+/github.com/pkg/errors/stack_test.go:175",
 		},
 	}, {
 		Wrap(New("ooh"), "ahh"), []string{
-			"github.com/pkg/errors/stack_test.go:181", // this is the stack of Wrap, not New
+			"github.com/pkg/errors.TestStacktrace\n" +
+				"\t.+/github.com/pkg/errors/stack_test.go:180", // this is the stack of Wrap, not New
 		},
 	}, {
 		Cause(Wrap(New("ooh"), "ahh")), []string{
-			"github.com/pkg/errors/stack_test.go:185", // this is the stack of New
+			"github.com/pkg/errors.TestStacktrace\n" +
+				"\t.+/github.com/pkg/errors/stack_test.go:185", // this is the stack of New
 		},
 	}, {
 		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
+			`github.com/pkg/errors.(func·005|TestStacktrace.func1)` +
+				"\n\t.+/github.com/pkg/errors/stack_test.go:190", // this is the stack of New
+			"github.com/pkg/errors.TestStacktrace\n" +
+				"\t.+/github.com/pkg/errors/stack_test.go:190", // this is the stack of New's caller
 		},
 	}, {
 		Cause(func() error {
@@ -196,12 +199,15 @@ func TestStacktrace(t *testing.T) {
 				return Errorf("hello %s", fmt.Sprintf("world"))
 			}()
 		}()), []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
+			`github.com/pkg/errors.(func·006|TestStacktrace.func2.1)` +
+				"\n\t.+/github.com/pkg/errors/stack_test.go:199", // this is the stack of Errorf
+			`github.com/pkg/errors.(func·007|TestStacktrace.func2)` +
+				"\n\t.+/github.com/pkg/errors/stack_test.go:200", // this is the stack of Errorf's caller
+			"github.com/pkg/errors.TestStacktrace\n" +
+				"\t.+/github.com/pkg/errors/stack_test.go:201", // this is the stack of Errorf's caller's caller
 		},
 	}}
-	for i, tt := range tests {
+	for _, tt := range tests {
 		x, ok := tt.err.(interface {
 			Stacktrace() Stacktrace
 		})
@@ -211,11 +217,7 @@ func TestStacktrace(t *testing.T) {
 		}
 		st := x.Stacktrace()
 		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)
-			}
+			testFormatRegexp(t, st[j], "%+v", want)
 		}
 	}
 }
@@ -236,57 +238,58 @@ func TestStacktraceFormat(t *testing.T) {
 	}{{
 		nil,
 		"%s",
-		"[]",
+		`\[\]`,
 	}, {
 		nil,
 		"%v",
-		"[]",
+		`\[\]`,
 	}, {
 		nil,
 		"%+v",
-		"[]",
+		"",
 	}, {
 		nil,
 		"%#v",
-		"[]errors.Frame(nil)",
+		`\[\]errors.Frame\(nil\)`,
 	}, {
 		make(Stacktrace, 0),
 		"%s",
-		"[]",
+		`\[\]`,
 	}, {
 		make(Stacktrace, 0),
 		"%v",
-		"[]",
+		`\[\]`,
 	}, {
 		make(Stacktrace, 0),
 		"%+v",
-		"[]",
+		"",
 	}, {
 		make(Stacktrace, 0),
 		"%#v",
-		"[]errors.Frame{}",
+		`\[\]errors.Frame{}`,
 	}, {
 		stacktrace()[:2],
 		"%s",
-		"[stack_test.go stack_test.go]",
+		`\[stack_test.go stack_test.go\]`,
 	}, {
 		stacktrace()[:2],
 		"%v",
-		"[stack_test.go:226 stack_test.go:273]",
+		`\[stack_test.go:228 stack_test.go:275\]`,
 	}, {
 		stacktrace()[:2],
 		"%+v",
-		"[github.com/pkg/errors/stack_test.go:226 github.com/pkg/errors/stack_test.go:277]",
+		"\n" +
+			"github.com/pkg/errors.stacktrace\n" +
+			"\t.+/github.com/pkg/errors/stack_test.go:228\n" +
+			"github.com/pkg/errors.TestStacktraceFormat\n" +
+			"\t.+/github.com/pkg/errors/stack_test.go:279",
 	}, {
 		stacktrace()[:2],
 		"%#v",
-		"[]errors.Frame{stack_test.go:226, stack_test.go:281}",
+		`\[\]errors.Frame{stack_test.go:228, stack_test.go:287}`,
 	}}
 
-	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)
-		}
+	for _, tt := range tests {
+		testFormatRegexp(t, tt.Stacktrace, tt.format, tt.want)
 	}
 }