123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- package leaktest
- import (
- "context"
- "errors"
- "fmt"
- "sync"
- "testing"
- "time"
- )
- type testReporter struct {
- failed bool
- msg string
- }
- func (tr *testReporter) Errorf(format string, args ...interface{}) {
- tr.failed = true
- tr.msg = fmt.Sprintf(format, args...)
- }
- var leakyFuncs = []func(){
- // Infinite for loop
- func() {
- for {
- time.Sleep(time.Second)
- }
- },
- // Select on a channel not referenced by other goroutines.
- func() {
- c := make(chan struct{})
- <-c
- },
- // Blocked select on channels not referenced by other goroutines.
- func() {
- c := make(chan struct{})
- c2 := make(chan struct{})
- select {
- case <-c:
- case c2 <- struct{}{}:
- }
- },
- // Blocking wait on sync.Mutex that isn't referenced by other goroutines.
- func() {
- var mu sync.Mutex
- mu.Lock()
- mu.Lock()
- },
- // Blocking wait on sync.RWMutex that isn't referenced by other goroutines.
- func() {
- var mu sync.RWMutex
- mu.RLock()
- mu.Lock()
- },
- func() {
- var mu sync.Mutex
- mu.Lock()
- c := sync.NewCond(&mu)
- c.Wait()
- },
- }
- func TestCheck(t *testing.T) {
- // this works because the running goroutine is left running at the
- // start of the next test case - so the previous leaks don't affect the
- // check for the next one
- for i, fn := range leakyFuncs {
- checker := &testReporter{}
- snapshot := CheckTimeout(checker, time.Second)
- go fn()
- snapshot()
- if !checker.failed {
- t.Errorf("didn't catch sleeping goroutine, test #%d", i)
- }
- }
- }
- // TestSlowTest verifies that the timeout works on slow tests: it should
- // be based on time after the test finishes rather than time after the test's
- // start.
- func TestSlowTest(t *testing.T) {
- defer CheckTimeout(t, 1000 * time.Millisecond)()
- go time.Sleep(1500 * time.Millisecond)
- time.Sleep(750 * time.Millisecond)
- }
- func TestEmptyLeak(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- defer CheckContext(ctx, t)()
- time.Sleep(time.Second)
- }
- // TestChangingStackTrace validates that a change in a preexisting goroutine's
- // stack is not detected as a leaked goroutine.
- func TestChangingStackTrace(t *testing.T) {
- started := make(chan struct{})
- c1 := make(chan struct{})
- c2 := make(chan struct{})
- defer close(c2)
- go func() {
- close(started)
- <-c1
- <-c2
- }()
- <-started
- func() {
- defer CheckTimeout(t, time.Second)()
- close(c1)
- }()
- }
- func TestInterestingGoroutine(t *testing.T) {
- s := "goroutine 123 [running]:\nmain.main()"
- gr, err := interestingGoroutine(s)
- if err != nil {
- t.Errorf("unexpected error: %s", err)
- }
- if gr.id != 123 {
- t.Errorf("goroutine id = %d; want %d", gr.id, 123)
- }
- if gr.stack != s {
- t.Errorf("goroutine stack = %q; want %q", gr.stack, s)
- }
- stacks := []struct {
- stack string
- err error
- }{
- {
- stack: "goroutine 123 [running]:",
- err: errors.New(`error parsing stack: "goroutine 123 [running]:"`),
- },
- {
- stack: "goroutine 123 [running]:\ntesting.RunTests",
- err: nil,
- },
- {
- stack: "goroutine 123 [running]:\nfoo\nbar\nruntime.goexit\nbaz",
- err: nil,
- },
- {
- stack: "goroutine 123:\nmain.main()",
- err: errors.New(`error parsing stack header: "goroutine 123:"`),
- },
- {
- stack: "goroutine NaN [running]:\nmain.main()",
- err: errors.New(`error parsing goroutine id: strconv.ParseUint: parsing "NaN": invalid syntax`),
- },
- }
- for i, s := range stacks {
- gr, err := interestingGoroutine(s.stack)
- if s.err == nil && err != nil {
- t.Errorf("%d: error = %v; want nil", i, err)
- } else if s.err != nil && (err == nil || err.Error() != s.err.Error()) {
- t.Errorf("%d: error = %v; want %s", i, err, s.err)
- }
- if gr != nil {
- t.Errorf("%d: gr = %v; want nil", i, gr)
- }
- }
- }
|