github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/gmhttp/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 gmhttp_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/hxx258456/ccgo/gmhttp"
    19  )
    20  
    21  var quietLog = log.New(io.Discard, "", 0)
    22  
    23  func TestMain(m *testing.M) {
    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  		sl := strings.SplitN(g, "\n", 2)
    36  		if len(sl) != 2 {
    37  			continue
    38  		}
    39  		stack := strings.TrimSpace(sl[1])
    40  		if stack == "" ||
    41  			strings.Contains(stack, "testing.(*M).before.func1") ||
    42  			strings.Contains(stack, "os/signal.signal_recv") ||
    43  			strings.Contains(stack, "created by net.startServer") ||
    44  			strings.Contains(stack, "created by testing.RunTests") ||
    45  			strings.Contains(stack, "closeWriteAndWait") ||
    46  			strings.Contains(stack, "testing.Main(") ||
    47  			// These only show up with GOTRACEBACK=2; Issue 5005 (comment 28)
    48  			strings.Contains(stack, "runtime.goexit") ||
    49  			strings.Contains(stack, "created by runtime.gc") ||
    50  			strings.Contains(stack, "gmgo/gmhttp_test.interestingGoroutines") ||
    51  			strings.Contains(stack, "runtime.MHeap_Scavenger") {
    52  			continue
    53  		}
    54  		gs = append(gs, stack)
    55  	}
    56  	sort.Strings(gs)
    57  	return
    58  }
    59  
    60  // Verify the other tests didn't leave any goroutines running.
    61  func goroutineLeaked() bool {
    62  	if testing.Short() || runningBenchmarks() {
    63  		// Don't worry about goroutine leaks in -short mode or in
    64  		// benchmark mode. Too distracting when there are false positives.
    65  		return false
    66  	}
    67  
    68  	var stackCount map[string]int
    69  	for i := 0; i < 5; i++ {
    70  		n := 0
    71  		stackCount = make(map[string]int)
    72  		gs := interestingGoroutines()
    73  		for _, g := range gs {
    74  			stackCount[g]++
    75  			n++
    76  		}
    77  		if n == 0 {
    78  			return false
    79  		}
    80  		// Wait for goroutines to schedule and die off:
    81  		time.Sleep(100 * time.Millisecond)
    82  	}
    83  	fmt.Fprintf(os.Stderr, "Too many goroutines running after github.com/hxx258456/ccgo/gmhttp test(s).\n")
    84  	for stack, count := range stackCount {
    85  		fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack)
    86  	}
    87  	return true
    88  }
    89  
    90  // setParallel marks t as a parallel test if we're in short mode
    91  // (all.bash), but as a serial test otherwise. Using t.Parallel isn't
    92  // compatible with the afterTest func in non-short mode.
    93  func setParallel(t *testing.T) {
    94  	if strings.Contains(t.Name(), "HTTP2") {
    95  		http.CondSkipHTTP2(t)
    96  	}
    97  	if testing.Short() {
    98  		t.Parallel()
    99  	}
   100  }
   101  
   102  func runningBenchmarks() bool {
   103  	for i, arg := range os.Args {
   104  		if strings.HasPrefix(arg, "-test.bench=") && !strings.HasSuffix(arg, "=") {
   105  			return true
   106  		}
   107  		if arg == "-test.bench" && i < len(os.Args)-1 && os.Args[i+1] != "" {
   108  			return true
   109  		}
   110  	}
   111  	return false
   112  }
   113  
   114  func afterTest(t testing.TB) {
   115  	http.DefaultTransport.(*http.Transport).CloseIdleConnections()
   116  	if testing.Short() {
   117  		return
   118  	}
   119  	var bad string
   120  	badSubstring := map[string]string{
   121  		").readLoop(":  "a Transport",
   122  		").writeLoop(": "a Transport",
   123  		"created by github.com/hxx258456/ccgo/gmhttp/httptest.(*Server).Start": "an httptest.Server",
   124  		"timeoutHandler":        "a TimeoutHandler",
   125  		"net.(*netFD).connect(": "a timing out dial",
   126  		").noteClientGone(":     "a closenotifier sender",
   127  	}
   128  	var stacks string
   129  	for i := 0; i < 10; i++ {
   130  		bad = ""
   131  		stacks = strings.Join(interestingGoroutines(), "\n\n")
   132  		for substr, what := range badSubstring {
   133  			if strings.Contains(stacks, substr) {
   134  				bad = what
   135  			}
   136  		}
   137  		if bad == "" {
   138  			return
   139  		}
   140  		// Bad stuff found, but goroutines might just still be
   141  		// shutting down, so give it some time.
   142  		time.Sleep(250 * time.Millisecond)
   143  	}
   144  	t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks)
   145  }
   146  
   147  // waitCondition reports whether fn eventually returned true,
   148  // checking immediately and then every checkEvery amount,
   149  // until waitFor has elapsed, at which point it returns false.
   150  func waitCondition(waitFor, checkEvery time.Duration, fn func() bool) bool {
   151  	deadline := time.Now().Add(waitFor)
   152  	for time.Now().Before(deadline) {
   153  		if fn() {
   154  			return true
   155  		}
   156  		time.Sleep(checkEvery)
   157  	}
   158  	return false
   159  }
   160  
   161  // waitErrCondition is like waitCondition but with errors instead of bools.
   162  func waitErrCondition(waitFor, checkEvery time.Duration, fn func() error) error {
   163  	deadline := time.Now().Add(waitFor)
   164  	var err error
   165  	for time.Now().Before(deadline) {
   166  		if err = fn(); err == nil {
   167  			return nil
   168  		}
   169  		time.Sleep(checkEvery)
   170  	}
   171  	return err
   172  }