pax_global_header00006660000000000000000000000064131070455210014510gustar00rootroot0000000000000052 comment=7dad53304f9614c1c365755c1176a8e876fee3e8 leaktest-1.1.0/000077500000000000000000000000001310704552100133235ustar00rootroot00000000000000leaktest-1.1.0/.travis.yml000066400000000000000000000004071310704552100154350ustar00rootroot00000000000000language: go go: - 1.7 - 1.8 - tip script: - go test -v -race -parallel 5 -coverprofile=coverage.txt -covermode=atomic ./ - go test github.com/fortytw2/leaktest -run ^TestEmptyLeak$ before_install: - pip install --user codecov after_success: - codecov leaktest-1.1.0/LICENSE000066400000000000000000000027071310704552100143360ustar00rootroot00000000000000Copyright (c) 2012 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. leaktest-1.1.0/README.md000066400000000000000000000031361310704552100146050ustar00rootroot00000000000000Leaktest [![Build Status](https://travis-ci.org/fortytw2/leaktest.svg?branch=master)](https://travis-ci.org/fortytw2/leaktest) [![codecov](https://codecov.io/gh/fortytw2/leaktest/branch/master/graph/badge.svg)](https://codecov.io/gh/fortytw2/leaktest) ------ Refactored, tested variant of the goroutine leak detector found in both `net/http` tests and the `cockroachdb` source tree. Takes a snapshot of running goroutines at the start of a test, and at the end - compares the two and *voila*. Ignores runtime/sys goroutines. Doesn't play nice with `t.Parallel()` right now, but there are plans to do so. ### Installation Go 1.7+ ``` go get -u github.com/fortytw2/leaktest ``` Go 1.5/1.6 need to use the tag `v1.0.0`, as newer versions depend on `context.Context`. ### Example These tests fail, because they leak a goroutine ```go // Default "Check" will poll for 5 seconds to check that all // goroutines are cleaned up func TestPool(t *testing.T) { defer leaktest.Check(t)() go func() { for { time.Sleep(time.Second) } }() } // Helper function to timeout after X duration func TestPoolTimeout(t *testing.T) { defer leaktest.CheckTimeout(t, time.Second)() go func() { for { time.Sleep(time.Second) } }() } // Use Go 1.7+ context.Context for cancellation func TestPoolContext(t *testing.T) { ctx, _ := context.WithTimeout(context.Background(), time.Second) defer leaktest.CheckContext(ctx, t)() go func() { for { time.Sleep(time.Second) } }() } ``` LICENSE ------ Same BSD-style as Go, see LICENSE leaktest-1.1.0/leaktest.go000066400000000000000000000061371310704552100154750ustar00rootroot00000000000000// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package leaktest provides tools to detect leaked goroutines in tests. // To use it, call "defer util.Check(t)()" at the beginning of each // test that may use goroutines. // copied out of the cockroachdb source tree with slight modifications to be // more re-useable package leaktest import ( "context" "runtime" "sort" "strings" "time" ) // interestingGoroutines returns all goroutines we care about for the purpose // of leak checking. It excludes testing or runtime ones. func interestingGoroutines() (gs []string) { buf := make([]byte, 2<<20) buf = buf[:runtime.Stack(buf, true)] for _, g := range strings.Split(string(buf), "\n\n") { sl := strings.SplitN(g, "\n", 2) if len(sl) != 2 { continue } stack := strings.TrimSpace(sl[1]) if strings.HasPrefix(stack, "testing.RunTests") { continue } if stack == "" || // Below are the stacks ignored by the upstream leaktest code. strings.Contains(stack, "testing.Main(") || strings.Contains(stack, "testing.(*T).Run(") || strings.Contains(stack, "runtime.goexit") || strings.Contains(stack, "created by runtime.gc") || strings.Contains(stack, "interestingGoroutines") || strings.Contains(stack, "runtime.MHeap_Scavenger") || strings.Contains(stack, "signal.signal_recv") || strings.Contains(stack, "sigterm.handler") || strings.Contains(stack, "runtime_mcall") || strings.Contains(stack, "goroutine in C code") { continue } gs = append(gs, strings.TrimSpace(g)) } sort.Strings(gs) return } // ErrorReporter is a tiny subset of a testing.TB to make testing not such a // massive pain type ErrorReporter interface { Errorf(format string, args ...interface{}) } // Check snapshots the currently-running goroutines and returns a // function to be run at the end of tests to see whether any // goroutines leaked, waiting up to 5 seconds in error conditions func Check(t ErrorReporter) func() { return CheckTimeout(t, 5*time.Second) } // CheckTimeout is the same as Check, but with a configurable timeout func CheckTimeout(t ErrorReporter, dur time.Duration) func() { ctx, cancel := context.WithTimeout(context.Background(), dur) fn := CheckContext(ctx, t) return func() { fn() cancel() } } // CheckContext is the same as Check, but uses a context.Context for // cancellation and timeout control func CheckContext(ctx context.Context, t ErrorReporter) func() { orig := map[string]bool{} for _, g := range interestingGoroutines() { orig[g] = true } return func() { var leaked []string for { select { case <-ctx.Done(): t.Errorf("leaktest: timed out checking goroutines") default: leaked = make([]string, 0) for _, g := range interestingGoroutines() { if !orig[g] { leaked = append(leaked, g) } } if len(leaked) == 0 { return } // don't spin needlessly time.Sleep(time.Millisecond * 50) continue } break } for _, g := range leaked { t.Errorf("leaktest: leaked goroutine: %v", g) } } } leaktest-1.1.0/leaktest_test.go000066400000000000000000000031621310704552100165270ustar00rootroot00000000000000package leaktest import ( "context" "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{}, 0) select { case <-c: } }, // Blocked select on channels not referenced by other goroutines. func() { c := make(chan struct{}, 0) c2 := make(chan struct{}, 0) 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) } } } func TestEmptyLeak(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() defer CheckContext(ctx, t)() time.Sleep(time.Second) }