github.com/ooni/oohttp@v0.7.2/main_test.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 http_test 6 7 import ( 8 "fmt" 9 "io" 10 "log" 11 "os" 12 "runtime" 13 "sort" 14 "strings" 15 "testing" 16 "time" 17 18 http "github.com/ooni/oohttp" 19 ) 20 21 var quietLog = log.New(io.Discard, "", 0) 22 23 func TestMain(m *testing.M) { 24 *http.MaxWriteWaitBeforeConnReuse = 60 * time.Minute 25 v := m.Run() 26 if v == 0 && goroutineLeaked() { 27 os.Exit(1) 28 } 29 os.Exit(v) 30 } 31 32 func interestingGoroutines() (gs []string) { 33 buf := make([]byte, 2<<20) 34 buf = buf[:runtime.Stack(buf, true)] 35 for _, g := range strings.Split(string(buf), "\n\n") { 36 _, stack, _ := strings.Cut(g, "\n") 37 stack = strings.TrimSpace(stack) 38 if stack == "" || 39 strings.Contains(stack, "testing.(*M).before.func1") || 40 strings.Contains(stack, "os/signal.signal_recv") || 41 strings.Contains(stack, "created by net.startServer") || 42 strings.Contains(stack, "created by testing.RunTests") || 43 strings.Contains(stack, "closeWriteAndWait") || 44 strings.Contains(stack, "testing.Main(") || 45 // These only show up with GOTRACEBACK=2; Issue 5005 (comment 28) 46 strings.Contains(stack, "runtime.goexit") || 47 strings.Contains(stack, "created by runtime.gc") || 48 strings.Contains(stack, "oohttp_test.interestingGoroutines") || 49 strings.Contains(stack, "runtime.MHeap_Scavenger") { 50 continue 51 } 52 gs = append(gs, stack) 53 } 54 sort.Strings(gs) 55 return 56 } 57 58 // Verify the other tests didn't leave any goroutines running. 59 func goroutineLeaked() bool { 60 if testing.Short() || runningBenchmarks() { 61 // Don't worry about goroutine leaks in -short mode or in 62 // benchmark mode. Too distracting when there are false positives. 63 return false 64 } 65 66 var stackCount map[string]int 67 for i := 0; i < 5; i++ { 68 n := 0 69 stackCount = make(map[string]int) 70 gs := interestingGoroutines() 71 for _, g := range gs { 72 stackCount[g]++ 73 n++ 74 } 75 if n == 0 { 76 return false 77 } 78 // Wait for goroutines to schedule and die off: 79 time.Sleep(100 * time.Millisecond) 80 } 81 fmt.Fprintf(os.Stderr, "Too many goroutines running after net/http test(s).\n") 82 for stack, count := range stackCount { 83 fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack) 84 } 85 return true 86 } 87 88 // setParallel marks t as a parallel test if we're in short mode 89 // (all.bash), but as a serial test otherwise. Using t.Parallel isn't 90 // compatible with the afterTest func in non-short mode. 91 func setParallel(t *testing.T) { 92 if strings.Contains(t.Name(), "HTTP2") { 93 http.CondSkipHTTP2(t) 94 } 95 if testing.Short() { 96 t.Parallel() 97 } 98 } 99 100 func runningBenchmarks() bool { 101 for i, arg := range os.Args { 102 if strings.HasPrefix(arg, "-test.bench=") && !strings.HasSuffix(arg, "=") { 103 return true 104 } 105 if arg == "-test.bench" && i < len(os.Args)-1 && os.Args[i+1] != "" { 106 return true 107 } 108 } 109 return false 110 } 111 112 var leakReported bool 113 114 func afterTest(t testing.TB) { 115 http.DefaultTransport.(*http.Transport).CloseIdleConnections() 116 if testing.Short() { 117 return 118 } 119 if leakReported { 120 // To avoid confusion, only report the first leak of each test run. 121 // After the first leak has been reported, we can't tell whether the leaked 122 // goroutines are a new leak from a subsequent test or just the same 123 // goroutines from the first leak still hanging around, and we may add a lot 124 // of latency waiting for them to exit at the end of each test. 125 return 126 } 127 128 // We shouldn't be running the leak check for parallel tests, because we might 129 // report the goroutines from a test that is still running as a leak from a 130 // completely separate test that has just finished. So we use non-atomic loads 131 // and stores for the leakReported variable, and store every time we start a 132 // leak check so that the race detector will flag concurrent leak checks as a 133 // race even if we don't detect any leaks. 134 leakReported = true 135 136 var bad string 137 badSubstring := map[string]string{ 138 ").readLoop(": "a Transport", 139 ").writeLoop(": "a Transport", 140 "created by net/http/httptest.(*Server).Start": "an httptest.Server", 141 "timeoutHandler": "a TimeoutHandler", 142 "net.(*netFD).connect(": "a timing out dial", 143 ").noteClientGone(": "a closenotifier sender", 144 } 145 var stacks string 146 for i := 0; i < 10; i++ { 147 bad = "" 148 stacks = strings.Join(interestingGoroutines(), "\n\n") 149 for substr, what := range badSubstring { 150 if strings.Contains(stacks, substr) { 151 bad = what 152 } 153 } 154 if bad == "" { 155 leakReported = false 156 return 157 } 158 // Bad stuff found, but goroutines might just still be 159 // shutting down, so give it some time. 160 time.Sleep(250 * time.Millisecond) 161 } 162 t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks) 163 } 164 165 // waitCondition waits for fn to return true, 166 // checking immediately and then at exponentially increasing intervals. 167 func waitCondition(t testing.TB, delay time.Duration, fn func(time.Duration) bool) { 168 t.Helper() 169 start := time.Now() 170 var since time.Duration 171 for !fn(since) { 172 time.Sleep(delay) 173 delay = 2*delay - (delay / 2) // 1.5x, rounded up 174 since = time.Since(start) 175 } 176 }