go-common/vendor/github.com/fortytw2/leaktest/leaktest_test.go

166 lines
3.6 KiB
Go

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)
}
}
}