Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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操作をバッファリングすることで効率を向上させるための機能を提供します。主な型としてReaderWriterがあります。

  • bufio.Reader: io.Readerインターフェースをラップし、内部バッファを使用して読み込み操作を最適化します。例えば、一度に多くのバイトを読み込んでバッファに格納し、その後の小さな読み込み要求に対してはバッファからデータを提供することで、基盤となるI/O操作の回数を減らします。
  • bufio.Writer: io.Writerインターフェースをラップし、内部バッファにデータを書き込み、バッファが満杯になったときやFlushメソッドが呼び出されたときに、まとめて基盤となるio.Writerに書き込みます。これにより、書き込み操作の回数を減らし、パフォーマンスを向上させます。

io.Readerio.Writerインターフェース

Go言語では、I/O操作はio.Readerio.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.Readerio.Writerから受け取ったエラーを適切に伝播させる必要があります。

Go言語のテスト

Go言語のテストは、testingパッケージを使用して行われます。テスト関数はTestXxx(*testing.T)というシグネチャを持ち、go testコマンドで実行されます。テストコードは通常、テスト対象のパッケージと同じディレクトリに_test.goというサフィックスを持つファイルとして配置されます。

技術的詳細

このコミットでは、bufio_test.goファイルに以下の2つの主要な変更が加えられています。

  1. TestWriteErrorsの改善: 既存のTestWriteErrors関数は、bufio.Writerが書き込みエラーに遭遇した際に、そのエラーが正しく報告されることを検証していました。この変更では、エラーが発生した後にFlush()メソッドを2回呼び出すように修正されています。 これは、bufio.Writerが一度エラー状態になった場合、そのエラーが「スティッキー(sticky)」である、つまり後続の操作でも同じエラーが報告され続けることを確認するためです。これにより、ライターが一度壊れた状態になったら、その状態が維持されることを保証します。

  2. TestReaderClearErrorの追加: この新しいテストは、bufio.Readerのエラーハンドリング、特にエラー状態からの回復またはエラーの伝播の挙動を検証するために導入されました。

    • errorThenGoodReader構造体: このテストのために、カスタムのio.Reader実装であるerrorThenGoodReaderが定義されています。このリーダーは、最初のRead呼び出しでは事前に定義されたerrFakeというエラーを返し、2回目以降のRead呼び出しでは成功(要求されたバイト数を読み込んだと報告)します。
    • テストシナリオ:
      • NewReadererrorThenGoodReaderをラップしたbufio.Readerを作成します。
      • Read(nil)を呼び出し、nilバッファでの読み込みがエラーを返さないことを確認します。これは、bufio.Readernilバッファで呼び出された場合に、内部状態を変更せずにエラーを返さないという期待される挙動をテストします。
      • Read(buf)を呼び出し、errorThenGoodReaderが返す最初のerrFakebufio.Readerによって正しく伝播されることを確認します。
      • 再度Read(nil)を呼び出し、エラーがクリアされていないことを確認します(bufio.Readerはエラーを内部に保持しているため)。
      • 3回目のRead(buf)を呼び出し、この時点でerrorThenGoodReaderは成功を返すため、bufio.Readerも成功を返すことを確認します。これは、bufio.Readerが基盤となるリーダーのエラー状態が回復したときに、自身も回復できることを示唆しています。
      • 最後に、基盤となるerrorThenGoodReaderReadメソッドが何回呼び出されたか(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の新規追加

  1. var errFake = errors.New("fake error"): テストで使用するカスタムエラーを定義しています。
  2. type errorThenGoodReader struct { ... }:
    • didErr bool: 最初の読み込みでエラーを返したかどうかを追跡するフラグ。
    • nread int: Readメソッドが呼び出された回数をカウントするカウンター。
    • Read(p []byte) (int, error)メソッドの実装:
      • r.nread++: 呼び出し回数をインクリメント。
      • if !r.didErr: まだエラーを返していない場合、r.didErrtrueに設定し、0バイトとerrFakeを返します。
      • return len(p), nil: 2回目以降の呼び出しでは、要求されたバッファサイズ(len(p))を読み込んだとしてnilエラーを返します。
  3. 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.Readernilバッファを渡されたときに、内部状態を変更したりエラーを発生させたりしないという期待される挙動をテストします。
    • 1st Read: b.Read(buf)を呼び出し、errorThenGoodReaderが返すerrFakebufio.Readerから正しく返されることを確認。
    • 2nd nil Read: 再度b.Read(nil)を呼び出し、エラーがまだ存在することを確認。これは、bufio.Readerが一度エラーを受け取ると、そのエラーを内部に保持し続けることを示します。
    • 3rd Read with buffer: b.Read(buf)を呼び出し、この時点で基盤のerrorThenGoodReaderは成功を返すため、bufio.Readerも成功を返すことを確認。これは、bufio.Readerが基盤となるリーダーのエラー状態が回復したときに、自身も回復できることを示します。
    • r.nread != 2: 最後に、基盤となるerrorThenGoodReaderReadメソッドが正確に2回呼び出されたことを検証。これは、bufio.Readerがバッファリングのために不必要な基盤リーダーの呼び出しを行っていないことを保証します。

これらの変更は、bufioパッケージの堅牢性と信頼性を高める上で非常に重要であり、特にエラーハンドリングの側面を詳細にテストしています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ (GitHub)
  • Gerrit Code Review (Goプロジェクトのコードレビューシステム): https://go-review.googlesource.com/ (コミットメッセージにあるhttps://golang.org/cl/7927044は、このGerritの変更リストへのリンクです。)
  • Go言語のテストに関する公式ブログ記事やチュートリアル