leak.go: Make the per-test AfterTest strictly wait for none of the unwanted rountines.

This commit is contained in:
Piotr Tabor 2021-03-05 23:25:42 +01:00
parent 94a371acd7
commit efb584cc9b

View File

@ -59,6 +59,7 @@ func CheckLeakedGoroutine() bool {
func CheckAfterTest(d time.Duration) error { func CheckAfterTest(d time.Duration) error {
http.DefaultTransport.(*http.Transport).CloseIdleConnections() http.DefaultTransport.(*http.Transport).CloseIdleConnections()
var bad string var bad string
// Presence of these goroutines causes immediate test failure.
badSubstring := map[string]string{ badSubstring := map[string]string{
").writeLoop(": "a Transport", ").writeLoop(": "a Transport",
"created by net/http/httptest.(*Server).Start": "an httptest.Server", "created by net/http/httptest.(*Server).Start": "an httptest.Server",
@ -74,17 +75,20 @@ func CheckAfterTest(d time.Duration) error {
begin := time.Now() begin := time.Now()
for time.Since(begin) < d { for time.Since(begin) < d {
bad = "" bad = ""
stacks = strings.Join(interestingGoroutines(), "\n\n") goroutines := interestingGoroutines()
if len(goroutines) == 0 {
return nil
}
stacks = strings.Join(goroutines, "\n\n")
for substr, what := range badSubstring { for substr, what := range badSubstring {
if strings.Contains(stacks, substr) { if strings.Contains(stacks, substr) {
bad = what bad = what
} }
} }
if bad == "" { // Undesired goroutines found, but goroutines might just still be
return nil
}
// Bad stuff found, but goroutines might just still be
// shutting down, so give it some time. // shutting down, so give it some time.
runtime.Gosched()
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
} }
return fmt.Errorf("appears to have leaked %s:\n%s", bad, stacks) return fmt.Errorf("appears to have leaked %s:\n%s", bad, stacks)
@ -94,7 +98,7 @@ func CheckAfterTest(d time.Duration) error {
// It will detect common goroutine leaks, retrying in case there are goroutines // It will detect common goroutine leaks, retrying in case there are goroutines
// not synchronously torn down, and fail the test if any goroutines are stuck. // not synchronously torn down, and fail the test if any goroutines are stuck.
func AfterTest(t *testing.T) { func AfterTest(t *testing.T) {
if err := CheckAfterTest(300 * time.Millisecond); err != nil { if err := CheckAfterTest(1 * time.Second); err != nil {
t.Errorf("Test %v", err) t.Errorf("Test %v", err)
} }
} }
@ -126,7 +130,8 @@ func interestingGoroutines() (gs []string) {
strings.Contains(stack, "created by text/template/parse.lex") || strings.Contains(stack, "created by text/template/parse.lex") ||
strings.Contains(stack, "runtime.MHeap_Scavenger") || strings.Contains(stack, "runtime.MHeap_Scavenger") ||
strings.Contains(stack, "rcrypto/internal/boring.(*PublicKeyRSA).finalize") || strings.Contains(stack, "rcrypto/internal/boring.(*PublicKeyRSA).finalize") ||
strings.Contains(stack, "net.(*netFD).Close(") { strings.Contains(stack, "net.(*netFD).Close(") ||
strings.Contains(stack, "testing.(*T).Run") {
continue continue
} }
gs = append(gs, stack) gs = append(gs, stack)