1package errors
2
3import (
4	"fmt"
5	"runtime"
6	"testing"
7)
8
9var initpc = caller()
10
11type X struct{}
12
13// val returns a Frame pointing to itself.
14func (x X) val() Frame {
15	return caller()
16}
17
18// ptr returns a Frame pointing to itself.
19func (x *X) ptr() Frame {
20	return caller()
21}
22
23func TestFrameFormat(t *testing.T) {
24	var tests = []struct {
25		Frame
26		format string
27		want   string
28	}{{
29		initpc,
30		"%s",
31		"stack_test.go",
32	}, {
33		initpc,
34		"%+s",
35		"github.com/pkg/errors.init\n" +
36			"\t.+/github.com/pkg/errors/stack_test.go",
37	}, {
38		0,
39		"%s",
40		"unknown",
41	}, {
42		0,
43		"%+s",
44		"unknown",
45	}, {
46		initpc,
47		"%d",
48		"9",
49	}, {
50		0,
51		"%d",
52		"0",
53	}, {
54		initpc,
55		"%n",
56		"init",
57	}, {
58		func() Frame {
59			var x X
60			return x.ptr()
61		}(),
62		"%n",
63		`\(\*X\).ptr`,
64	}, {
65		func() Frame {
66			var x X
67			return x.val()
68		}(),
69		"%n",
70		"X.val",
71	}, {
72		0,
73		"%n",
74		"",
75	}, {
76		initpc,
77		"%v",
78		"stack_test.go:9",
79	}, {
80		initpc,
81		"%+v",
82		"github.com/pkg/errors.init\n" +
83			"\t.+/github.com/pkg/errors/stack_test.go:9",
84	}, {
85		0,
86		"%v",
87		"unknown:0",
88	}}
89
90	for i, tt := range tests {
91		testFormatRegexp(t, i, tt.Frame, tt.format, tt.want)
92	}
93}
94
95func TestFuncname(t *testing.T) {
96	tests := []struct {
97		name, want string
98	}{
99		{"", ""},
100		{"runtime.main", "main"},
101		{"github.com/pkg/errors.funcname", "funcname"},
102		{"funcname", "funcname"},
103		{"io.copyBuffer", "copyBuffer"},
104		{"main.(*R).Write", "(*R).Write"},
105	}
106
107	for _, tt := range tests {
108		got := funcname(tt.name)
109		want := tt.want
110		if got != want {
111			t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got)
112		}
113	}
114}
115
116func TestStackTrace(t *testing.T) {
117	tests := []struct {
118		err  error
119		want []string
120	}{{
121		New("ooh"), []string{
122			"github.com/pkg/errors.TestStackTrace\n" +
123				"\t.+/github.com/pkg/errors/stack_test.go:121",
124		},
125	}, {
126		Wrap(New("ooh"), "ahh"), []string{
127			"github.com/pkg/errors.TestStackTrace\n" +
128				"\t.+/github.com/pkg/errors/stack_test.go:126", // this is the stack of Wrap, not New
129		},
130	}, {
131		Cause(Wrap(New("ooh"), "ahh")), []string{
132			"github.com/pkg/errors.TestStackTrace\n" +
133				"\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New
134		},
135	}, {
136		func() error { return New("ooh") }(), []string{
137			`github.com/pkg/errors.TestStackTrace.func1` +
138				"\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New
139			"github.com/pkg/errors.TestStackTrace\n" +
140				"\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New's caller
141		},
142	}, {
143		Cause(func() error {
144			return func() error {
145				return Errorf("hello %s", fmt.Sprintf("world: %s", "ooh"))
146			}()
147		}()), []string{
148			`github.com/pkg/errors.TestStackTrace.func2.1` +
149				"\n\t.+/github.com/pkg/errors/stack_test.go:145", // this is the stack of Errorf
150			`github.com/pkg/errors.TestStackTrace.func2` +
151				"\n\t.+/github.com/pkg/errors/stack_test.go:146", // this is the stack of Errorf's caller
152			"github.com/pkg/errors.TestStackTrace\n" +
153				"\t.+/github.com/pkg/errors/stack_test.go:147", // this is the stack of Errorf's caller's caller
154		},
155	}}
156	for i, tt := range tests {
157		x, ok := tt.err.(interface {
158			StackTrace() StackTrace
159		})
160		if !ok {
161			t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err)
162			continue
163		}
164		st := x.StackTrace()
165		for j, want := range tt.want {
166			testFormatRegexp(t, i, st[j], "%+v", want)
167		}
168	}
169}
170
171func stackTrace() StackTrace {
172	const depth = 8
173	var pcs [depth]uintptr
174	n := runtime.Callers(1, pcs[:])
175	var st stack = pcs[0:n]
176	return st.StackTrace()
177}
178
179func TestStackTraceFormat(t *testing.T) {
180	tests := []struct {
181		StackTrace
182		format string
183		want   string
184	}{{
185		nil,
186		"%s",
187		`\[\]`,
188	}, {
189		nil,
190		"%v",
191		`\[\]`,
192	}, {
193		nil,
194		"%+v",
195		"",
196	}, {
197		nil,
198		"%#v",
199		`\[\]errors.Frame\(nil\)`,
200	}, {
201		make(StackTrace, 0),
202		"%s",
203		`\[\]`,
204	}, {
205		make(StackTrace, 0),
206		"%v",
207		`\[\]`,
208	}, {
209		make(StackTrace, 0),
210		"%+v",
211		"",
212	}, {
213		make(StackTrace, 0),
214		"%#v",
215		`\[\]errors.Frame{}`,
216	}, {
217		stackTrace()[:2],
218		"%s",
219		`\[stack_test.go stack_test.go\]`,
220	}, {
221		stackTrace()[:2],
222		"%v",
223		`\[stack_test.go:174 stack_test.go:221\]`,
224	}, {
225		stackTrace()[:2],
226		"%+v",
227		"\n" +
228			"github.com/pkg/errors.stackTrace\n" +
229			"\t.+/github.com/pkg/errors/stack_test.go:174\n" +
230			"github.com/pkg/errors.TestStackTraceFormat\n" +
231			"\t.+/github.com/pkg/errors/stack_test.go:225",
232	}, {
233		stackTrace()[:2],
234		"%#v",
235		`\[\]errors.Frame{stack_test.go:174, stack_test.go:233}`,
236	}}
237
238	for i, tt := range tests {
239		testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
240	}
241}
242
243// a version of runtime.Caller that returns a Frame, not a uintptr.
244func caller() Frame {
245	var pcs [3]uintptr
246	n := runtime.Callers(2, pcs[:])
247	frames := runtime.CallersFrames(pcs[:n])
248	frame, _ := frames.Next()
249	return Frame(frame.PC)
250}
251