package errors import ( "fmt" "regexp" "runtime" "strings" "testing" ) var initpc = caller() func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { t.Helper() got := fmt.Sprintf(format, arg) gotLines := strings.SplitN(got, "\n", -1) wantLines := strings.SplitN(want, "\n", -1) if len(wantLines) > len(gotLines) { t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want) return } for i, w := range wantLines { match, err := regexp.MatchString(w, gotLines[i]) if err != nil { t.Fatal(err) } if !match { t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want) } } } type X struct{} // val returns a Frame pointing to itself. func (x X) val() Frame { return caller() } // ptr returns a Frame pointing to itself. func (x *X) ptr() Frame { return caller() } func TestFrameFormat(t *testing.T) { var tests = []struct { Frame format string want string }{{ initpc, "%s", "stack_test.go", }, { initpc, "%+s", "github.com/go-ap/errors.init\n" + "\t.+/errors/stack_test.go", }, { 0, "%s", "unknown", }, { 0, "%+s", "unknown", }, { initpc, "%d", "11", }, { 0, "%d", "0", }, { initpc, "%n", "init", }, { func() Frame { var x X return x.ptr() }(), "%n", `\(\*X\).ptr`, }, { func() Frame { var x X return x.val() }(), "%n", "X.val", }, { 0, "%n", "", }, { initpc, "%v", "stack_test.go:11", }, { initpc, "%+v", "github.com/go-ap/errors.init\n" + "\t.+/errors/stack_test.go:11", }, { 0, "%v", "unknown:0", }} for i, tt := range tests { testFormatRegexp(t, i, tt.Frame, tt.format, tt.want) } } func TestFuncname(t *testing.T) { tests := []struct { name, want string }{ {"", ""}, {"runtime.main", "main"}, {"github.com/go-ap/errors.funcname", "funcname"}, {"funcname", "funcname"}, {"io.copyBuffer", "copyBuffer"}, {"main.(*R).Write", "(*R).Write"}, } for _, tt := range tests { got := funcname(tt.name) want := tt.want if got != want { t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got) } } } func TestStackTrace(t *testing.T) { tests := []struct { err error want []string }{{ Newf("ooh"), []string{ "github.com/go-ap/errors.TestStackTrace\n" + "\t.+/errors/stack_test.go:145", }, }, { wrap(Newf("ooh"), "ahh"), []string{ "github.com/go-ap/errors.TestStackTrace\n" + "\t.+/errors/stack_test.go:150", // this is the stack of Wrap, not New }, }, { func() error { return Newf("ooh") }(), []string{ `github.com/go-ap/errors.TestStackTrace.func1` + "\n\t.+/errors/stack_test.go:155", // this is the stack of New "github.com/go-ap/errors.TestStackTrace\n" + "\t.+/errors/stack_test.go:155", // this is the stack of New's caller }, }} t.Skipf(`TODO(marius): This needs some more work As going one level up the stack in stack.callers() removes meaningful information from the tests`) for i, tt := range tests { x, ok := tt.err.(interface { StackTrace() StackTrace }) if !ok { t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err) continue } st := x.StackTrace() if len(st) == 0 { continue } for j, want := range tt.want { testFormatRegexp(t, i, st[j], "%+v", 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:183 stack_test.go:230\]`, }, { stackTrace()[:2], "%+v", "\n" + "github.com/go-ap/errors.stackTrace\n" + "\t.+/errors/stack_test.go:183\n" + "github.com/go-ap/errors.TestStackTraceFormat\n" + "\t.+/errors/stack_test.go:234", }, { stackTrace()[:2], "%#v", `\[\]errors.Frame{stack_test.go:183, stack_test.go:242}`, }} t.Skipf(`TODO(marius): This needs some more work As going one level up the stack in stack.callers() removes meaningful information from the tests`) for i, tt := range tests { testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) } } // a version of runtime.Caller that returns a Frame, not a uintptr. func caller() Frame { var pcs [3]uintptr n := runtime.Callers(2, pcs[:]) frames := runtime.CallersFrames(pcs[:n]) frame, _ := frames.Next() return Frame(frame.PC) }