1 package stack_test
2
3 import (
4 "fmt"
5 "io/ioutil"
6 "path"
7 "path/filepath"
8 "reflect"
9 "runtime"
10 "strings"
11 "testing"
12
13 "github.com/go-stack/stack"
14 )
15
16 func TestCaller(t *testing.T) {
17 t.Parallel()
18
19 c := stack.Caller(0)
20 _, file, line, ok := runtime.Caller(0)
21 line--
22 if !ok {
23 t.Fatal("runtime.Caller(0) failed")
24 }
25
26 if got, want := c.Frame().File, file; got != want {
27 t.Errorf("got file == %v, want file == %v", got, want)
28 }
29
30 if got, want := c.Frame().Line, line; got != want {
31 t.Errorf("got line == %v, want line == %v", got, want)
32 }
33 }
34
35 func f3(f1 func() stack.Call) stack.Call {
36 return f2(f1)
37 }
38
39 func f2(f1 func() stack.Call) stack.Call {
40 return f1()
41 }
42
43 func TestCallerMidstackInlined(t *testing.T) {
44 t.Parallel()
45
46 _, _, line, ok := runtime.Caller(0)
47 line -= 10
48 if !ok {
49 t.Fatal("runtime.Caller(0) failed")
50 }
51
52 c := f3(func() stack.Call {
53 return stack.Caller(2)
54 })
55
56 if got, want := c.Frame().Line, line; got != want {
57 t.Errorf("got line == %v, want line == %v", got, want)
58 }
59 if got, want := c.Frame().Function, "github.com/go-stack/stack_test.f3"; got != want {
60 t.Errorf("got func name == %v, want func name == %v", got, want)
61 }
62 }
63
64 func TestCallerPanic(t *testing.T) {
65 t.Parallel()
66
67 var (
68 line int
69 ok bool
70 )
71
72 defer func() {
73 if recover() != nil {
74 var pcs [32]uintptr
75 n := runtime.Callers(1, pcs[:])
76 frames := runtime.CallersFrames(pcs[:n])
77
78 panicIdx := 0
79 for {
80 f, more := frames.Next()
81 if f.Function == "runtime.sigpanic" {
82 break
83 }
84 panicIdx++
85 if !more {
86 t.Fatal("no runtime.sigpanic entry on the stack")
87 }
88 }
89 c := stack.Caller(panicIdx)
90 if got, want := c.Frame().Function, "runtime.sigpanic"; got != want {
91 t.Errorf("sigpanic frame: got name == %v, want name == %v", got, want)
92 }
93 c1 := stack.Caller(panicIdx + 1)
94 if got, want := c1.Frame().Function, "github.com/go-stack/stack_test.TestCallerPanic"; got != want {
95 t.Errorf("TestCallerPanic frame: got name == %v, want name == %v", got, want)
96 }
97 if got, want := c1.Frame().Line, line; got != want {
98 t.Errorf("TestCallerPanic frame: got line == %v, want line == %v", got, want)
99 }
100 }
101 }()
102
103 _, _, line, ok = runtime.Caller(0)
104 line += 7
105 if !ok {
106 t.Fatal("runtime.Caller(0) failed")
107 }
108
109 var x *uintptr
110 _ = *x
111 }
112
113 type tholder struct {
114 trace func() stack.CallStack
115 }
116
117 func (th *tholder) traceLabyrinth() stack.CallStack {
118 for {
119 return th.trace()
120 }
121 }
122
123 func TestTrace(t *testing.T) {
124 t.Parallel()
125
126 _, _, line, ok := runtime.Caller(0)
127 if !ok {
128 t.Fatal("runtime.Caller(0) failed")
129 }
130
131 fh := tholder{
132 trace: func() stack.CallStack {
133 cs := stack.Trace()
134 return cs
135 },
136 }
137
138 cs := fh.traceLabyrinth()
139
140 lines := []int{line + 7, line - 7, line + 12}
141
142 for i, line := range lines {
143 if got, want := cs[i].Frame().Line, line; got != want {
144 t.Errorf("got line[%d] == %v, want line[%d] == %v", i, got, i, want)
145 }
146 }
147 }
148
149
150 func TestTracePanic(t *testing.T) {
151 t.Parallel()
152
153 var (
154 line int
155 ok bool
156 )
157
158 defer func() {
159 if recover() != nil {
160 trace := stack.Trace()
161
162
163 panicIdx := -1
164 for i, c := range trace {
165 if c.Frame().Function == "runtime.sigpanic" {
166 panicIdx = i
167 break
168 }
169 }
170 if panicIdx == -1 {
171 t.Fatal("no runtime.sigpanic entry on the stack")
172 }
173 if got, want := trace[panicIdx].Frame().Function, "runtime.sigpanic"; got != want {
174 t.Errorf("sigpanic frame: got name == %v, want name == %v", got, want)
175 }
176 if got, want := trace[panicIdx+1].Frame().Function, "github.com/go-stack/stack_test.TestTracePanic"; got != want {
177 t.Errorf("TestTracePanic frame: got name == %v, want name == %v", got, want)
178 }
179 if got, want := trace[panicIdx+1].Frame().Line, line; got != want {
180 t.Errorf("TestTracePanic frame: got line == %v, want line == %v", got, want)
181 }
182 }
183 }()
184
185 _, _, line, ok = runtime.Caller(0)
186 line += 7
187 if !ok {
188 t.Fatal("runtime.Caller(0) failed")
189 }
190
191 var x *uintptr
192 _ = *x
193 }
194
195 const importPath = "github.com/go-stack/stack"
196
197 type testType struct{}
198
199 func (tt testType) testMethod() (c stack.Call, file string, line int, ok bool) {
200 c = stack.Caller(0)
201 _, file, line, ok = runtime.Caller(0)
202 line--
203 return
204 }
205
206 func TestCallFormat(t *testing.T) {
207 t.Parallel()
208
209 c := stack.Caller(0)
210 _, file, line, ok := runtime.Caller(0)
211 line--
212 if !ok {
213 t.Fatal("runtime.Caller(0) failed")
214 }
215 relFile := path.Join(importPath, filepath.Base(file))
216
217 c2, file2, line2, ok2 := testType{}.testMethod()
218 if !ok2 {
219 t.Fatal("runtime.Caller(0) failed")
220 }
221 relFile2 := path.Join(importPath, filepath.Base(file2))
222
223 data := []struct {
224 c stack.Call
225 desc string
226 fmt string
227 out string
228 }{
229 {stack.Call{}, "error", "%s", "%!s(NOFUNC)"},
230
231 {c, "func", "%s", path.Base(file)},
232 {c, "func", "%+s", relFile},
233 {c, "func", "%#s", file},
234 {c, "func", "%d", fmt.Sprint(line)},
235 {c, "func", "%n", "TestCallFormat"},
236 {c, "func", "%+n", "github.com/go-stack/stack_test.TestCallFormat"},
237 {c, "func", "%k", "stack_test"},
238 {c, "func", "%+k", "github.com/go-stack/stack_test"},
239 {c, "func", "%v", fmt.Sprint(path.Base(file), ":", line)},
240 {c, "func", "%+v", fmt.Sprint(relFile, ":", line)},
241 {c, "func", "%#v", fmt.Sprint(file, ":", line)},
242
243 {c2, "meth", "%s", path.Base(file2)},
244 {c2, "meth", "%+s", relFile2},
245 {c2, "meth", "%#s", file2},
246 {c2, "meth", "%d", fmt.Sprint(line2)},
247 {c2, "meth", "%n", "testType.testMethod"},
248 {c2, "meth", "%+n", "github.com/go-stack/stack_test.testType.testMethod"},
249 {c2, "meth", "%k", "stack_test"},
250 {c2, "meth", "%+k", "github.com/go-stack/stack_test"},
251 {c2, "meth", "%v", fmt.Sprint(path.Base(file2), ":", line2)},
252 {c2, "meth", "%+v", fmt.Sprint(relFile2, ":", line2)},
253 {c2, "meth", "%#v", fmt.Sprint(file2, ":", line2)},
254 }
255
256 for _, d := range data {
257 got := fmt.Sprintf(d.fmt, d.c)
258 if got != d.out {
259 t.Errorf("fmt.Sprintf(%q, Call(%s)) = %s, want %s", d.fmt, d.desc, got, d.out)
260 }
261 }
262 }
263
264 func TestCallString(t *testing.T) {
265 t.Parallel()
266
267 c := stack.Caller(0)
268 _, file, line, ok := runtime.Caller(0)
269 line--
270 if !ok {
271 t.Fatal("runtime.Caller(0) failed")
272 }
273
274 c2, file2, line2, ok2 := testType{}.testMethod()
275 if !ok2 {
276 t.Fatal("runtime.Caller(0) failed")
277 }
278
279 data := []struct {
280 c stack.Call
281 desc string
282 out string
283 }{
284 {stack.Call{}, "error", "%!v(NOFUNC)"},
285 {c, "func", fmt.Sprint(path.Base(file), ":", line)},
286 {c2, "meth", fmt.Sprint(path.Base(file2), ":", line2)},
287 }
288
289 for _, d := range data {
290 got := d.c.String()
291 if got != d.out {
292 t.Errorf("got %s, want %s", got, d.out)
293 }
294 }
295 }
296
297 func TestCallMarshalText(t *testing.T) {
298 t.Parallel()
299
300 c := stack.Caller(0)
301 _, file, line, ok := runtime.Caller(0)
302 line--
303 if !ok {
304 t.Fatal("runtime.Caller(0) failed")
305 }
306
307 c2, file2, line2, ok2 := testType{}.testMethod()
308 if !ok2 {
309 t.Fatal("runtime.Caller(0) failed")
310 }
311
312 data := []struct {
313 c stack.Call
314 desc string
315 out []byte
316 err error
317 }{
318 {stack.Call{}, "error", nil, stack.ErrNoFunc},
319 {c, "func", []byte(fmt.Sprint(path.Base(file), ":", line)), nil},
320 {c2, "meth", []byte(fmt.Sprint(path.Base(file2), ":", line2)), nil},
321 }
322
323 for _, d := range data {
324 text, err := d.c.MarshalText()
325 if got, want := err, d.err; got != want {
326 t.Errorf("%s: got err %v, want err %v", d.desc, got, want)
327 }
328 if got, want := text, d.out; !reflect.DeepEqual(got, want) {
329 t.Errorf("%s: got %s, want %s", d.desc, got, want)
330 }
331 }
332 }
333
334 func TestCallStackString(t *testing.T) {
335 cs, line0 := getTrace(t)
336 _, file, line1, ok := runtime.Caller(0)
337 line1--
338 if !ok {
339 t.Fatal("runtime.Caller(0) failed")
340 }
341 file = path.Base(file)
342 if got, want := cs.String(), fmt.Sprintf("[%s:%d %s:%d]", file, line0, file, line1); got != want {
343 t.Errorf("\n got %v\nwant %v", got, want)
344 }
345 }
346
347 func TestCallStackMarshalText(t *testing.T) {
348 cs, line0 := getTrace(t)
349 _, file, line1, ok := runtime.Caller(0)
350 line1--
351 if !ok {
352 t.Fatal("runtime.Caller(0) failed")
353 }
354 file = path.Base(file)
355 text, _ := cs.MarshalText()
356 if got, want := text, []byte(fmt.Sprintf("[%s:%d %s:%d]", file, line0, file, line1)); !reflect.DeepEqual(got, want) {
357 t.Errorf("\n got %v\nwant %v", got, want)
358 }
359 }
360
361 func getTrace(t *testing.T) (stack.CallStack, int) {
362 cs := stack.Trace().TrimRuntime()
363 _, _, line, ok := runtime.Caller(0)
364 line--
365 if !ok {
366 t.Fatal("runtime.Caller(0) failed")
367 }
368 return cs, line
369 }
370
371 func TestTrimAbove(t *testing.T) {
372 trace := trimAbove()
373 if got, want := len(trace), 2; got != want {
374 t.Fatalf("got len(trace) == %v, want %v, trace: %n", got, want, trace)
375 }
376 if got, want := fmt.Sprintf("%n", trace[1]), "TestTrimAbove"; got != want {
377 t.Errorf("got %q, want %q", got, want)
378 }
379 }
380
381 func trimAbove() stack.CallStack {
382 call := stack.Caller(1)
383 trace := stack.Trace()
384 return trace.TrimAbove(call)
385 }
386
387 func TestTrimBelow(t *testing.T) {
388 trace := trimBelow()
389 if got, want := fmt.Sprintf("%n", trace[0]), "TestTrimBelow"; got != want {
390 t.Errorf("got %q, want %q", got, want)
391 }
392 }
393
394 func trimBelow() stack.CallStack {
395 call := stack.Caller(1)
396 trace := stack.Trace()
397 return trace.TrimBelow(call)
398 }
399
400 func TestTrimRuntime(t *testing.T) {
401 trace := stack.Trace().TrimRuntime()
402 if got, want := len(trace), 1; got != want {
403 t.Errorf("got len(trace) == %v, want %v, goroot: %q, trace: %#v", got, want, runtime.GOROOT(), trace)
404 }
405 }
406
407 func BenchmarkCallVFmt(b *testing.B) {
408 c := stack.Caller(0)
409 b.ResetTimer()
410 for i := 0; i < b.N; i++ {
411 fmt.Fprint(ioutil.Discard, c)
412 }
413 }
414
415 func BenchmarkCallPlusVFmt(b *testing.B) {
416 c := stack.Caller(0)
417 b.ResetTimer()
418 for i := 0; i < b.N; i++ {
419 fmt.Fprintf(ioutil.Discard, "%+v", c)
420 }
421 }
422
423 func BenchmarkCallSharpVFmt(b *testing.B) {
424 c := stack.Caller(0)
425 b.ResetTimer()
426 for i := 0; i < b.N; i++ {
427 fmt.Fprintf(ioutil.Discard, "%#v", c)
428 }
429 }
430
431 func BenchmarkCallSFmt(b *testing.B) {
432 c := stack.Caller(0)
433 b.ResetTimer()
434 for i := 0; i < b.N; i++ {
435 fmt.Fprintf(ioutil.Discard, "%s", c)
436 }
437 }
438
439 func BenchmarkCallPlusSFmt(b *testing.B) {
440 c := stack.Caller(0)
441 b.ResetTimer()
442 for i := 0; i < b.N; i++ {
443 fmt.Fprintf(ioutil.Discard, "%+s", c)
444 }
445 }
446
447 func BenchmarkCallSharpSFmt(b *testing.B) {
448 c := stack.Caller(0)
449 b.ResetTimer()
450 for i := 0; i < b.N; i++ {
451 fmt.Fprintf(ioutil.Discard, "%#s", c)
452 }
453 }
454
455 func BenchmarkCallDFmt(b *testing.B) {
456 c := stack.Caller(0)
457 b.ResetTimer()
458 for i := 0; i < b.N; i++ {
459 fmt.Fprintf(ioutil.Discard, "%d", c)
460 }
461 }
462
463 func BenchmarkCallNFmt(b *testing.B) {
464 c := stack.Caller(0)
465 b.ResetTimer()
466 for i := 0; i < b.N; i++ {
467 fmt.Fprintf(ioutil.Discard, "%n", c)
468 }
469 }
470
471 func BenchmarkCallPlusNFmt(b *testing.B) {
472 c := stack.Caller(0)
473 b.ResetTimer()
474 for i := 0; i < b.N; i++ {
475 fmt.Fprintf(ioutil.Discard, "%+n", c)
476 }
477 }
478
479 func BenchmarkCaller(b *testing.B) {
480 for i := 0; i < b.N; i++ {
481 stack.Caller(0)
482 }
483 }
484
485 func BenchmarkTrace(b *testing.B) {
486 for i := 0; i < b.N; i++ {
487 stack.Trace()
488 }
489 }
490
491 func deepStack(depth int, b *testing.B) stack.CallStack {
492 if depth > 0 {
493 return deepStack(depth-1, b)
494 }
495 b.StartTimer()
496 s := stack.Trace()
497 return s
498 }
499
500 func BenchmarkTrace10(b *testing.B) {
501 for i := 0; i < b.N; i++ {
502 b.StopTimer()
503 deepStack(10, b)
504 }
505 }
506
507 func BenchmarkTrace50(b *testing.B) {
508 b.StopTimer()
509 for i := 0; i < b.N; i++ {
510 deepStack(50, b)
511 }
512 }
513
514 func BenchmarkTrace100(b *testing.B) {
515 b.StopTimer()
516 for i := 0; i < b.N; i++ {
517 deepStack(100, b)
518 }
519 }
520
521
522
523
524
525 func BenchmarkCallerAndVFmt(b *testing.B) {
526 for i := 0; i < b.N; i++ {
527 fmt.Fprint(ioutil.Discard, stack.Caller(0))
528 }
529 }
530
531 func BenchmarkTraceAndVFmt(b *testing.B) {
532 for i := 0; i < b.N; i++ {
533 fmt.Fprint(ioutil.Discard, stack.Trace())
534 }
535 }
536
537 func BenchmarkTrace10AndVFmt(b *testing.B) {
538 for i := 0; i < b.N; i++ {
539 b.StopTimer()
540 fmt.Fprint(ioutil.Discard, deepStack(10, b))
541 }
542 }
543
544
545
546
547
548 func BenchmarkRuntimeCaller(b *testing.B) {
549 for i := 0; i < b.N; i++ {
550 runtime.Caller(0)
551 }
552 }
553
554 func BenchmarkRuntimeCallerAndFmt(b *testing.B) {
555 for i := 0; i < b.N; i++ {
556 _, file, line, _ := runtime.Caller(0)
557 const sep = "/"
558 if i := strings.LastIndex(file, sep); i != -1 {
559 file = file[i+len(sep):]
560 }
561 fmt.Fprint(ioutil.Discard, file, ":", line)
562 }
563 }
564
565 func BenchmarkFuncForPC(b *testing.B) {
566 pc, _, _, _ := runtime.Caller(0)
567 pc--
568 b.ResetTimer()
569 for i := 0; i < b.N; i++ {
570 runtime.FuncForPC(pc)
571 }
572 }
573
574 func BenchmarkFuncFileLine(b *testing.B) {
575 pc, _, _, _ := runtime.Caller(0)
576 pc--
577 fn := runtime.FuncForPC(pc)
578 b.ResetTimer()
579 for i := 0; i < b.N; i++ {
580 fn.FileLine(pc)
581 }
582 }
583
View as plain text