Source file src/net/http/main_test.go

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

View as plain text