main_test.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  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. package integration
  5. import (
  6. "fmt"
  7. "net/http"
  8. "os"
  9. "runtime"
  10. "sort"
  11. "strings"
  12. "testing"
  13. "time"
  14. )
  15. var atLeastGo15 bool = false
  16. func init() {
  17. var major, minor int
  18. var discard string
  19. i, err := fmt.Sscanf(runtime.Version(), "go%d.%d%s", &major, &minor, &discard)
  20. atLeastGo15 = (err == nil && i == 3 && (major > 1 || major == 1 && minor >= 5))
  21. }
  22. func interestingGoroutines() (gs []string) {
  23. buf := make([]byte, 2<<20)
  24. buf = buf[:runtime.Stack(buf, true)]
  25. for _, g := range strings.Split(string(buf), "\n\n") {
  26. sl := strings.SplitN(g, "\n", 2)
  27. if len(sl) != 2 {
  28. continue
  29. }
  30. stack := strings.TrimSpace(sl[1])
  31. if stack == "" ||
  32. strings.Contains(stack, "created by testing.RunTests") ||
  33. strings.Contains(stack, "testing.Main(") ||
  34. strings.Contains(stack, "runtime.goexit") ||
  35. strings.Contains(stack, "github.com/coreos/etcd/integration.interestingGoroutines") ||
  36. strings.Contains(stack, "github.com/coreos/etcd/pkg/logutil.(*MergeLogger).outputLoop") ||
  37. strings.Contains(stack, "github.com/golang/glog.(*loggingT).flushDaemon") ||
  38. strings.Contains(stack, "created by runtime.gc") ||
  39. strings.Contains(stack, "runtime.MHeap_Scavenger") {
  40. continue
  41. }
  42. gs = append(gs, stack)
  43. }
  44. sort.Strings(gs)
  45. return
  46. }
  47. // Verify the other tests didn't leave any goroutines running.
  48. func TestMain(m *testing.M) {
  49. v := m.Run()
  50. if v == 0 && goroutineLeaked() {
  51. os.Exit(1)
  52. }
  53. os.Exit(v)
  54. }
  55. func goroutineLeaked() bool {
  56. if testing.Short() {
  57. // not counting goroutines for leakage in -short mode
  58. return false
  59. }
  60. gs := interestingGoroutines()
  61. n := 0
  62. stackCount := make(map[string]int)
  63. for _, g := range gs {
  64. stackCount[g]++
  65. n++
  66. }
  67. if n == 0 {
  68. return false
  69. }
  70. fmt.Fprintf(os.Stderr, "Too many goroutines running after integration test(s).\n")
  71. for stack, count := range stackCount {
  72. fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack)
  73. }
  74. return true
  75. }
  76. func afterTest(t *testing.T) {
  77. http.DefaultTransport.(*http.Transport).CloseIdleConnections()
  78. if testing.Short() {
  79. return
  80. }
  81. var bad string
  82. badSubstring := map[string]string{
  83. ").writeLoop(": "a Transport",
  84. "created by net/http/httptest.(*Server).Start": "an httptest.Server",
  85. "timeoutHandler": "a TimeoutHandler",
  86. "net.(*netFD).connect(": "a timing out dial",
  87. ").noteClientGone(": "a closenotifier sender",
  88. }
  89. // readLoop was buggy before go1.5:
  90. // https://github.com/golang/go/issues/10457
  91. if atLeastGo15 {
  92. badSubstring[").readLoop("] = "a Transport"
  93. }
  94. var stacks string
  95. for i := 0; i < 6; i++ {
  96. bad = ""
  97. stacks = strings.Join(interestingGoroutines(), "\n\n")
  98. for substr, what := range badSubstring {
  99. if strings.Contains(stacks, substr) {
  100. bad = what
  101. }
  102. }
  103. if bad == "" {
  104. return
  105. }
  106. // Bad stuff found, but goroutines might just still be
  107. // shutting down, so give it some time.
  108. time.Sleep(50 * time.Millisecond)
  109. }
  110. t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks)
  111. }