Skip to content
This repository was archived by the owner on Dec 1, 2021. It is now read-only.

Commit a5b220e

Browse files
authored
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.
1 parent d7cdef1 commit a5b220e

File tree

3 files changed

+114
-28
lines changed

3 files changed

+114
-28
lines changed

example_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func ExampleErrorf() {
7171

7272
func Example_stacktrace() {
7373
type Stacktrace interface {
74-
Stacktrace() []errors.Frame
74+
Stacktrace() errors.Stacktrace
7575
}
7676

7777
err, ok := errors.Cause(fn()).(Stacktrace)

stack.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,29 @@ func (f Frame) Format(s fmt.State, verb rune) {
7676
}
7777
}
7878

79+
// Stacktrace is stack of Frames from innermost (newest) to outermost (oldest).
80+
type Stacktrace []Frame
81+
82+
func (st Stacktrace) Format(s fmt.State, verb rune) {
83+
switch verb {
84+
case 'v':
85+
switch {
86+
case s.Flag('+'):
87+
fmt.Fprintf(s, "%+v", []Frame(st))
88+
case s.Flag('#'):
89+
fmt.Fprintf(s, "%#v", []Frame(st))
90+
default:
91+
fmt.Fprintf(s, "%v", []Frame(st))
92+
}
93+
case 's':
94+
fmt.Fprintf(s, "%s", []Frame(st))
95+
}
96+
}
97+
7998
// stack represents a stack of program counters.
8099
type stack []uintptr
81100

82-
func (s *stack) Stacktrace() []Frame {
101+
func (s *stack) Stacktrace() Stacktrace {
83102
f := make([]Frame, len(*s))
84103
for i := 0; i < len(f); i++ {
85104
f[i] = Frame((*s)[i])

stack_test.go

Lines changed: 93 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -170,56 +170,123 @@ func TestTrimGOPATH(t *testing.T) {
170170
}
171171

172172
func TestStacktrace(t *testing.T) {
173-
type fileline struct {
174-
file string
175-
line int
176-
}
177173
tests := []struct {
178174
err error
179-
want []fileline
175+
want []string
180176
}{{
181-
New("ooh"), []fileline{
182-
{"github.com/pkg/errors/stack_test.go", 181},
177+
New("ooh"), []string{
178+
"github.com/pkg/errors/stack_test.go:177",
183179
},
184180
}, {
185-
Wrap(New("ooh"), "ahh"), []fileline{
186-
{"github.com/pkg/errors/stack_test.go", 185}, // this is the stack of Wrap, not New
181+
Wrap(New("ooh"), "ahh"), []string{
182+
"github.com/pkg/errors/stack_test.go:181", // this is the stack of Wrap, not New
187183
},
188184
}, {
189-
Cause(Wrap(New("ooh"), "ahh")), []fileline{
190-
{"github.com/pkg/errors/stack_test.go", 189}, // this is the stack of New
185+
Cause(Wrap(New("ooh"), "ahh")), []string{
186+
"github.com/pkg/errors/stack_test.go:185", // this is the stack of New
191187
},
192188
}, {
193-
func() error { return New("ooh") }(), []fileline{
194-
{"github.com/pkg/errors/stack_test.go", 193}, // this is the stack of New
195-
{"github.com/pkg/errors/stack_test.go", 193}, // this is the stack of New's caller
189+
func() error { return New("ooh") }(), []string{
190+
"github.com/pkg/errors/stack_test.go:189", // this is the stack of New
191+
"github.com/pkg/errors/stack_test.go:189", // this is the stack of New's caller
196192
},
197193
}, {
198194
Cause(func() error {
199195
return func() error {
200196
return Errorf("hello %s", fmt.Sprintf("world"))
201197
}()
202-
}()), []fileline{
203-
{"github.com/pkg/errors/stack_test.go", 200}, // this is the stack of Errorf
204-
{"github.com/pkg/errors/stack_test.go", 201}, // this is the stack of Errorf's caller
205-
{"github.com/pkg/errors/stack_test.go", 202}, // this is the stack of Errorf's caller's caller
198+
}()), []string{
199+
"github.com/pkg/errors/stack_test.go:196", // this is the stack of Errorf
200+
"github.com/pkg/errors/stack_test.go:197", // this is the stack of Errorf's caller
201+
"github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller
206202
},
207203
}}
208-
for _, tt := range tests {
204+
for i, tt := range tests {
209205
x, ok := tt.err.(interface {
210-
Stacktrace() []Frame
206+
Stacktrace() Stacktrace
211207
})
212208
if !ok {
213-
t.Errorf("expected %#v to implement Stacktrace() []Frame", tt.err)
209+
t.Errorf("expected %#v to implement Stacktrace() Stacktrace", tt.err)
214210
continue
215211
}
216212
st := x.Stacktrace()
217-
for i, want := range tt.want {
218-
frame := st[i]
219-
file, line := fmt.Sprintf("%+s", frame), frame.line()
220-
if file != want.file || line != want.line {
221-
t.Errorf("frame %d: expected %s:%d, got %s:%d", i, want.file, want.line, file, line)
213+
for j, want := range tt.want {
214+
frame := st[j]
215+
got := fmt.Sprintf("%+v", frame)
216+
if got != want {
217+
t.Errorf("test %d: frame %d: got %q, want %q", i, j, got, want)
222218
}
223219
}
224220
}
225221
}
222+
223+
func stacktrace() Stacktrace {
224+
const depth = 8
225+
var pcs [depth]uintptr
226+
n := runtime.Callers(1, pcs[:])
227+
var st stack = pcs[0:n]
228+
return st.Stacktrace()
229+
}
230+
231+
func TestStacktraceFormat(t *testing.T) {
232+
tests := []struct {
233+
Stacktrace
234+
format string
235+
want string
236+
}{{
237+
nil,
238+
"%s",
239+
"[]",
240+
}, {
241+
nil,
242+
"%v",
243+
"[]",
244+
}, {
245+
nil,
246+
"%+v",
247+
"[]",
248+
}, {
249+
nil,
250+
"%#v",
251+
"[]errors.Frame(nil)",
252+
}, {
253+
make(Stacktrace, 0),
254+
"%s",
255+
"[]",
256+
}, {
257+
make(Stacktrace, 0),
258+
"%v",
259+
"[]",
260+
}, {
261+
make(Stacktrace, 0),
262+
"%+v",
263+
"[]",
264+
}, {
265+
make(Stacktrace, 0),
266+
"%#v",
267+
"[]errors.Frame{}",
268+
}, {
269+
stacktrace()[:2],
270+
"%s",
271+
"[stack_test.go stack_test.go]",
272+
}, {
273+
stacktrace()[:2],
274+
"%v",
275+
"[stack_test.go:226 stack_test.go:273]",
276+
}, {
277+
stacktrace()[:2],
278+
"%+v",
279+
"[github.com/pkg/errors/stack_test.go:226 github.com/pkg/errors/stack_test.go:277]",
280+
}, {
281+
stacktrace()[:2],
282+
"%#v",
283+
"[]errors.Frame{stack_test.go:226, stack_test.go:281}",
284+
}}
285+
286+
for i, tt := range tests {
287+
got := fmt.Sprintf(tt.format, tt.Stacktrace)
288+
if got != tt.want {
289+
t.Errorf("test %d: got: %q, want: %q", i+1, got, tt.want)
290+
}
291+
}
292+
}

0 commit comments

Comments
 (0)