pax_global_header00006660000000000000000000000064133713222220014507gustar00rootroot0000000000000052 comment=9a23578d06a26ec1b47bfc8965bf5e7011df8bd6 leaktest-1.3.0/000077500000000000000000000000001337132222200133245ustar00rootroot00000000000000leaktest-1.3.0/.travis.yml000066400000000000000000000004331337132222200154350ustar00rootroot00000000000000language: go go: - 1.8 - 1.9 - "1.10" - "1.11" - 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.3.0/LICENSE000066400000000000000000000027071337132222200143370ustar00rootroot00000000000000Copyright (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.3.0/README.md000066400000000000000000000035441337132222200146110ustar00rootroot00000000000000## Leaktest [![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) [![Sourcegraph](https://sourcegraph.com/github.com/fortytw2/leaktest/-/badge.svg)](https://sourcegraph.com/github.com/fortytw2/leaktest?badge) [![Documentation](https://godoc.org/github.com/fortytw2/gpt?status.svg)](http://godoc.org/github.com/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.3.0/leaktest.go000066400000000000000000000103671337132222200154760ustar00rootroot00000000000000// 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 leaktest.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" "fmt" "runtime" "sort" "strconv" "strings" "time" ) type goroutine struct { id uint64 stack string } type goroutineByID []*goroutine func (g goroutineByID) Len() int { return len(g) } func (g goroutineByID) Less(i, j int) bool { return g[i].id < g[j].id } func (g goroutineByID) Swap(i, j int) { g[i], g[j] = g[j], g[i] } func interestingGoroutine(g string) (*goroutine, error) { sl := strings.SplitN(g, "\n", 2) if len(sl) != 2 { return nil, fmt.Errorf("error parsing stack: %q", g) } stack := strings.TrimSpace(sl[1]) if strings.HasPrefix(stack, "testing.RunTests") { return nil, nil } if stack == "" || // Ignore HTTP keep alives strings.Contains(stack, ").readLoop(") || strings.Contains(stack, ").writeLoop(") || // 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") { return nil, nil } // Parse the goroutine's ID from the header line. h := strings.SplitN(sl[0], " ", 3) if len(h) < 3 { return nil, fmt.Errorf("error parsing stack header: %q", sl[0]) } id, err := strconv.ParseUint(h[1], 10, 64) if err != nil { return nil, fmt.Errorf("error parsing goroutine id: %s", err) } return &goroutine{id: id, stack: strings.TrimSpace(g)}, nil } // interestingGoroutines returns all goroutines we care about for the purpose // of leak checking. It excludes testing or runtime ones. func interestingGoroutines(t ErrorReporter) []*goroutine { buf := make([]byte, 2<<20) buf = buf[:runtime.Stack(buf, true)] var gs []*goroutine for _, g := range strings.Split(string(buf), "\n\n") { gr, err := interestingGoroutine(g) if err != nil { t.Errorf("leaktest: %s", err) continue } else if gr == nil { continue } gs = append(gs, gr) } sort.Sort(goroutineByID(gs)) return gs } // 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.WithCancel(context.Background()) fn := CheckContext(ctx, t) return func() { timer := time.AfterFunc(dur, cancel) fn() // Remember to clean up the timer and context timer.Stop() 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[uint64]bool{} for _, g := range interestingGoroutines(t) { orig[g.id] = 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(t) { if !orig[g.id] { leaked = append(leaked, g.stack) } } 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.3.0/leaktest_test.go000066400000000000000000000120241337132222200165250ustar00rootroot00000000000000package leaktest import ( "context" "errors" "fmt" "net/http" "net/http/httptest" "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...) } // Client for the TestServer var testServer *httptest.Server func TestCheck(t *testing.T) { leakyFuncs := []struct { f func() name string expectLeak bool }{ { name: "Infinite for loop", expectLeak: true, f: func() { for { time.Sleep(time.Second) } }, }, { name: "Select on a channel not referenced by other goroutines.", expectLeak: true, f: func() { c := make(chan struct{}) <-c }, }, { name: "Blocked select on channels not referenced by other goroutines.", expectLeak: true, f: func() { c := make(chan struct{}) c2 := make(chan struct{}) select { case <-c: case c2 <- struct{}{}: } }, }, { name: "Blocking wait on sync.Mutex that isn't referenced by other goroutines.", expectLeak: true, f: func() { var mu sync.Mutex mu.Lock() mu.Lock() }, }, { name: "Blocking wait on sync.RWMutex that isn't referenced by other goroutines.", expectLeak: true, f: func() { var mu sync.RWMutex mu.RLock() mu.Lock() }, }, { name: "HTTP Client with KeepAlive Disabled.", expectLeak: false, f: func() { tr := &http.Transport{ DisableKeepAlives: true, } client := &http.Client{Transport: tr} _, err := client.Get(testServer.URL) if err != nil { t.Error(err) } }, }, { name: "HTTP Client with KeepAlive Enabled.", expectLeak: true, f: func() { tr := &http.Transport{ DisableKeepAlives: false, } client := &http.Client{Transport: tr} _, err := client.Get(testServer.URL) if err != nil { t.Error(err) } }, }, } // Start our keep alive server for keep alive tests ctx, cancel := context.WithCancel(context.Background()) defer cancel() testServer = startKeepAliveEnabledServer(ctx) // 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 _, leakyTestcase := range leakyFuncs { t.Run(leakyTestcase.name, func(t *testing.T) { checker := &testReporter{} snapshot := CheckTimeout(checker, time.Second) go leakyTestcase.f() snapshot() if !checker.failed && leakyTestcase.expectLeak { t.Error("didn't catch sleeping goroutine") } if checker.failed && !leakyTestcase.expectLeak { t.Error("got leak but didn't expect it") } }) } } // 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 299 [IO wait]:\nnet/http.(*persistConn).readLoop(0xc420556240)", err: nil, }, { 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) } } } leaktest-1.3.0/leaktest_utils_test.go000066400000000000000000000011361337132222200177470ustar00rootroot00000000000000package leaktest import ( "context" "net/http" "net/http/httptest" "time" ) func index() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) } func startKeepAliveEnabledServer(ctx context.Context) *httptest.Server { server := httptest.NewUnstartedServer(index()) server.Config.ReadTimeout = 5 * time.Second server.Config.WriteTimeout = 10 * time.Second server.Config.IdleTimeout = 15 * time.Second server.Config.SetKeepAlivesEnabled(true) server.Start() go func() { <-ctx.Done() server.Close() }() return server }