このページについて
uberが手掛けているgoleakを使ってgoroutine leakを検出できるか試してみました。簡単なデモコードではしっかりエラーにしてくれたので、使ってみてもよいのかなと思います。
現在(2019-08)α版 なのでもう少し待ってみてもいいかもしれませんが、、
内容
goroutine leakの起こる関数を書き、goleakを使って検出できるか試してみます。
関数
package helper
import (
"fmt"
"time"
)
func Leak(sleep int) {
fmt.Println("leak test")
go func() {
fmt.Println("start goroutine")
for {
// leak しないパターンも試す
if sleep == 0 {
return
}
time.Sleep(time.Duration(sleep) * time.Second)
}
}()
}
テスト
leak_test.go
package helper
import "testing"
func TestLeak(t *testing.T) {
type args struct {
sleep int
}
tests := []struct {
name string
args args
}{
{
name: "leak",
args: args{
sleep: 1,
},
},
{
name: "not leak",
args: args{
sleep: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Leak(tt.args.sleep)
})
}
}
goleakはパッケージ全体に適用したい場合はTestMainから動かすと良いみたいです
main_test.go
package helper
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
これで準備は整いました。
テストを実行します
$ go test -v ./...
=== RUN TestLeak
=== RUN TestLeak/leak
leak test
start goroutine
=== RUN TestLeak/not_leak
leak test
start goroutine
--- PASS: TestLeak (0.00s)
--- PASS: TestLeak/leak (0.00s)
--- PASS: TestLeak/not_leak (0.00s)
PASS
coverage: 100.0% of statements
goleak: Errors on successful test run: found unexpected goroutines:
[Goroutine 33 in state sleep, with runtime.goparkunlock on top of the stack:
goroutine 33 [sleep]:
runtime.goparkunlock(...)
/usr/local/Cellar/go/1.12.1/libexec/src/runtime/proc.go:307
time.Sleep(0x3b9aca00)
/usr/local/Cellar/go/1.12.1/libexec/src/runtime/time.go:105 +0x159
github.com/smith-30/ootd/helper.Leak.func1(0x1)
/go/src/github.com/smith-30/ootd/helper/leak.go:16 +0xa0
created by github.com/smith-30/ootd/helper.Leak
/go/src/github.com/smith-30/ootd/helper/leak.go:10 +0xa6
]
FAIL github.com/smith-30/ootd/helper 0.457s
Error: Tests failed.
goroutineリークを検出してテストを落としてくれていますね!
もちろん、テストケースが not leak
のみの場合はエラーで落ちません
goroutineをバリバリ使うデーモンや並列処理を使うAPIのメインロジックのe2eテストの際には使っていきたいと思っています。