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
diff --git a/errors.go b/errors.go
index 0e4f94a..8143650 100644
--- a/errors.go
+++ b/errors.go
@@ -87,7 +87,9 @@
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 @@
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 @@
// 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 @@
// 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 @@
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,28 +3,43 @@
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",
+ }, {
+ New("error"),
+ "%v",
+ "error",
+ }, {
+ New("error"),
+ "%+v",
+ "error\n" +
+ "github.com/pkg/errors.TestFormatNew\n" +
+ "\t.+/github.com/pkg/errors/format_test.go:25",
+ }}
- New("error"),
- "%s",
- "error",
- }, {
- New("error"),
- "%v",
- "error",
- }, {
- New("error"),
- "%+v",
- "github.com/pkg/errors/format_test.go:24: error",
- }, {
+ 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 @@
}, {
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 @@
}, {
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 @@
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 @@
}, {
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 @@
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 @@
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 @@
}, {
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 @@
return x.ptr()
}(),
"%n",
- "(*X).ptr",
+ `\(\*X\).ptr`,
}, {
func() Frame {
var x X
@@ -111,7 +112,8 @@
}, {
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 @@
}}
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 @@
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 @@
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 @@
}
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 @@
}{{
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)
}
}