Skip to content

Commit 121aceb

Browse files
authored
Merge pull request #1672 from ydb-platform/check-goroutines-leak
added internal/xtest.CheckGoroutinesLeak
2 parents 8d20221 + e208108 commit 121aceb

File tree

5 files changed

+116
-16
lines changed

5 files changed

+116
-16
lines changed

internal/xtest/leak.go

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

internal/xtest/leak_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package xtest
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestCheckGoroutinesLeak(t *testing.T) {
10+
t.Run("Leak", func(t *testing.T) {
11+
TestManyTimes(t, func(t testing.TB) {
12+
ch := make(chan struct{})
13+
require.Panics(t, func() {
14+
defer checkGoroutinesLeak(func([]string) {
15+
panic("test")
16+
})
17+
go func() {
18+
<-ch
19+
}()
20+
})
21+
close(ch)
22+
})
23+
})
24+
t.Run("NoLeak", func(t *testing.T) {
25+
TestManyTimes(t, func(t testing.TB) {
26+
require.NotPanics(t, func() {
27+
defer checkGoroutinesLeak(func([]string) {
28+
panic("test")
29+
})
30+
ch := make(chan struct{})
31+
defer func() {
32+
<-ch
33+
}()
34+
go func() {
35+
close(ch)
36+
}()
37+
})
38+
})
39+
})
40+
}

tests/integration/basic_example_database_sql_bindings_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
)
2727

2828
func TestBasicExampleDatabaseSqlBindings(t *testing.T) {
29-
defer simpleDetectGoroutineLeak(t)
29+
defer xtest.CheckGoroutinesLeak(t)
3030

3131
folder := t.Name()
3232

tests/integration/basic_example_database_sql_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
)
2828

2929
func TestBasicExampleDatabaseSql(t *testing.T) {
30-
defer simpleDetectGoroutineLeak(t)
30+
defer xtest.CheckGoroutinesLeak(t)
3131

3232
folder := t.Name()
3333

tests/integration/helpers_test.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"fmt"
1111
"os"
1212
"path"
13-
"runtime"
1413
"strings"
1514
"testing"
1615
"text/template"
@@ -465,16 +464,3 @@ func driverEngine(db *sql.DB) (engine xsql.Engine) {
465464

466465
return engine
467466
}
468-
469-
func simpleDetectGoroutineLeak(t *testing.T) {
470-
// 1) testing.go => main.main()
471-
// 2) current test
472-
const expectedGoroutinesCount = 2
473-
if num := runtime.NumGoroutine(); num > expectedGoroutinesCount {
474-
bb := make([]byte, 2<<32)
475-
if n := runtime.Stack(bb, true); n < len(bb) {
476-
bb = bb[:n]
477-
}
478-
t.Error(fmt.Sprintf("unexpected goroutines:\n%s\n", string(bb[runtime.Stack(bb, false)+1:])))
479-
}
480-
}

0 commit comments

Comments
 (0)