Home | History | Annotate | Download | only in http
      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/ioutil"
     10 	"log"
     11 	"net/http"
     12 	"os"
     13 	"runtime"
     14 	"sort"
     15 	"strings"
     16 	"testing"
     17 	"time"
     18 )
     19 
     20 var quietLog = log.New(ioutil.Discard, "", 0)
     21 
     22 func TestMain(m *testing.M) {
     23 	v := m.Run()
     24 	if v == 0 && goroutineLeaked() {
     25 		os.Exit(1)
     26 	}
     27 	os.Exit(v)
     28 }
     29 
     30 func interestingGoroutines() (gs []string) {
     31 	buf := make([]byte, 2<<20)
     32 	buf = buf[:runtime.Stack(buf, true)]
     33 	for _, g := range strings.Split(string(buf), "\n\n") {
     34 		sl := strings.SplitN(g, "\n", 2)
     35 		if len(sl) != 2 {
     36 			continue
     37 		}
     38 		stack := strings.TrimSpace(sl[1])
     39 		if stack == "" ||
     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, "net/http_test.interestingGoroutines") ||
     48 			strings.Contains(stack, "runtime.MHeap_Scavenger") {
     49 			continue
     50 		}
     51 		gs = append(gs, stack)
     52 	}
     53 	sort.Strings(gs)
     54 	return
     55 }
     56 
     57 // Verify the other tests didn't leave any goroutines running.
     58 func goroutineLeaked() bool {
     59 	if testing.Short() {
     60 		// not counting goroutines for leakage in -short mode
     61 		return false
     62 	}
     63 
     64 	var stackCount map[string]int
     65 	for i := 0; i < 5; i++ {
     66 		n := 0
     67 		stackCount = make(map[string]int)
     68 		gs := interestingGoroutines()
     69 		for _, g := range gs {
     70 			stackCount[g]++
     71 			n++
     72 		}
     73 		if n == 0 {
     74 			return false
     75 		}
     76 		// Wait for goroutines to schedule and die off:
     77 		time.Sleep(100 * time.Millisecond)
     78 	}
     79 	fmt.Fprintf(os.Stderr, "Too many goroutines running after net/http test(s).\n")
     80 	for stack, count := range stackCount {
     81 		fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack)
     82 	}
     83 	return true
     84 }
     85 
     86 // setParallel marks t as a parallel test if we're in short mode
     87 // (all.bash), but as a serial test otherwise. Using t.Parallel isn't
     88 // compatible with the afterTest func in non-short mode.
     89 func setParallel(t *testing.T) {
     90 	if testing.Short() {
     91 		t.Parallel()
     92 	}
     93 }
     94 
     95 func afterTest(t testing.TB) {
     96 	http.DefaultTransport.(*http.Transport).CloseIdleConnections()
     97 	if testing.Short() {
     98 		return
     99 	}
    100 	var bad string
    101 	badSubstring := map[string]string{
    102 		").readLoop(":                                  "a Transport",
    103 		").writeLoop(":                                 "a Transport",
    104 		"created by net/http/httptest.(*Server).Start": "an httptest.Server",
    105 		"timeoutHandler":                               "a TimeoutHandler",
    106 		"net.(*netFD).connect(":                        "a timing out dial",
    107 		").noteClientGone(":                            "a closenotifier sender",
    108 	}
    109 	var stacks string
    110 	for i := 0; i < 4; i++ {
    111 		bad = ""
    112 		stacks = strings.Join(interestingGoroutines(), "\n\n")
    113 		for substr, what := range badSubstring {
    114 			if strings.Contains(stacks, substr) {
    115 				bad = what
    116 			}
    117 		}
    118 		if bad == "" {
    119 			return
    120 		}
    121 		// Bad stuff found, but goroutines might just still be
    122 		// shutting down, so give it some time.
    123 		time.Sleep(250 * time.Millisecond)
    124 	}
    125 	t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks)
    126 }
    127 
    128 // waitCondition reports whether fn eventually returned true,
    129 // checking immediately and then every checkEvery amount,
    130 // until waitFor has elapsed, at which point it returns false.
    131 func waitCondition(waitFor, checkEvery time.Duration, fn func() bool) bool {
    132 	deadline := time.Now().Add(waitFor)
    133 	for time.Now().Before(deadline) {
    134 		if fn() {
    135 			return true
    136 		}
    137 		time.Sleep(checkEvery)
    138 	}
    139 	return false
    140 }
    141 
    142 // waitErrCondition is like waitCondition but with errors instead of bools.
    143 func waitErrCondition(waitFor, checkEvery time.Duration, fn func() error) error {
    144 	deadline := time.Now().Add(waitFor)
    145 	var err error
    146 	for time.Now().Before(deadline) {
    147 		if err = fn(); err == nil {
    148 			return nil
    149 		}
    150 		time.Sleep(checkEvery)
    151 	}
    152 	return err
    153 }
    154 
    155 func closeClient(c *http.Client) {
    156 	c.Transport.(*http.Transport).CloseIdleConnections()
    157 }
    158