stack_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. // Copyright (c) 2019 Uber Technologies, Inc.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. package xreflect
  21. import (
  22. "fmt"
  23. "testing"
  24. "github.com/stretchr/testify/assert"
  25. "github.com/stretchr/testify/require"
  26. )
  27. func TestStack(t *testing.T) {
  28. // NOTE:
  29. // We don't assert the length of the stack because we cannot make
  30. // guarantees about how many frames the test runner is allowed to
  31. // introduce.
  32. t.Run("default", func(t *testing.T) {
  33. frames := CallerStack(0, 0)
  34. require.NotEmpty(t, frames)
  35. f := frames[0]
  36. assert.Equal(t, "git.llsapp.com/zhenghe/pkg/di/xreflect.TestStack.func1", f.Function)
  37. assert.Contains(t, f.File, "xreflect/stack_test.go")
  38. assert.NotZero(t, f.Line)
  39. })
  40. t.Run("default/deeper", func(t *testing.T) {
  41. // Introduce a few frames.
  42. frames := func() []Frame {
  43. return func() []Frame {
  44. return CallerStack(0, 0)
  45. }()
  46. }()
  47. require.True(t, len(frames) > 3, "expected at least three frames")
  48. for i, name := range []string{"func2.1.1", "func2.1", "func2"} {
  49. f := frames[i]
  50. assert.Equal(t, "git.llsapp.com/zhenghe/pkg/di/xreflect.TestStack."+name, f.Function)
  51. assert.Contains(t, f.File, "xreflect/stack_test.go")
  52. assert.NotZero(t, f.Line)
  53. }
  54. })
  55. t.Run("skip", func(t *testing.T) {
  56. // Introduce a few frames and skip 2.
  57. frames := func() []Frame {
  58. return func() []Frame {
  59. return CallerStack(2, 0)
  60. }()
  61. }()
  62. require.NotEmpty(t, frames)
  63. f := frames[0]
  64. assert.Equal(t, "git.llsapp.com/zhenghe/pkg/di/xreflect.TestStack.func3", f.Function)
  65. assert.Contains(t, f.File, "xreflect/stack_test.go")
  66. assert.NotZero(t, f.Line)
  67. })
  68. }
  69. func TestStackCallerName(t *testing.T) {
  70. tests := []struct {
  71. desc string
  72. give Stack
  73. want string
  74. }{
  75. {desc: "empty", want: "n/a"},
  76. {
  77. desc: "skip di components",
  78. give: Stack{
  79. {
  80. Function: "git.llsapp.com/zhenghe/pkg/di.Foo()",
  81. File: "git.llsapp.com/zhenghe/pkg/di/foo.go",
  82. },
  83. {
  84. Function: "foo/bar.Baz()",
  85. File: "foo/bar/baz.go",
  86. },
  87. },
  88. want: "foo/bar.Baz()",
  89. },
  90. {
  91. desc: "skip di in wrong directory",
  92. give: Stack{
  93. {
  94. Function: "git.llsapp.com/zhenghe/pkg/di/di.Foo()",
  95. File: "di/foo.go",
  96. },
  97. {
  98. Function: "foo/bar.Baz()",
  99. File: "foo/bar/baz.go",
  100. },
  101. },
  102. want: "foo/bar.Baz()",
  103. },
  104. {
  105. desc: "skip di subpackage",
  106. give: Stack{
  107. {
  108. Function: "git.llsapp.com/zhenghe/pkg/di/xreflect.Foo()",
  109. File: "di/internal/xreflect/foo.go",
  110. },
  111. {
  112. Function: "foo/bar.Baz()",
  113. File: "foo/bar/baz.go",
  114. },
  115. },
  116. want: "foo/bar.Baz()",
  117. },
  118. {
  119. desc: "don't skip di tests",
  120. give: Stack{
  121. {
  122. Function: "some/thing.Foo()",
  123. File: "git.llsapp.com/zhenghe/pkg/di/foo_test.go",
  124. },
  125. },
  126. want: "some/thing.Foo()",
  127. },
  128. {
  129. desc: "don't skip di prefix",
  130. give: Stack{
  131. {
  132. Function: "git.llsapp.com/zhenghe/pkg/difoo.Bar()",
  133. File: "git.llsapp.com/zhenghe/pkg/difoo/bar.go",
  134. },
  135. },
  136. want: "git.llsapp.com/zhenghe/pkg/difoo.Bar()",
  137. },
  138. }
  139. for _, tt := range tests {
  140. t.Run(tt.desc, func(t *testing.T) {
  141. assert.Equal(t, tt.want, tt.give.CallerName())
  142. })
  143. }
  144. }
  145. func TestFrameString(t *testing.T) {
  146. tests := []struct {
  147. desc string
  148. give Frame
  149. want string
  150. }{
  151. {
  152. desc: "zero",
  153. give: Frame{},
  154. want: "unknown",
  155. },
  156. {
  157. desc: "file and line",
  158. give: Frame{File: "foo.go", Line: 42},
  159. want: "(foo.go:42)",
  160. },
  161. {
  162. desc: "file only",
  163. give: Frame{File: "foo.go"},
  164. want: "(foo.go)",
  165. },
  166. {
  167. desc: "function only",
  168. give: Frame{Function: "foo"},
  169. want: "foo",
  170. },
  171. {
  172. desc: "function and file",
  173. give: Frame{Function: "foo", File: "bar.go"},
  174. want: "foo (bar.go)",
  175. },
  176. {
  177. desc: "function and line",
  178. give: Frame{Function: "foo", Line: 42},
  179. want: "foo", // line without file is meaningless
  180. },
  181. {
  182. desc: "function, file, and line",
  183. give: Frame{Function: "foo", File: "bar.go", Line: 42},
  184. want: "foo (bar.go:42)",
  185. },
  186. }
  187. for _, tt := range tests {
  188. t.Run(tt.desc, func(t *testing.T) {
  189. assert.Equal(t, tt.want, tt.give.String())
  190. })
  191. }
  192. }
  193. func TestStackFormat(t *testing.T) {
  194. stack := Stack{
  195. {
  196. Function: "path/to/module.SomeFunction()",
  197. File: "path/to/file.go",
  198. Line: 42,
  199. },
  200. {
  201. Function: "path/to/another/module.AnotherFunction()",
  202. File: "path/to/another/file.go",
  203. Line: 12,
  204. },
  205. }
  206. t.Run("single line", func(t *testing.T) {
  207. assert.Equal(t,
  208. "path/to/module.SomeFunction() (path/to/file.go:42); "+
  209. "path/to/another/module.AnotherFunction() (path/to/another/file.go:12)",
  210. fmt.Sprintf("%v", stack))
  211. })
  212. t.Run("multi line", func(t *testing.T) {
  213. assert.Equal(t, `path/to/module.SomeFunction()
  214. path/to/file.go:42
  215. path/to/another/module.AnotherFunction()
  216. path/to/another/file.go:12
  217. `, fmt.Sprintf("%+v", stack))
  218. })
  219. }