You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

582 lines
12 KiB

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