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