resource_test.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package tstest
  4. import (
  5. "strings"
  6. "testing"
  7. "github.com/google/go-cmp/cmp"
  8. )
  9. func TestPrintGoroutines(t *testing.T) {
  10. tests := []struct {
  11. name string
  12. in string
  13. want string
  14. }{
  15. {
  16. name: "empty",
  17. in: "goroutine profile: total 0\n",
  18. want: "goroutine profile: total 0",
  19. },
  20. {
  21. name: "single goroutine",
  22. in: `goroutine profile: total 1
  23. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  24. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  25. `,
  26. want: `goroutine profile: total 1
  27. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  28. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  29. `,
  30. },
  31. {
  32. name: "multiple goroutines sorted",
  33. in: `goroutine profile: total 14
  34. 7 @ 0x47bc0e 0x413705 0x4132b2 0x10fda4d 0x483da1
  35. # 0x10fda4c github.com/user/pkg.RoutineA+0x16c pkg/a.go:443
  36. 7 @ 0x47bc0e 0x458e57 0x754927 0x483da1
  37. # 0x754926 net/http.(*persistConn).writeLoop+0xe6 net/http/transport.go:2596
  38. `,
  39. want: `goroutine profile: total 14
  40. 7 @ 0x47bc0e 0x413705 0x4132b2 0x10fda4d 0x483da1
  41. # 0x10fda4c github.com/user/pkg.RoutineA+0x16c pkg/a.go:443
  42. 7 @ 0x47bc0e 0x458e57 0x754927 0x483da1
  43. # 0x754926 net/http.(*persistConn).writeLoop+0xe6 net/http/transport.go:2596
  44. `,
  45. },
  46. }
  47. for _, tt := range tests {
  48. t.Run(tt.name, func(t *testing.T) {
  49. got := string(printGoroutines(parseGoroutines([]byte(tt.in))))
  50. if got != tt.want {
  51. t.Errorf("printGoroutines() = %q, want %q, diff:\n%s", got, tt.want, cmp.Diff(tt.want, got))
  52. }
  53. })
  54. }
  55. }
  56. func TestDiffPprofGoroutines(t *testing.T) {
  57. tests := []struct {
  58. name string
  59. x, y string
  60. want string
  61. }{
  62. {
  63. name: "no difference",
  64. x: `goroutine profile: total 1
  65. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  66. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261`,
  67. y: `goroutine profile: total 1
  68. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  69. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  70. `,
  71. want: "",
  72. },
  73. {
  74. name: "different counts",
  75. x: `goroutine profile: total 1
  76. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  77. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  78. `,
  79. y: `goroutine profile: total 2
  80. 2 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  81. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  82. `,
  83. want: `- goroutine profile: total 1
  84. + goroutine profile: total 2
  85. - 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  86. + 2 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  87. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  88. `,
  89. },
  90. {
  91. name: "new goroutine",
  92. x: `goroutine profile: total 1
  93. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  94. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  95. `,
  96. y: `goroutine profile: total 2
  97. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  98. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  99. 1 @ 0x47bc0e 0x458e57 0x754927 0x483da1
  100. # 0x754926 net/http.(*persistConn).writeLoop+0xe6 net/http/transport.go:2596
  101. `,
  102. want: `- goroutine profile: total 1
  103. + goroutine profile: total 2
  104. + 1 @ 0x47bc0e 0x458e57 0x754927 0x483da1
  105. + # 0x754926 net/http.(*persistConn).writeLoop+0xe6 net/http/transport.go:2596
  106. `,
  107. },
  108. {
  109. name: "removed goroutine",
  110. x: `goroutine profile: total 2
  111. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  112. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  113. 1 @ 0x47bc0e 0x458e57 0x754927 0x483da1
  114. # 0x754926 net/http.(*persistConn).writeLoop+0xe6 net/http/transport.go:2596
  115. `,
  116. y: `goroutine profile: total 1
  117. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  118. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  119. `,
  120. want: `- goroutine profile: total 2
  121. + goroutine profile: total 1
  122. - 1 @ 0x47bc0e 0x458e57 0x754927 0x483da1
  123. - # 0x754926 net/http.(*persistConn).writeLoop+0xe6 net/http/transport.go:2596
  124. `,
  125. },
  126. {
  127. name: "removed many goroutine",
  128. x: `goroutine profile: total 2
  129. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  130. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  131. 1 @ 0x47bc0e 0x458e57 0x754927 0x483da1
  132. # 0x754926 net/http.(*persistConn).writeLoop+0xe6 net/http/transport.go:2596
  133. `,
  134. y: `goroutine profile: total 0`,
  135. want: `- goroutine profile: total 2
  136. + goroutine profile: total 0
  137. - 1 @ 0x47bc0e 0x458e57 0x754927 0x483da1
  138. - # 0x754926 net/http.(*persistConn).writeLoop+0xe6 net/http/transport.go:2596
  139. - 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  140. - # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  141. `,
  142. },
  143. {
  144. name: "invalid input x",
  145. x: "invalid",
  146. y: "goroutine profile: total 0\n",
  147. want: "- invalid\n+ goroutine profile: total 0\n",
  148. },
  149. {
  150. name: "invalid input y",
  151. x: "goroutine profile: total 0\n",
  152. y: "invalid",
  153. want: "- goroutine profile: total 0\n+ invalid\n",
  154. },
  155. }
  156. for _, tt := range tests {
  157. t.Run(tt.name, func(t *testing.T) {
  158. got := diffGoroutines(
  159. parseGoroutines([]byte(tt.x)),
  160. parseGoroutines([]byte(tt.y)),
  161. )
  162. if got != tt.want {
  163. t.Errorf("diffPprofGoroutines() diff:\ngot:\n%s\nwant:\n%s\ndiff (-want +got):\n%s", got, tt.want, cmp.Diff(tt.want, got))
  164. }
  165. })
  166. }
  167. }
  168. func TestParseGoroutines(t *testing.T) {
  169. tests := []struct {
  170. name string
  171. in string
  172. wantHeader string
  173. wantCount int
  174. }{
  175. {
  176. name: "empty profile",
  177. in: "goroutine profile: total 0\n",
  178. wantHeader: "goroutine profile: total 0",
  179. wantCount: 0,
  180. },
  181. {
  182. name: "single goroutine",
  183. in: `goroutine profile: total 1
  184. 1 @ 0x47bc0e 0x458e57 0x847587 0x483da1
  185. # 0x847586 database/sql.(*DB).connectionOpener+0x86 database/sql/sql.go:1261
  186. `,
  187. wantHeader: "goroutine profile: total 1",
  188. wantCount: 1,
  189. },
  190. {
  191. name: "multiple goroutines",
  192. in: `goroutine profile: total 14
  193. 7 @ 0x47bc0e 0x413705 0x4132b2 0x10fda4d 0x483da1
  194. # 0x10fda4c github.com/user/pkg.RoutineA+0x16c pkg/a.go:443
  195. 7 @ 0x47bc0e 0x458e57 0x754927 0x483da1
  196. # 0x754926 net/http.(*persistConn).writeLoop+0xe6 net/http/transport.go:2596
  197. `,
  198. wantHeader: "goroutine profile: total 14",
  199. wantCount: 2,
  200. },
  201. {
  202. name: "invalid format",
  203. in: "invalid",
  204. wantHeader: "invalid",
  205. },
  206. }
  207. for _, tt := range tests {
  208. t.Run(tt.name, func(t *testing.T) {
  209. g := parseGoroutines([]byte(tt.in))
  210. if got := string(g.head); got != tt.wantHeader {
  211. t.Errorf("parseGoroutines() header = %q, want %q", got, tt.wantHeader)
  212. }
  213. if got := len(g.goroutines); got != tt.wantCount {
  214. t.Errorf("parseGoroutines() goroutine count = %d, want %d", got, tt.wantCount)
  215. }
  216. // Verify that the sort field is correctly reversed
  217. for _, g := range g.goroutines {
  218. original := strings.Fields(string(g.header))
  219. sorted := strings.Fields(string(g.sort))
  220. if len(original) != len(sorted) {
  221. t.Errorf("sort field has different number of words: got %d, want %d", len(sorted), len(original))
  222. continue
  223. }
  224. for i := 0; i < len(original); i++ {
  225. if original[i] != sorted[len(sorted)-1-i] {
  226. t.Errorf("sort field word mismatch at position %d: got %q, want %q", i, sorted[len(sorted)-1-i], original[i])
  227. }
  228. }
  229. }
  230. })
  231. }
  232. }