[インデックス 15885] ファイルの概要
このコミットは、Go言語の標準ライブラリであるbufio
パッケージのテストコード(src/pkg/bufio/bufio_test.go
)に、いくつかの新しいテストケースを追加するものです。具体的には、bufio.Writer
のエラーハンドリングの堅牢性を検証する既存のテスト(TestWriteErrors
)の改善と、bufio.Reader
がエラー状態から回復できるか、またはエラーが適切に伝播されるかを検証する新しいテスト(TestReaderClearError
)の追加が行われています。
コミット
commit dc71ace28261a37922afe02d36a02adf50ba2f94
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Thu Mar 21 19:59:49 2013 -0700
bufio: add some tests
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/7927044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dc71ace28261a37922afe02d36a02adf50ba2f94
元コミット内容
bufio: add some tests
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/7927044
変更の背景
このコミットの背景には、Go言語の標準ライブラリの品質と堅牢性を高めるという継続的な取り組みがあります。bufio
パッケージは、I/O操作の効率を向上させるためにバッファリング機能を提供する非常に重要なパッケージです。このような基盤となるパッケージにおいては、予期せぬエラーシナリオやエッジケースに対する挙動が正確であることを保証することが不可欠です。
特に、I/O操作におけるエラーの伝播と処理は複雑になりがちです。bufio.Writer
が一度エラー状態になった場合に、そのエラーが後続の操作(特にFlush
)で正しく報告され続けるか、またbufio.Reader
が基盤となるリーダーからエラーを受け取った後に、そのエラー状態がどのように扱われ、クリアされるのかといった挙動は、ライブラリの利用者にとって非常に重要です。
このコミットは、これらのエラーハンドリングの側面をより厳密にテストし、bufio
パッケージが様々なエラー条件下で期待通りに動作することを保証するために行われました。これにより、開発者がbufio
パッケージを安心して利用できるよう、信頼性が向上します。
前提知識の解説
Go言語のbufio
パッケージ
bufio
パッケージは、Go言語の標準ライブラリの一部であり、I/O操作をバッファリングすることで効率を向上させるための機能を提供します。主な型としてReader
とWriter
があります。
bufio.Reader
:io.Reader
インターフェースをラップし、内部バッファを使用して読み込み操作を最適化します。例えば、一度に多くのバイトを読み込んでバッファに格納し、その後の小さな読み込み要求に対してはバッファからデータを提供することで、基盤となるI/O操作の回数を減らします。bufio.Writer
:io.Writer
インターフェースをラップし、内部バッファにデータを書き込み、バッファが満杯になったときやFlush
メソッドが呼び出されたときに、まとめて基盤となるio.Writer
に書き込みます。これにより、書き込み操作の回数を減らし、パフォーマンスを向上させます。
io.Reader
とio.Writer
インターフェース
Go言語では、I/O操作はio.Reader
とio.Writer
というシンプルなインターフェースによって抽象化されています。
io.Reader
:Read(p []byte) (n int, err error)
メソッドを持つインターフェースです。データを読み込み、読み込んだバイト数とエラーを返します。io.Writer
:Write(p []byte) (n int, err error)
メソッドを持つインターフェースです。データを書き込み、書き込んだバイト数とエラーを返します。
これらのインターフェースは、様々なI/Oソース(ファイル、ネットワーク接続、メモリなど)に対して統一的な操作を提供し、柔軟なコード設計を可能にします。
Go言語のエラーハンドリング
Go言語では、エラーは戻り値として明示的に扱われます。関数は通常、最後の戻り値としてerror
型の値を返します。nil
はエラーがないことを意味し、非nil
の値はエラーが発生したことを示します。このアプローチにより、エラー処理がコード内で明示的になり、エラーの見落としを防ぐことができます。
I/O操作においては、io.EOF
(ファイルの終端)や、ネットワークエラー、ディスクエラーなど、様々な種類のエラーが発生する可能性があります。bufio
パッケージも、基盤となるio.Reader
やio.Writer
から受け取ったエラーを適切に伝播させる必要があります。
Go言語のテスト
Go言語のテストは、testing
パッケージを使用して行われます。テスト関数はTestXxx(*testing.T)
というシグネチャを持ち、go test
コマンドで実行されます。テストコードは通常、テスト対象のパッケージと同じディレクトリに_test.go
というサフィックスを持つファイルとして配置されます。
技術的詳細
このコミットでは、bufio_test.go
ファイルに以下の2つの主要な変更が加えられています。
-
TestWriteErrors
の改善: 既存のTestWriteErrors
関数は、bufio.Writer
が書き込みエラーに遭遇した際に、そのエラーが正しく報告されることを検証していました。この変更では、エラーが発生した後にFlush()
メソッドを2回呼び出すように修正されています。 これは、bufio.Writer
が一度エラー状態になった場合、そのエラーが「スティッキー(sticky)」である、つまり後続の操作でも同じエラーが報告され続けることを確認するためです。これにより、ライターが一度壊れた状態になったら、その状態が維持されることを保証します。 -
TestReaderClearError
の追加: この新しいテストは、bufio.Reader
のエラーハンドリング、特にエラー状態からの回復またはエラーの伝播の挙動を検証するために導入されました。errorThenGoodReader
構造体: このテストのために、カスタムのio.Reader
実装であるerrorThenGoodReader
が定義されています。このリーダーは、最初のRead
呼び出しでは事前に定義されたerrFake
というエラーを返し、2回目以降のRead
呼び出しでは成功(要求されたバイト数を読み込んだと報告)します。- テストシナリオ:
NewReader
でerrorThenGoodReader
をラップしたbufio.Reader
を作成します。Read(nil)
を呼び出し、nil
バッファでの読み込みがエラーを返さないことを確認します。これは、bufio.Reader
がnil
バッファで呼び出された場合に、内部状態を変更せずにエラーを返さないという期待される挙動をテストします。Read(buf)
を呼び出し、errorThenGoodReader
が返す最初のerrFake
がbufio.Reader
によって正しく伝播されることを確認します。- 再度
Read(nil)
を呼び出し、エラーがクリアされていないことを確認します(bufio.Reader
はエラーを内部に保持しているため)。 - 3回目の
Read(buf)
を呼び出し、この時点でerrorThenGoodReader
は成功を返すため、bufio.Reader
も成功を返すことを確認します。これは、bufio.Reader
が基盤となるリーダーのエラー状態が回復したときに、自身も回復できることを示唆しています。 - 最後に、基盤となる
errorThenGoodReader
のRead
メソッドが何回呼び出されたか(r.nread
)を検証し、期待される回数(2回)であることを確認します。これは、bufio.Reader
が不必要な読み込みを行っていないことを保証します。
これらのテストは、bufio
パッケージが様々なエラーシナリオにおいて、予測可能で正しい挙動を示すことを保証するために重要です。
コアとなるコードの変更箇所
変更はすべてsrc/pkg/bufio/bufio_test.go
ファイル内で行われています。
--- a/src/pkg/bufio/bufio_test.go
+++ b/src/pkg/bufio/bufio_test.go
@@ -7,6 +7,7 @@ package bufio_test
import (
. "bufio"
"bytes"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -434,9 +435,12 @@ func TestWriteErrors(t *testing.T) {
t.Errorf("Write hello to %v: %v", w, e)
continue
}
- e = buf.Flush()
- if e != w.expect {
- t.Errorf("Flush %v: got %v, wanted %v", w, e, w.expect)
+ // Two flushes, to verify the error is sticky.
+ for i := 0; i < 2; i++ {
+ e = buf.Flush()
+ if e != w.expect {
+ t.Errorf("Flush %d/2 %v: got %v, wanted %v", i+1, w, e, w.expect)
+ }
}
}
}
@@ -962,6 +966,43 @@ func TestNegativeRead(t *testing.T) {
b.Read(make([]byte, 100))
}
+var errFake = errors.New("fake error")
+
+type errorThenGoodReader struct {
+ didErr bool
+ nread int
+}
+
+func (r *errorThenGoodReader) Read(p []byte) (int, error) {
+ r.nread++
+ if !r.didErr {
+ r.didErr = true
+ return 0, errFake
+ }
+ return len(p), nil
+}
+
+func TestReaderClearError(t *testing.T) {
+ r := &errorThenGoodReader{}
+ b := NewReader(r)
+ buf := make([]byte, 1)
+ if _, err := b.Read(nil); err != nil {
+ t.Fatalf("1st nil Read = %v; want nil", err)
+ }
+ if _, err := b.Read(buf); err != errFake {
+ t.Fatalf("1st Read = %v; want errFake", err)
+ }
+ if _, err := b.Read(nil); err != nil {
+ t.Fatalf("2nd nil Read = %v; want nil", err)
+ }
+ if _, err := b.Read(buf); err != nil {
+ t.Fatalf("3rd Read with buffer = %v; want nil", err)\n+\t}\n+\tif r.nread != 2 {\n+\t\tt.Errorf("num reads = %d; want 2", r.nread)
+ }
+}
+
// An onlyReader only implements io.Reader, no matter what other methods the underlying implementation may have.
type onlyReader struct {
r io.Reader
コアとなるコードの解説
TestWriteErrors
の変更点
元のコードでは、buf.Flush()
が1回だけ呼び出されていました。変更後では、for i := 0; i < 2; i++
ループが追加され、Flush()
が2回呼び出されるようになりました。これにより、bufio.Writer
が一度エラー状態になった後も、そのエラーが持続的に報告されることを確認しています。これは、ライターがエラー状態を適切に「記憶」し、後続の操作に影響を与えるべきであることを保証するための重要なテストです。
TestReaderClearError
の新規追加
var errFake = errors.New("fake error")
: テストで使用するカスタムエラーを定義しています。type errorThenGoodReader struct { ... }
:didErr bool
: 最初の読み込みでエラーを返したかどうかを追跡するフラグ。nread int
:Read
メソッドが呼び出された回数をカウントするカウンター。Read(p []byte) (int, error)
メソッドの実装:r.nread++
: 呼び出し回数をインクリメント。if !r.didErr
: まだエラーを返していない場合、r.didErr
をtrue
に設定し、0
バイトとerrFake
を返します。return len(p), nil
: 2回目以降の呼び出しでは、要求されたバッファサイズ(len(p)
)を読み込んだとしてnil
エラーを返します。
func TestReaderClearError(t *testing.T) { ... }
:r := &errorThenGoodReader{}
: カスタムリーダーのインスタンスを作成。b := NewReader(r)
:bufio.Reader
でカスタムリーダーをラップ。buf := make([]byte, 1)
: 読み込み用の1バイトバッファを作成。1st nil Read
:b.Read(nil)
を呼び出し、nil
バッファでの読み込みがエラーを返さないことを確認。これは、bufio.Reader
がnil
バッファを渡されたときに、内部状態を変更したりエラーを発生させたりしないという期待される挙動をテストします。1st Read
:b.Read(buf)
を呼び出し、errorThenGoodReader
が返すerrFake
がbufio.Reader
から正しく返されることを確認。2nd nil Read
: 再度b.Read(nil)
を呼び出し、エラーがまだ存在することを確認。これは、bufio.Reader
が一度エラーを受け取ると、そのエラーを内部に保持し続けることを示します。3rd Read with buffer
:b.Read(buf)
を呼び出し、この時点で基盤のerrorThenGoodReader
は成功を返すため、bufio.Reader
も成功を返すことを確認。これは、bufio.Reader
が基盤となるリーダーのエラー状態が回復したときに、自身も回復できることを示します。r.nread != 2
: 最後に、基盤となるerrorThenGoodReader
のRead
メソッドが正確に2回呼び出されたことを検証。これは、bufio.Reader
がバッファリングのために不必要な基盤リーダーの呼び出しを行っていないことを保証します。
これらの変更は、bufio
パッケージの堅牢性と信頼性を高める上で非常に重要であり、特にエラーハンドリングの側面を詳細にテストしています。
関連リンク
- Go言語の
bufio
パッケージのドキュメント: https://pkg.go.dev/bufio - Go言語の
io
パッケージのドキュメント: https://pkg.go.dev/io - Go言語の
errors
パッケージのドキュメント: https://pkg.go.dev/errors - Go言語の
testing
パッケージのドキュメント: https://pkg.go.dev/testing
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコードリポジトリ (GitHub)
- Gerrit Code Review (Goプロジェクトのコードレビューシステム): https://go-review.googlesource.com/ (コミットメッセージにある
https://golang.org/cl/7927044
は、このGerritの変更リストへのリンクです。) - Go言語のテストに関する公式ブログ記事やチュートリアル