leaktest_test.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package leaktest
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "sync"
  7. "testing"
  8. "time"
  9. )
  10. type testReporter struct {
  11. failed bool
  12. msg string
  13. }
  14. func (tr *testReporter) Errorf(format string, args ...interface{}) {
  15. tr.failed = true
  16. tr.msg = fmt.Sprintf(format, args...)
  17. }
  18. var leakyFuncs = []func(){
  19. // Infinite for loop
  20. func() {
  21. for {
  22. time.Sleep(time.Second)
  23. }
  24. },
  25. // Select on a channel not referenced by other goroutines.
  26. func() {
  27. c := make(chan struct{})
  28. <-c
  29. },
  30. // Blocked select on channels not referenced by other goroutines.
  31. func() {
  32. c := make(chan struct{})
  33. c2 := make(chan struct{})
  34. select {
  35. case <-c:
  36. case c2 <- struct{}{}:
  37. }
  38. },
  39. // Blocking wait on sync.Mutex that isn't referenced by other goroutines.
  40. func() {
  41. var mu sync.Mutex
  42. mu.Lock()
  43. mu.Lock()
  44. },
  45. // Blocking wait on sync.RWMutex that isn't referenced by other goroutines.
  46. func() {
  47. var mu sync.RWMutex
  48. mu.RLock()
  49. mu.Lock()
  50. },
  51. func() {
  52. var mu sync.Mutex
  53. mu.Lock()
  54. c := sync.NewCond(&mu)
  55. c.Wait()
  56. },
  57. }
  58. func TestCheck(t *testing.T) {
  59. // this works because the running goroutine is left running at the
  60. // start of the next test case - so the previous leaks don't affect the
  61. // check for the next one
  62. for i, fn := range leakyFuncs {
  63. checker := &testReporter{}
  64. snapshot := CheckTimeout(checker, time.Second)
  65. go fn()
  66. snapshot()
  67. if !checker.failed {
  68. t.Errorf("didn't catch sleeping goroutine, test #%d", i)
  69. }
  70. }
  71. }
  72. // TestSlowTest verifies that the timeout works on slow tests: it should
  73. // be based on time after the test finishes rather than time after the test's
  74. // start.
  75. func TestSlowTest(t *testing.T) {
  76. defer CheckTimeout(t, 1000 * time.Millisecond)()
  77. go time.Sleep(1500 * time.Millisecond)
  78. time.Sleep(750 * time.Millisecond)
  79. }
  80. func TestEmptyLeak(t *testing.T) {
  81. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  82. defer cancel()
  83. defer CheckContext(ctx, t)()
  84. time.Sleep(time.Second)
  85. }
  86. // TestChangingStackTrace validates that a change in a preexisting goroutine's
  87. // stack is not detected as a leaked goroutine.
  88. func TestChangingStackTrace(t *testing.T) {
  89. started := make(chan struct{})
  90. c1 := make(chan struct{})
  91. c2 := make(chan struct{})
  92. defer close(c2)
  93. go func() {
  94. close(started)
  95. <-c1
  96. <-c2
  97. }()
  98. <-started
  99. func() {
  100. defer CheckTimeout(t, time.Second)()
  101. close(c1)
  102. }()
  103. }
  104. func TestInterestingGoroutine(t *testing.T) {
  105. s := "goroutine 123 [running]:\nmain.main()"
  106. gr, err := interestingGoroutine(s)
  107. if err != nil {
  108. t.Errorf("unexpected error: %s", err)
  109. }
  110. if gr.id != 123 {
  111. t.Errorf("goroutine id = %d; want %d", gr.id, 123)
  112. }
  113. if gr.stack != s {
  114. t.Errorf("goroutine stack = %q; want %q", gr.stack, s)
  115. }
  116. stacks := []struct {
  117. stack string
  118. err error
  119. }{
  120. {
  121. stack: "goroutine 123 [running]:",
  122. err: errors.New(`error parsing stack: "goroutine 123 [running]:"`),
  123. },
  124. {
  125. stack: "goroutine 123 [running]:\ntesting.RunTests",
  126. err: nil,
  127. },
  128. {
  129. stack: "goroutine 123 [running]:\nfoo\nbar\nruntime.goexit\nbaz",
  130. err: nil,
  131. },
  132. {
  133. stack: "goroutine 123:\nmain.main()",
  134. err: errors.New(`error parsing stack header: "goroutine 123:"`),
  135. },
  136. {
  137. stack: "goroutine NaN [running]:\nmain.main()",
  138. err: errors.New(`error parsing goroutine id: strconv.ParseUint: parsing "NaN": invalid syntax`),
  139. },
  140. }
  141. for i, s := range stacks {
  142. gr, err := interestingGoroutine(s.stack)
  143. if s.err == nil && err != nil {
  144. t.Errorf("%d: error = %v; want nil", i, err)
  145. } else if s.err != nil && (err == nil || err.Error() != s.err.Error()) {
  146. t.Errorf("%d: error = %v; want %s", i, err, s.err)
  147. }
  148. if gr != nil {
  149. t.Errorf("%d: gr = %v; want nil", i, gr)
  150. }
  151. }
  152. }