scissors_test.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. package common
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. "github.com/prometheus/client_golang/prometheus"
  11. dto "github.com/prometheus/client_model/go"
  12. )
  13. func getCounterValue(metric *prometheus.CounterVec, runnableName string) float64 {
  14. var m = &dto.Metric{}
  15. if err := metric.WithLabelValues(runnableName).Write(m); err != nil {
  16. return 0
  17. }
  18. return m.Counter.GetValue()
  19. }
  20. func throwNil(ctx context.Context) error {
  21. var x *int = nil
  22. //nolint:govet // This test function is specifically checking for nil errors, so being flagged for `nilness` is expected
  23. *x = 5
  24. return nil
  25. }
  26. func runTest(t *testing.T, ctx context.Context, testCase int) (result error) {
  27. t.Helper()
  28. defer func() {
  29. if r := recover(); r != nil {
  30. switch x := r.(type) {
  31. case string:
  32. result = errors.New(x)
  33. case error:
  34. result = x
  35. default:
  36. result = fmt.Errorf("unknown panic in runTest/%d", testCase)
  37. }
  38. }
  39. }()
  40. errC := make(chan error)
  41. switch testCase {
  42. case 0:
  43. _ = throwNil(ctx) // fall into defer above
  44. case 1:
  45. RunWithScissors(ctx, errC, "test1Thread", throwNil)
  46. case 2:
  47. RunWithScissors(ctx, errC, "test2Thread", func(ctx context.Context) error {
  48. <-ctx.Done()
  49. return nil
  50. })
  51. _ = throwNil(ctx)
  52. case 3:
  53. go func() { _ = throwNil(ctx) }() // uncatchable
  54. }
  55. select {
  56. case <-ctx.Done():
  57. return ctx.Err()
  58. case err := <-errC:
  59. return err
  60. }
  61. }
  62. func TestSupervisor(t *testing.T) {
  63. for i := 0; i < 3; i++ {
  64. t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
  65. rootCtx := context.Background()
  66. ctx, fn := context.WithCancel(rootCtx)
  67. err := runTest(t, ctx, i)
  68. switch i {
  69. case 0:
  70. assert.EqualError(t, err, "runtime error: invalid memory address or nil pointer dereference")
  71. case 1:
  72. assert.EqualError(t, err, "test1Thread: runtime error: invalid memory address or nil pointer dereference")
  73. case 2:
  74. assert.EqualError(t, err, "runtime error: invalid memory address or nil pointer dereference")
  75. }
  76. fn()
  77. },
  78. )
  79. }
  80. }
  81. func TestRunWithScissorsCleanExit(t *testing.T) {
  82. ctx := context.Background()
  83. errC := make(chan error)
  84. itRan := make(chan bool, 1)
  85. RunWithScissors(ctx, errC, "TestRunWithScissorsCleanExit", func(ctx context.Context) error {
  86. itRan <- true
  87. return nil
  88. })
  89. shouldHaveRun := <-itRan
  90. require.Equal(t, true, shouldHaveRun)
  91. // Need to wait a bit to make sure the scissors code completes without hanging.
  92. time.Sleep(100 * time.Millisecond)
  93. assert.Equal(t, 0.0, getCounterValue(ScissorsErrorsCaught, "TestRunWithScissorsCleanExit"))
  94. assert.Equal(t, 0.0, getCounterValue(ScissorsPanicsCaught, "TestRunWithScissorsCleanExit"))
  95. }
  96. func TestRunWithScissorsPanicReturned(t *testing.T) {
  97. ctx := context.Background()
  98. errC := make(chan error)
  99. itRan := make(chan bool, 1)
  100. RunWithScissors(ctx, errC, "TestRunWithScissorsPanicReturned", func(ctx context.Context) error {
  101. itRan <- true
  102. panic("Some random panic")
  103. })
  104. var err error
  105. select {
  106. case <-ctx.Done():
  107. break
  108. case err = <-errC:
  109. break
  110. }
  111. shouldHaveRun := <-itRan
  112. require.Equal(t, true, shouldHaveRun)
  113. assert.Error(t, err)
  114. assert.Equal(t, "TestRunWithScissorsPanicReturned: Some random panic", err.Error())
  115. assert.Equal(t, 0.0, getCounterValue(ScissorsErrorsCaught, "TestRunWithScissorsPanicReturned"))
  116. assert.Equal(t, 1.0, getCounterValue(ScissorsPanicsCaught, "TestRunWithScissorsPanicReturned"))
  117. }
  118. func TestRunWithScissorsPanicDoesNotBlockWhenNoListener(t *testing.T) {
  119. ctx := context.Background()
  120. errC := make(chan error)
  121. itRan := make(chan bool, 1)
  122. RunWithScissors(ctx, errC, "TestRunWithScissorsPanicDoesNotBlockWhenNoListener", func(ctx context.Context) error {
  123. itRan <- true
  124. panic("Some random panic")
  125. })
  126. shouldHaveRun := <-itRan
  127. require.Equal(t, true, shouldHaveRun)
  128. // Need to wait a bit to make sure the scissors code completes without hanging.
  129. time.Sleep(100 * time.Millisecond)
  130. assert.Equal(t, 0.0, getCounterValue(ScissorsErrorsCaught, "TestRunWithScissorsPanicDoesNotBlockWhenNoListener"))
  131. assert.Equal(t, 1.0, getCounterValue(ScissorsPanicsCaught, "TestRunWithScissorsPanicDoesNotBlockWhenNoListener"))
  132. }
  133. func TestRunWithScissorsErrorReturned(t *testing.T) {
  134. ctx := context.Background()
  135. errC := make(chan error)
  136. itRan := make(chan bool, 1)
  137. RunWithScissors(ctx, errC, "TestRunWithScissorsErrorReturned", func(ctx context.Context) error {
  138. itRan <- true
  139. return fmt.Errorf("Some random error")
  140. })
  141. var err error
  142. select {
  143. case <-ctx.Done():
  144. break
  145. case err = <-errC:
  146. break
  147. }
  148. shouldHaveRun := <-itRan
  149. require.Equal(t, true, shouldHaveRun)
  150. assert.Error(t, err)
  151. assert.Equal(t, "Some random error", err.Error())
  152. assert.Equal(t, 1.0, getCounterValue(ScissorsErrorsCaught, "TestRunWithScissorsErrorReturned"))
  153. assert.Equal(t, 0.0, getCounterValue(ScissorsPanicsCaught, "TestRunWithScissorsErrorReturned"))
  154. }
  155. func TestRunWithScissorsErrorDoesNotBlockWhenNoListener(t *testing.T) {
  156. ctx := context.Background()
  157. errC := make(chan error)
  158. itRan := make(chan bool, 1)
  159. RunWithScissors(ctx, errC, "TestRunWithScissorsErrorDoesNotBlockWhenNoListener", func(ctx context.Context) error {
  160. itRan <- true
  161. return fmt.Errorf("Some random error")
  162. })
  163. shouldHaveRun := <-itRan
  164. require.Equal(t, true, shouldHaveRun)
  165. // Need to wait a bit to make sure the scissors code completes without hanging.
  166. time.Sleep(100 * time.Millisecond)
  167. assert.Equal(t, 1.0, getCounterValue(ScissorsErrorsCaught, "TestRunWithScissorsErrorDoesNotBlockWhenNoListener"))
  168. assert.Equal(t, 0.0, getCounterValue(ScissorsPanicsCaught, "TestRunWithScissorsErrorDoesNotBlockWhenNoListener"))
  169. }
  170. func TestStartRunnable_CleanExit(t *testing.T) {
  171. ctx := context.Background()
  172. errC := make(chan error)
  173. itRan := make(chan bool, 1)
  174. StartRunnable(ctx, errC, true, "TestStartRunnable_CleanExit", func(ctx context.Context) error {
  175. itRan <- true
  176. return nil
  177. })
  178. shouldHaveRun := <-itRan
  179. require.Equal(t, true, shouldHaveRun)
  180. // Need to wait a bit to make sure the scissors code completes without hanging.
  181. time.Sleep(100 * time.Millisecond)
  182. assert.Equal(t, 0.0, getCounterValue(ScissorsErrorsCaught, "TestStartRunnable_CleanExit"))
  183. assert.Equal(t, 0.0, getCounterValue(ScissorsPanicsCaught, "TestStartRunnable_CleanExit"))
  184. }
  185. func TestStartRunnable_OnError(t *testing.T) {
  186. ctx := context.Background()
  187. errC := make(chan error)
  188. itRan := make(chan bool, 1)
  189. StartRunnable(ctx, errC, true, "TestStartRunnable_OnError", func(ctx context.Context) error {
  190. itRan <- true
  191. return fmt.Errorf("Some random error")
  192. })
  193. var err error
  194. select {
  195. case <-ctx.Done():
  196. break
  197. case err = <-errC:
  198. break
  199. }
  200. shouldHaveRun := <-itRan
  201. require.Equal(t, true, shouldHaveRun)
  202. assert.Error(t, err)
  203. assert.Equal(t, "Some random error", err.Error())
  204. assert.Equal(t, 1.0, getCounterValue(ScissorsErrorsCaught, "TestStartRunnable_OnError"))
  205. assert.Equal(t, 0.0, getCounterValue(ScissorsPanicsCaught, "TestStartRunnable_OnError"))
  206. }
  207. func TestStartRunnable_DontCatchPanics_OnPanic(t *testing.T) {
  208. ctx := context.Background()
  209. errC := make(chan error)
  210. itRan := make(chan bool, 1)
  211. itPanicked := make(chan bool, 1)
  212. // We can't use StartRunnable() because we cannot test for a panic in another go routine.
  213. // This verifies that startRunnable() lets the panic through so it gets caught here, allowing us to test for it.
  214. func() {
  215. defer func() {
  216. if r := recover(); r != nil {
  217. itPanicked <- true
  218. }
  219. itRan <- true
  220. }()
  221. startRunnable(ctx, errC, "TestStartRunnable_DontCatchPanics_OnPanic", func(ctx context.Context) error {
  222. panic("Some random panic")
  223. })
  224. }()
  225. var shouldHaveRun bool
  226. select {
  227. case <-ctx.Done():
  228. break
  229. case shouldHaveRun = <-itRan:
  230. break
  231. }
  232. require.Equal(t, true, shouldHaveRun)
  233. require.Equal(t, 1, len(itPanicked))
  234. shouldHavePanicked := <-itPanicked
  235. require.Equal(t, true, shouldHavePanicked)
  236. assert.Equal(t, 0.0, getCounterValue(ScissorsErrorsCaught, "TestStartRunnable_DontCatchPanics_OnPanic"))
  237. assert.Equal(t, 0.0, getCounterValue(ScissorsPanicsCaught, "TestStartRunnable_DontCatchPanics_OnPanic"))
  238. }
  239. func TestStartRunnable_CatchPanics_OnPanic(t *testing.T) {
  240. ctx := context.Background()
  241. errC := make(chan error)
  242. itRan := make(chan bool, 1)
  243. StartRunnable(ctx, errC, true, "TestStartRunnable_CatchPanics_OnPanic", func(ctx context.Context) error {
  244. itRan <- true
  245. panic("Some random panic")
  246. })
  247. var err error
  248. select {
  249. case <-ctx.Done():
  250. break
  251. case err = <-errC:
  252. break
  253. }
  254. shouldHaveRun := <-itRan
  255. require.Equal(t, true, shouldHaveRun)
  256. assert.Error(t, err)
  257. assert.Equal(t, "TestStartRunnable_CatchPanics_OnPanic: Some random panic", err.Error())
  258. assert.Equal(t, 0.0, getCounterValue(ScissorsErrorsCaught, "TestStartRunnable_CatchPanics_OnPanic"))
  259. assert.Equal(t, 1.0, getCounterValue(ScissorsPanicsCaught, "TestStartRunnable_CatchPanics_OnPanic"))
  260. }
  261. func TestStartRunnable_DoesNotBlockWhenNoListener(t *testing.T) {
  262. ctx := context.Background()
  263. errC := make(chan error)
  264. itRan := make(chan bool, 1)
  265. StartRunnable(ctx, errC, true, "TestStartRunnable_DoesNotBlockWhenNoListener", func(ctx context.Context) error {
  266. itRan <- true
  267. panic("Some random panic")
  268. })
  269. shouldHaveRun := <-itRan
  270. require.Equal(t, true, shouldHaveRun)
  271. // Need to wait a bit to make sure the scissors code completes without hanging.
  272. time.Sleep(100 * time.Millisecond)
  273. assert.Equal(t, 0.0, getCounterValue(ScissorsErrorsCaught, "TestStartRunnable_DoesNotBlockWhenNoListener"))
  274. assert.Equal(t, 1.0, getCounterValue(ScissorsPanicsCaught, "TestStartRunnable_DoesNotBlockWhenNoListener"))
  275. }