github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/testleak/leaktest.go (about) 1 // Copyright 2020 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 // Copyright 2020 WHTCORPS INC, Inc. 6 // 7 // Licensed under the Apache License, Version 2.0 (the "License"); 8 // you may not use this file except in compliance with the License. 9 // You may obtain a copy of the License at 10 // 11 // http://www.apache.org/licenses/LICENSE-2.0 12 // 13 // Unless required by applicable law or agreed to in writing, software 14 // distributed under the License is distributed on an "AS IS" BASIS, 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 // +build leak 18 19 package testleak 20 21 import ( 22 "runtime" 23 "sort" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/whtcorpsinc/check" 29 ) 30 31 func interestingGoroutines() (gs []string) { 32 buf := make([]byte, 2<<20) 33 buf = buf[:runtime.Stack(buf, true)] 34 ignoreList := []string{ 35 "created by github.com/whtcorpsinc/milevadb.init", 36 "testing.RunTests", 37 "check.(*resultTracker).start", 38 "check.(*suiteRunner).runFunc", 39 "check.(*suiteRunner).parallelRun", 40 "localstore.(*dbStore).scheduler", 41 "einsteindb.(*noGCHandler).Start", 42 "dbs.(*dbs).start", 43 "dbs.(*delRange).startEmulator", 44 "petri.NewPetri", 45 "testing.(*T).Run", 46 "petri.(*Petri).LoadPrivilegeLoop", 47 "petri.(*Petri).UFIDelateBlockStatsLoop", 48 "testing.Main(", 49 "runtime.goexit", 50 "created by runtime.gc", 51 "interestingGoroutines", 52 "runtime.MHeap_Scavenger", 53 "created by os/signal.init", 54 // these go routines are async terminated, so they may still alive after test end, thus cause 55 // false positive leak failures 56 "google.golang.org/grpc.(*addrConn).resetTransport", 57 "google.golang.org/grpc.(*ccBalancerWrapper).watcher", 58 "github.com/whtcorpsinc/goleveldb/leveldb/soliton.(*BufferPool).drain", 59 "github.com/whtcorpsinc/goleveldb/leveldb.(*EDB).compactionError", 60 "github.com/whtcorpsinc/goleveldb/leveldb.(*EDB).mpoolDrain", 61 "go.etcd.io/etcd/pkg/logutil.(*MergeLogger).outputLoop", 62 "go.etcd.io/etcd/v3/pkg/logutil.(*MergeLogger).outputLoop", 63 "oracles.(*FIDelOracle).uFIDelateTS", 64 "einsteindb.(*einsteindbStore).runSafePointChecker", 65 "einsteindb.(*RegionCache).asyncCheckAndResolveLoop", 66 "github.com/whtcorpsinc/badger", 67 "github.com/ngaut/entangledstore/einsteindb.(*MVCCStore).runUFIDelateSafePointLoop", 68 } 69 shouldIgnore := func(stack string) bool { 70 if stack == "" { 71 return true 72 } 73 for _, ident := range ignoreList { 74 if strings.Contains(stack, ident) { 75 return true 76 } 77 } 78 return false 79 } 80 for _, g := range strings.Split(string(buf), "\n\n") { 81 sl := strings.SplitN(g, "\n", 2) 82 if len(sl) != 2 { 83 continue 84 } 85 stack := strings.TrimSpace(sl[1]) 86 if shouldIgnore(stack) { 87 continue 88 } 89 gs = append(gs, stack) 90 } 91 sort.Strings(gs) 92 return 93 } 94 95 var beforeTestGoroutines = map[string]bool{} 96 var testGoroutinesInited bool 97 98 // BeforeTest gets the current goroutines. 99 // It's used for check.Suite.SetUpSuite() function. 100 // Now it's only used in the milevadb_test.go. 101 // Note: it's not accurate, consider the following function: 102 // func loop() { 103 // for { 104 // select { 105 // case <-ticker.C: 106 // DoSomething() 107 // } 108 // } 109 // } 110 // If this loop step into DoSomething() during BeforeTest(), the stack for this goroutine will contain DoSomething(). 111 // Then if this loop jumps out of DoSomething during AfterTest(), the stack for this goroutine will not contain DoSomething(). 112 // Resulting in false-positive leak reports. 113 func BeforeTest() { 114 for _, g := range interestingGoroutines() { 115 beforeTestGoroutines[g] = true 116 } 117 testGoroutinesInited = true 118 } 119 120 const defaultCheckCnt = 50 121 122 func checkLeakAfterTest(errorFunc func(cnt int, g string)) func() { 123 // After `BeforeTest`, `beforeTestGoroutines` may still be empty, in this case, 124 // we shouldn't init it again. 125 if !testGoroutinesInited && len(beforeTestGoroutines) == 0 { 126 for _, g := range interestingGoroutines() { 127 beforeTestGoroutines[g] = true 128 } 129 } 130 131 cnt := defaultCheckCnt 132 return func() { 133 defer func() { 134 beforeTestGoroutines = map[string]bool{} 135 testGoroutinesInited = false 136 }() 137 138 var leaked []string 139 for i := 0; i < cnt; i++ { 140 leaked = leaked[:0] 141 for _, g := range interestingGoroutines() { 142 if !beforeTestGoroutines[g] { 143 leaked = append(leaked, g) 144 } 145 } 146 // Bad stuff found, but goroutines might just still be 147 // shutting down, so give it some time. 148 if len(leaked) != 0 { 149 time.Sleep(50 * time.Millisecond) 150 continue 151 } 152 153 return 154 } 155 for _, g := range leaked { 156 errorFunc(cnt, g) 157 } 158 } 159 } 160 161 // AfterTest gets the current goroutines and runs the returned function to 162 // get the goroutines at that time to contrast whether any goroutines leaked. 163 // Usage: defer testleak.AfterTest(c)() 164 // It can call with BeforeTest() at the beginning of check.Suite.TearDownSuite() or 165 // call alone at the beginning of each test. 166 func AfterTest(c *check.C) func() { 167 errorFunc := func(cnt int, g string) { 168 c.Errorf("Test %s check-count %d appears to have leaked: %v", c.TestName(), cnt, g) 169 } 170 return checkLeakAfterTest(errorFunc) 171 } 172 173 // AfterTestT is used after all the test cases is finished. 174 func AfterTestT(t *testing.T) func() { 175 errorFunc := func(cnt int, g string) { 176 t.Errorf("Test %s check-count %d appears to have leaked: %v", t.Name(), cnt, g) 177 } 178 return checkLeakAfterTest(errorFunc) 179 }