forked from cockroachdb/cockroach
-
Notifications
You must be signed in to change notification settings - Fork 0
/
leaktest.go
137 lines (129 loc) · 4.18 KB
/
leaktest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// 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.AfterTest(t)" at the beginning of each
// test that may use goroutines, and add a TestMain function for the package
// which calls leaktest.TestMainWithLeakCheck.
package leaktest
import (
"fmt"
"net/http"
"os"
"runtime"
"sort"
"strings"
"testing"
"time"
)
// TestMainWithLeakCheck is an implementation of TestMain which verifies that
// there are no leaked goroutines at the end of the run (except those created
// by the system which are on a whitelist). Usage:
//
//
// // Adjust the relative path as needed.
// //go:generate ../util/leaktest/add-leaktest.sh *_test.go
//
// func TestMain(m *testing.M) {
// leaktest.TestMainWithLeakCheck(m)
// }
func TestMainWithLeakCheck(m *testing.M) {
v := m.Run()
if v == 0 && goroutineLeaked() {
os.Exit(1)
}
os.Exit(v)
}
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 stack == "" ||
strings.Contains(stack, "created by net.startServer") ||
strings.Contains(stack, "created by testing.RunTests") ||
strings.Contains(stack, "closeWriteAndWait") ||
strings.Contains(stack, "testing.Main(") ||
// These only show up with GOTRACEBACK=2; Issue 5005 (comment 28)
strings.Contains(stack, "runtime.goexit") ||
strings.Contains(stack, "created by runtime.gc") ||
strings.Contains(stack, "github.com/cockroachdb/cockroach/util/leaktest.interestingGoroutines") ||
strings.Contains(stack, "runtime.MHeap_Scavenger") ||
strings.Contains(stack, "github.com/cockroachdb/cockroach/util/log.init") {
continue
}
gs = append(gs, stack)
}
sort.Strings(gs)
return
}
// Verify the other tests didn't leave any goroutines running.
func goroutineLeaked() bool {
if testing.Short() {
// not counting goroutines for leakage in -short mode
return false
}
var stackCount map[string]int
for i := 0; i < 8; i++ {
gs := interestingGoroutines()
n := 0
stackCount = make(map[string]int)
for _, g := range gs {
stackCount[g]++
n++
}
if n == 0 {
return false
}
time.Sleep(10 * time.Millisecond)
}
fmt.Fprintf(os.Stderr, "Too many goroutines running after tests.\n")
for stack, count := range stackCount {
fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack)
}
return true
}
// AfterTest should be called (generally with "defer leaktest.AfterTest(t)")
// from each test which uses goroutines. This waits for all goroutines
// on a blacklist to terminate and provides more precise error reporting
// than TestMainWithLeakCheck alone.
func AfterTest(t testing.TB) {
http.DefaultTransport.(*http.Transport).CloseIdleConnections()
if testing.Short() {
return
}
var bad string
badSubstring := map[string]string{
").readLoop(": "a Transport",
").writeLoop(": "a Transport",
"created by net/http/httptest.(*Server).Start": "an httptest.Server",
"timeoutHandler": "a TimeoutHandler",
"net.(*netFD).connect(": "a timing out dial",
").noteClientGone(": "a closenotifier sender",
"created by net/rpc.NewClientWithCodec": "an rpc client",
"created by net/rpc.(*Server.ServeCodec)": "an rpc server connection",
"(*Store).Start": "a store",
"(*Range).AddCmd": "a range command",
}
var stacks string
for i := 0; i < 8; i++ {
bad = ""
stacks = strings.Join(interestingGoroutines(), "\n\n")
for substr, what := range badSubstring {
if strings.Contains(stacks, substr) {
bad = what
}
}
if bad == "" {
return
}
// Bad stuff found, but goroutines might just still be
// shutting down, so give it some time.
time.Sleep(10 * time.Millisecond)
}
t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks)
}