| // 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 http_test |
| |
| import ( |
| "fmt" |
| "io" |
| "log" |
| "net/http" |
| "os" |
| "runtime" |
| "sort" |
| "strings" |
| "testing" |
| "time" |
| ) |
| |
| var quietLog = log.New(io.Discard, "", 0) |
| |
| func TestMain(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") { |
| _, stack, _ := strings.Cut(g, "\n") |
| stack = strings.TrimSpace(stack) |
| if stack == "" || |
| strings.Contains(stack, "testing.(*M).before.func1") || |
| strings.Contains(stack, "os/signal.signal_recv") || |
| 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, "net/http_test.interestingGoroutines") || |
| strings.Contains(stack, "runtime.MHeap_Scavenger") { |
| 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() || runningBenchmarks() { |
| // Don't worry about goroutine leaks in -short mode or in |
| // benchmark mode. Too distracting when there are false positives. |
| return false |
| } |
| |
| var stackCount map[string]int |
| for i := 0; i < 5; i++ { |
| n := 0 |
| stackCount = make(map[string]int) |
| gs := interestingGoroutines() |
| for _, g := range gs { |
| stackCount[g]++ |
| n++ |
| } |
| if n == 0 { |
| return false |
| } |
| // Wait for goroutines to schedule and die off: |
| time.Sleep(100 * time.Millisecond) |
| } |
| fmt.Fprintf(os.Stderr, "Too many goroutines running after net/http test(s).\n") |
| for stack, count := range stackCount { |
| fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack) |
| } |
| return true |
| } |
| |
| // setParallel marks t as a parallel test if we're in short mode |
| // (all.bash), but as a serial test otherwise. Using t.Parallel isn't |
| // compatible with the afterTest func in non-short mode. |
| func setParallel(t *testing.T) { |
| if strings.Contains(t.Name(), "HTTP2") { |
| http.CondSkipHTTP2(t) |
| } |
| if testing.Short() { |
| t.Parallel() |
| } |
| } |
| |
| func runningBenchmarks() bool { |
| for i, arg := range os.Args { |
| if strings.HasPrefix(arg, "-test.bench=") && !strings.HasSuffix(arg, "=") { |
| return true |
| } |
| if arg == "-test.bench" && i < len(os.Args)-1 && os.Args[i+1] != "" { |
| return true |
| } |
| } |
| return false |
| } |
| |
| 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", |
| } |
| var stacks string |
| for i := 0; i < 10; 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(250 * time.Millisecond) |
| } |
| t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks) |
| } |
| |
| // waitCondition reports whether fn eventually returned true, |
| // checking immediately and then every checkEvery amount, |
| // until waitFor has elapsed, at which point it returns false. |
| func waitCondition(waitFor, checkEvery time.Duration, fn func() bool) bool { |
| deadline := time.Now().Add(waitFor) |
| for time.Now().Before(deadline) { |
| if fn() { |
| return true |
| } |
| time.Sleep(checkEvery) |
| } |
| return false |
| } |
| |
| // waitErrCondition is like waitCondition but with errors instead of bools. |
| func waitErrCondition(waitFor, checkEvery time.Duration, fn func() error) error { |
| deadline := time.Now().Add(waitFor) |
| var err error |
| for time.Now().Before(deadline) { |
| if err = fn(); err == nil { |
| return nil |
| } |
| time.Sleep(checkEvery) |
| } |
| return err |
| } |