diff --git a/errors.go b/errors.go index 0e4f94a..8143650 100644 --- a/errors.go +++ b/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': diff --git a/example_test.go b/example_test.go index ebec7e7..0f0c80a 100644 --- a/example_test.go +++ b/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 } diff --git a/format_test.go b/format_test.go index c7d49f4..60806ae 100644 --- a/format_test.go +++ b/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) } } } diff --git a/stack.go b/stack.go index 6f6940d..72a7a6c 100644 --- a/stack.go +++ b/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: diff --git a/stack_test.go b/stack_test.go index 00221e4..dcc5436 100644 --- a/stack_test.go +++ b/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) } }