go.etcd.io/etcd@v3.3.27+incompatible/pkg/testutil/leak.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package testutil 6 7 import ( 8 "fmt" 9 "net/http" 10 "os" 11 "regexp" 12 "runtime" 13 "sort" 14 "strings" 15 "testing" 16 "time" 17 ) 18 19 /* 20 CheckLeakedGoroutine verifies tests do not leave any leaky 21 goroutines. It returns true when there are goroutines still 22 running(leaking) after all tests. 23 24 import "github.com/coreos/etcd/pkg/testutil" 25 26 func TestMain(m *testing.M) { 27 v := m.Run() 28 if v == 0 && testutil.CheckLeakedGoroutine() { 29 os.Exit(1) 30 } 31 os.Exit(v) 32 } 33 34 func TestSample(t *testing.T) { 35 defer testutil.AfterTest(t) 36 ... 37 } 38 39 */ 40 func CheckLeakedGoroutine() bool { 41 if testing.Short() { 42 // not counting goroutines for leakage in -short mode 43 return false 44 } 45 gs := interestingGoroutines() 46 if len(gs) == 0 { 47 return false 48 } 49 50 stackCount := make(map[string]int) 51 re := regexp.MustCompile(`\(0[0-9a-fx, ]*\)`) 52 for _, g := range gs { 53 // strip out pointer arguments in first function of stack dump 54 normalized := string(re.ReplaceAll([]byte(g), []byte("(...)"))) 55 stackCount[normalized]++ 56 } 57 58 fmt.Fprintf(os.Stderr, "Too many goroutines running after all test(s).\n") 59 for stack, count := range stackCount { 60 fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack) 61 } 62 return true 63 } 64 65 // CheckAfterTest returns an error if AfterTest would fail with an error. 66 func CheckAfterTest(d time.Duration) error { 67 http.DefaultTransport.(*http.Transport).CloseIdleConnections() 68 if testing.Short() { 69 return nil 70 } 71 var bad string 72 badSubstring := map[string]string{ 73 ").writeLoop(": "a Transport", 74 "created by net/http/httptest.(*Server).Start": "an httptest.Server", 75 "timeoutHandler": "a TimeoutHandler", 76 "net.(*netFD).connect(": "a timing out dial", 77 ").noteClientGone(": "a closenotifier sender", 78 ").readLoop(": "a Transport", 79 ".grpc": "a gRPC resource", 80 } 81 82 var stacks string 83 begin := time.Now() 84 for time.Since(begin) < d { 85 bad = "" 86 stacks = strings.Join(interestingGoroutines(), "\n\n") 87 for substr, what := range badSubstring { 88 if strings.Contains(stacks, substr) { 89 bad = what 90 } 91 } 92 if bad == "" { 93 return nil 94 } 95 // Bad stuff found, but goroutines might just still be 96 // shutting down, so give it some time. 97 time.Sleep(50 * time.Millisecond) 98 } 99 return fmt.Errorf("appears to have leaked %s:\n%s", bad, stacks) 100 } 101 102 // AfterTest is meant to run in a defer that executes after a test completes. 103 // It will detect common goroutine leaks, retrying in case there are goroutines 104 // not synchronously torn down, and fail the test if any goroutines are stuck. 105 func AfterTest(t *testing.T) { 106 if err := CheckAfterTest(300 * time.Millisecond); err != nil { 107 t.Errorf("Test %v", err) 108 } 109 } 110 111 func interestingGoroutines() (gs []string) { 112 buf := make([]byte, 2<<20) 113 buf = buf[:runtime.Stack(buf, true)] 114 for _, g := range strings.Split(string(buf), "\n\n") { 115 sl := strings.SplitN(g, "\n", 2) 116 if len(sl) != 2 { 117 continue 118 } 119 stack := strings.TrimSpace(sl[1]) 120 if stack == "" || 121 strings.Contains(stack, "sync.(*WaitGroup).Done") || 122 strings.Contains(stack, "os.(*file).close") || 123 strings.Contains(stack, "created by os/signal.init") || 124 strings.Contains(stack, "runtime/panic.go") || 125 strings.Contains(stack, "created by testing.RunTests") || 126 strings.Contains(stack, "testing.Main(") || 127 strings.Contains(stack, "runtime.goexit") || 128 strings.Contains(stack, "github.com/coreos/etcd/pkg/testutil.interestingGoroutines") || 129 strings.Contains(stack, "github.com/coreos/etcd/pkg/logutil.(*MergeLogger).outputLoop") || 130 strings.Contains(stack, "github.com/golang/glog.(*loggingT).flushDaemon") || 131 strings.Contains(stack, "created by runtime.gc") || 132 strings.Contains(stack, "runtime.MHeap_Scavenger") { 133 continue 134 } 135 gs = append(gs, stack) 136 } 137 sort.Strings(gs) 138 return gs 139 }