Skip to content

Commit be86ef3

Browse files
committed
added internal/xtest.CheckGoroutinesLeak
1 parent 887888a commit be86ef3

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed

internal/xtest/leak.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package xtest
2+
3+
import (
4+
"regexp"
5+
"runtime"
6+
"strings"
7+
"testing"
8+
"time"
9+
)
10+
11+
func checkGoroutinesLeak(onLeak func(stacks []string)) {
12+
var (
13+
bb = make([]byte, 2<<32)
14+
currentGoroutine string
15+
)
16+
17+
time.Sleep(time.Millisecond)
18+
19+
if n := runtime.Stack(bb, false); n < len(bb) {
20+
currentGoroutine = string(regexp.MustCompile("^goroutine \\d+ ").Find(bb[:n]))
21+
}
22+
23+
if n := runtime.Stack(bb, true); n < len(bb) {
24+
bb = bb[:n]
25+
}
26+
27+
goroutines := strings.Split(string(bb), "\n\n")
28+
unexpectedGoroutines := make([]string, 0, len(goroutines))
29+
30+
for _, g := range goroutines {
31+
if strings.HasPrefix(g, currentGoroutine) {
32+
continue
33+
}
34+
stack := strings.Split(g, "\n")
35+
firstFunction := stack[1]
36+
state := strings.Trim(regexp.MustCompile("\\[.*\\]").FindString(regexp.MustCompile("^goroutine \\d+ \\[.*\\]").FindString(stack[0])), "[]")
37+
switch {
38+
case strings.HasPrefix(firstFunction, "testing.RunTests"),
39+
strings.HasPrefix(firstFunction, "testing.(*T).Run"),
40+
strings.HasPrefix(firstFunction, "testing.(*T).Parallel"),
41+
strings.HasPrefix(firstFunction, "testing.runFuzzing"),
42+
strings.HasPrefix(firstFunction, "testing.runFuzzTests"):
43+
if strings.Contains(state, "chan receive") {
44+
continue
45+
}
46+
47+
case strings.HasPrefix(firstFunction, "runtime.goexit") && state == "syscall":
48+
continue
49+
50+
case strings.HasPrefix(firstFunction, "os/signal.signal_recv"),
51+
strings.HasPrefix(firstFunction, "os/signal.loop"):
52+
if strings.Contains(g, "runtime.ensureSigM") {
53+
continue
54+
}
55+
56+
//case strings.HasPrefix(firstFunction, "syscall.syscall"):
57+
// if strings.Contains(state, "syscall") {
58+
// continue
59+
// }
60+
}
61+
62+
unexpectedGoroutines = append(unexpectedGoroutines, g)
63+
}
64+
if l := len(unexpectedGoroutines); l > 0 {
65+
onLeak(goroutines)
66+
}
67+
}
68+
69+
func CheckGoroutinesLeak(tb testing.TB) {
70+
tb.Helper()
71+
checkGoroutinesLeak(func(stacks []string) {
72+
tb.Helper()
73+
tb.Errorf("found %d unexpected goroutines:\n%s", len(stacks), strings.Join(stacks, "\n"))
74+
})
75+
}

internal/xtest/leak_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package xtest
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestCheckGoroutinesLeak(t *testing.T) {
11+
t.Run("Leak", func(t *testing.T) {
12+
require.Panics(t, func() {
13+
defer checkGoroutinesLeak(func(stacks []string) {
14+
panic("panic")
15+
})
16+
go func() {
17+
time.Sleep(time.Second)
18+
}()
19+
})
20+
})
21+
t.Run("NoLeak", func(t *testing.T) {
22+
require.NotPanics(t, func() {
23+
defer checkGoroutinesLeak(func(stacks []string) {
24+
panic("panic")
25+
})
26+
time.Sleep(time.Second)
27+
})
28+
})
29+
}

0 commit comments

Comments
 (0)