tl;dr
io.Pipe
を使う
モチベーション
goroutine で無限ループさせながら特定の io.Writer
に書き込むようなコードがあった時、テストで正しく書き込まれているか知りたいことがある。
しかしよくテストで使われる bytes.Buffer
を利用すると、内部のバッファを取り合う形になってしまうのでデータ競合が発生する。
例えば以下のようなコード
- 被テストコード
type A struct {
w io.Writer
}
func (a *A) loop() {
for {
message := []byte("foo")
fmt.Fprintf(a.w, "%s", message)
time.Sleep(10 * time.Millisecond)
}
}
- テストコード
func TestRace(t *testing.T) {
var w bytes.Buffer
a := &A{
w: &w,
}
go a.loop()
time.Sleep(50 * time.Millisecond)
// data race!!!
bytes := make([]byte, 1024)
w.Read(bytes)
}
対策
bytes.Buffer
の代わりに io.Pipe
を使う。
io.Pipe
は内部で同じバッファを共有しつつ、read/writeそれぞれにproxyしてくれるかのような振る舞いをしてくれるので、今回のようなケースでは積極的に使っていきたい所存である。
- テストコード
func TestNonRace(t *testing.T) {
pr, pw := io.Pipe()
a := &A{
w: pw,
}
go a.loop()
time.Sleep(50 * time.Millisecond)
// non data race :)
bytes := make([]byte, 1024)
pr.Read(bytes)
}