[インデックス 13441] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http パッケージ内のテストコードのクリーンアップと改善を目的としています。具体的には、ファイルシステム関連のテスト (fs_test.go) の整理と、govet ツールによって検出されたフォーマット引数の不一致問題の修正が含まれています。また、serve_test.go 内のログ出力のフォーマットも修正されています。
コミット
- コミットハッシュ:
55cc1ff721aaf14b27df31efcf0295c4ff09e35a - 作者: Brad Fitzpatrick bradfitz@golang.org
- コミット日時: 2012年7月3日 火曜日 10:17:55 -0700
- コミットメッセージ:
net/http: clean up fs tests a bit And fix some govet-caught format arg issues. R=r CC=golang-dev https://golang.org/cl/6354068
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/55cc1ff721aaf14b27df31efcf0295c4ff09e35a
元コミット内容
net/http: clean up fs tests a bit
And fix some govet-caught format arg issues.
R=r
CC=golang-dev
https://golang.org/cl/6354068
変更の背景
このコミットの主な背景は、Go言語の標準ライブラリの品質向上とテストの堅牢性強化にあります。
govetによる指摘の対応:govetはGo言語のコードを静的に解析し、疑わしい構造や潜在的なバグを検出するツールです。このコミットでは、govetが指摘したfmtパッケージのフォーマット文字列と引数の不一致(例:%qが指定されているのに対応する引数がない、または型が異なる)を修正しています。このような不一致は、実行時にパニックを引き起こしたり、意図しない出力になったりする可能性があるため、修正はコードの安定性向上に寄与します。- テストの改善とクリーンアップ:
net/httpパッケージはGo言語のウェブアプリケーション開発において非常に重要なコンポーネントです。そのテストコードは、機能の正確性を保証するために非常に重要です。このコミットでは、テストのロジックをより堅牢にし、エラーハンドリングを改善することで、テストがより多くのシナリオを適切にカバーし、問題発生時に正確な情報を提供するようにしています。特に、t.Fatalfをt.Errorfに変更し、continue Casesを追加することで、一つのテストケースでエラーが発生しても、そのテスト関数内の他のテストケースの実行を継続できるように改善されています。これにより、テスト実行の効率が向上し、一度の実行でより多くの問題を発見できるようになります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とツールに関する知識が役立ちます。
net/httpパッケージ: Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。ウェブアプリケーションやAPIの構築に不可欠なパッケージです。testingパッケージ: Go言語の標準テストフレームワークです。テスト関数はTestで始まり、*testing.T型の引数を取ります。t.Errorf(...): テスト中にエラーが発生したことを報告しますが、テストの実行は継続します。t.Fatalf(...): テスト中に致命的なエラーが発生したことを報告し、現在のテスト関数を即座に終了させます。このコミットでは、一部のt.Fatalfがt.Errorfに変更されており、テストの継続性を高めています。
fmtパッケージ: フォーマットされたI/Oを実装するためのパッケージです。Printf系の関数でフォーマット文字列を使用します。- フォーマット動詞:
%v(任意の値をデフォルトフォーマットで出力),%q(文字列をダブルクォートで囲み、Go構文でエスケープして出力),%d(整数を10進数で出力) などがあります。govetはこれらの動詞と引数の型の不一致を検出します。
- フォーマット動詞:
govetツール: Go言語の静的解析ツールの一つで、コード内の疑わしい構造(例:Printfフォーマット文字列の誤用、到達不能なコード、ロックの誤用など)を検出します。GoのビルドプロセスやCI/CDパイプラインでよく使用され、コード品質の維持に貢献します。- HTTP Range ヘッダー: クライアントがサーバーに対して、リソース全体ではなく、その一部(バイト範囲)のみを要求するために使用するHTTPヘッダーです。例えば、動画のストリーミングやダウンロードの再開などに利用されます。
multipart/byteranges: HTTP Range ヘッダーで複数のバイト範囲が要求された場合、サーバーはContent-Type: multipart/byterangesを使用して、各範囲のデータをマルチパート形式で返します。logパッケージ: Go言語の標準ロギングパッケージです。log.Panicf(...): フォーマットされた文字列をログに出力し、その後panicを発生させます。これは通常、回復不可能なエラーを示すために使用されます。
技術的詳細
このコミットで行われた技術的な変更は、主に以下の2つのカテゴリに分けられます。
-
govetによるフォーマット引数不一致の修正:src/pkg/net/http/fs_test.goおよびsrc/pkg/net/http/serve_test.go内のt.Errorfやlog.Panicfの呼び出しにおいて、フォーマット文字列 (%q,%vなど) に対応する引数が不足していたり、誤っていたりする箇所が修正されました。例えば、t.Errorf("range=%q content-type = %q; unexpected multipart/byteranges", rt.r)という行では、%qが2つあるにもかかわらず、引数がrt.rの1つしかなかったため、2つ目の%qに対応するctが追加されました。これにより、govetの警告が解消され、実行時の出力が意図通りになります。log.Panicfの呼び出しでも同様に、エラーオブジェクト (err) やボディ (body) を適切にフォーマットするために%vや%qが追加されました。
-
テストロジックの改善と堅牢化:
t.Fatalfからt.Errorfへの変更とcontinue Casesの導入:fs_test.go内のTestServeFile関数において、multipart.NewReaderからのパート読み込みエラーや、パートボディの読み込みエラーが発生した場合に、以前はt.Fatalfを使用してテスト関数全体を終了させていました。しかし、この変更によりt.Errorfに変更され、さらにcontinue Casesというラベル付きcontinue文が追加されました。continue Casesは、外側のforループ(Cases:ラベルが付いているループ)の次のイテレーションに進むことを意味します。これにより、あるレンジテストケースでエラーが発生しても、そのテスト関数内の他のレンジテストケースの実行が中断されずに継続されるようになります。これは、テストの網羅性を高め、一度のテスト実行でより多くの問題を特定できるようにするための重要な改善です。
- 早期
continueの追加:multipart/byterangesのContent-Typeが期待通りでない場合や、boundaryが不足している場合に、エラーを報告した後にcontinueを追加することで、その後の無意味な処理をスキップし、テストのロジックをより明確にしています。 Content-Rangeヘッダーのチェック順序の変更: マルチパートの各パートのContent-Rangeヘッダーのチェックが、ボディの比較の前に移動されました。これは論理的な順序の改善であり、ヘッダーが正しくない場合はボディの比較に進む必要がないため、テストの効率と可読性が向上します。
これらの変更は、Go言語のテストプラクティスにおけるベストプラクティスに沿ったものであり、テストの信頼性と保守性を高めるものです。
コアとなるコードの変更箇所
src/pkg/net/http/fs_test.go
--- a/src/pkg/net/http/fs_test.go
+++ b/src/pkg/net/http/fs_test.go
@@ -81,6 +81,7 @@ func TestServeFile(t *testing.T) {
}
// Range tests
+Cases:
for _, rt := range ServeFileRangeTests {
\tif rt.r != "" {
\t\treq.Header.Set("Range", rt.r)
@@ -109,7 +110,7 @@ func TestServeFile(t *testing.T) {
\t\t\tt.Errorf("range=%q: body = %q, want %q", rt.r, body, wantBody)
\t\t\t}
\t\tif strings.HasPrefix(ct, "multipart/byteranges") {
-\t\t\t\tt.Errorf("range=%q content-type = %q; unexpected multipart/byteranges", rt.r)
+\t\t\t\tt.Errorf("range=%q content-type = %q; unexpected multipart/byteranges", rt.r, ct)
\t\t}
\t}
\tif len(rt.ranges) > 1 {
@@ -119,37 +120,41 @@ func TestServeFile(t *testing.T) {
\t\t\t\tcontinue
\t\t\t}
\t\t\tif typ != "multipart/byteranges" {
-\t\t\t\t\tt.Errorf("range=%q content-type = %q; want multipart/byteranges", rt.r)
+\t\t\t\t\tt.Errorf("range=%q content-type = %q; want multipart/byteranges", rt.r, typ)
\t\t\t\tcontinue
\t\t\t}
\t\t\tif params["boundary"] == "" {
\t\t\t\tt.Errorf("range=%q content-type = %q; lacks boundary", rt.r, ct)
+\t\t\t\t\tcontinue
\t\t\t}
\t\t\tif g, w := resp.ContentLength, int64(len(body)); g != w {
\t\t\t\tt.Errorf("range=%q Content-Length = %d; want %d", rt.r, g, w)
+\t\t\t\t\tcontinue
\t\t\t}
\t\t\tmr := multipart.NewReader(bytes.NewReader(body), params["boundary"])
\t\t\tfor ri, rng := range rt.ranges {\
\t\t\t\tpart, err := mr.NextPart()
\t\t\t\tif err != nil {\
-\t\t\t\t\t\tt.Fatalf("range=%q, reading part index %d: %v", rt.r, ri, err)
+\t\t\t\t\t\tt.Errorf("range=%q, reading part index %d: %v", rt.r, ri, err)
+\t\t\t\t\t\tcontinue Cases
+\t\t\t\t\t}
+\t\t\t\t\twantContentRange = fmt.Sprintf("bytes %d-%d/%d", rng.start, rng.end-1, testFileLen)
+\t\t\t\t\tif g, w := part.Header.Get("Content-Range"), wantContentRange; g != w {
+\t\t\t\t\t\tt.Errorf("range=%q: part Content-Range = %q; want %q", rt.r, g, w)
\t\t\t\t}
\t\t\t\tbody, err := ioutil.ReadAll(part)
\t\t\t\tif err != nil {\
-\t\t\t\t\t\tt.Fatalf("range=%q, reading part index %d body: %v", rt.r, ri, err)
+\t\t\t\t\t\tt.Errorf("range=%q, reading part index %d body: %v", rt.r, ri, err)
+\t\t\t\t\t\tcontinue Cases
\t\t\t\t}
-\t\t\t\twantContentRange = fmt.Sprintf("bytes %d-%d/%d", rng.start, rng.end-1, testFileLen)
\t\t\t\twantBody := file[rng.start:rng.end]
\t\t\t\tif !bytes.Equal(body, wantBody) {\
\t\t\t\t\tt.Errorf("range=%q: body = %q, want %q", rt.r, body, wantBody)
\t\t\t\t}
-\t\t\t\t\tif g, w := part.Header.Get("Content-Range"), wantContentRange; g != w {\
-\t\t\t\t\t\tt.Errorf("range=%q: part Content-Range = %q; want %q", rt.r, g, w)
-\t\t\t\t\t}
\t\t\t}
\t\t\t_, err = mr.NextPart()
\t\t\tif err != io.EOF {\
-\t\t\t\t\tt.Errorf("range=%q; expected final error io.EOF; got %v", err)
+\t\t\t\t\tt.Errorf("range=%q; expected final error io.EOF; got %v", rt.r, err)
\t\t\t}
\t\t}
\t}
src/pkg/net/http/serve_test.go
--- a/src/pkg/net/http/serve_test.go
+++ b/src/pkg/net/http/serve_test.go
@@ -1283,15 +1283,15 @@ func BenchmarkServer(b *testing.B) {
\t\tfor i := 0; i < n; i++ {\
\t\t\tres, err := Get(url)
\t\t\tif err != nil {\
-\t\t\t\t\tlog.Panicf("Get:", err)
+\t\t\t\t\tlog.Panicf("Get: %v", err)
\t\t\t}
\t\t\tall, err := ioutil.ReadAll(res.Body)
\t\t\tif err != nil {\
-\t\t\t\t\tlog.Panicf("ReadAll:", err)
+\t\t\t\t\tlog.Panicf("ReadAll: %v", err)
\t\t\t}
\t\t\tbody := string(all)
\t\t\tif body != "Hello world.\\n" {\
-\t\t\t\t\tlog.Panicf("Got body:", body)
+\t\t\t\t\tlog.Panicf("Got body: %q", body)
\t\t\t}
\t\t}
\t\tos.Exit(0)
コアとなるコードの解説
src/pkg/net/http/fs_test.go の変更点
Cases:ラベルの追加:for _, rt := range ServeFileRangeTestsループの直前にCases:というラベルが追加されました。これは、continue Cases文のターゲットとして使用されます。t.Errorfのフォーマット引数修正:t.Errorf("range=%q content-type = %q; unexpected multipart/byteranges", rt.r)→t.Errorf("range=%q content-type = %q; unexpected multipart/byteranges", rt.r, ct): 2つ目の%qに対応するct変数が追加されました。t.Errorf("range=%q content-type = %q; want multipart/byteranges", rt.r)→t.Errorf("range=%q content-type = %q; want multipart/byteranges", rt.r, typ): 同様に、2つ目の%qに対応するtyp変数が追加されました。t.Errorf("range=%q; expected final error io.EOF; got %v", err)→t.Errorf("range=%q; expected final error io.EOF; got %v", rt.r, err):%qに対応するrt.rが追加されました。
- 早期
continueの追加:if params["boundary"] == "" { ... continue }:multipart/byterangesのContent-Typeにboundaryが含まれていない場合、エラーを報告した後にループの次のイテレーションに進むようになりました。これにより、boundaryがない状態でmultipart.NewReaderを呼び出すことを避け、テストのロジックがより堅牢になります。if g, w := resp.ContentLength, int64(len(body)); g != w { ... continue }:Content-Lengthが期待値と異なる場合も同様に、次のイテレーションに進むようになりました。
t.Fatalfからt.Errorfへの変更とcontinue Cases:t.Fatalf("range=%q, reading part index %d: %v", rt.r, ri, err)→t.Errorf("range=%q, reading part index %d: %v", rt.r, ri, err)とcontinue Cases: マルチパートのパート読み込みエラーが発生した場合、以前はテスト関数全体が終了していましたが、t.Errorfに変更されたことでエラーを報告しつつテストは継続されます。さらにcontinue Casesによって、現在のレンジテストケースの残りの処理をスキップし、次のレンジテストケースの検証に進むことができます。これにより、一つのエラーでテストスイート全体が停止するのを防ぎ、より多くのテスト結果を得られるようになります。t.Fatalf("range=%q, reading part index %d body: %v", rt.r, ri, err)→t.Errorf("range=%q, reading part index %d body: %v", rt.r, ri, err)とcontinue Cases: パートのボディ読み込みエラーについても同様の変更が適用されています。
wantContentRangeの計算とチェック順序の変更:wantContentRange = fmt.Sprintf("bytes %d-%d/%d", rng.start, rng.end-1, testFileLen)の行が、part, err := mr.NextPart()の直後に移動しました。これにより、Content-Rangeヘッダーの期待値がより早く計算され、その直後にpart.Header.Get("Content-Range")のチェックが行われるようになりました。これは論理的な順序の改善であり、テストの可読性を向上させます。
src/pkg/net/http/serve_test.go の変更点
log.Panicfのフォーマット引数修正:log.Panicf("Get:", err)→log.Panicf("Get: %v", err): エラーオブジェクトerrを適切にフォーマットするために%vが追加されました。log.Panicf("ReadAll:", err)→log.Panicf("ReadAll: %v", err): 同様に、エラーオブジェクトerrを適切にフォーマットするために%vが追加されました。log.Panicf("Got body:", body)→log.Panicf("Got body: %q", body): 文字列bodyをGo構文でエスケープして出力するために%qが追加されました。
これらの変更は、govet の指摘に対応し、テストの出力がより正確で情報量が多くなるようにするためのものです。
関連リンク
- Go CL 6354068: https://golang.org/cl/6354068
参考にした情報源リンク
- Go言語
testingパッケージ: https://pkg.go.dev/testing - Go言語
fmtパッケージ: https://pkg.go.dev/fmt - Go言語
logパッケージ: https://pkg.go.dev/log govetツール (Go公式ドキュメント): https://pkg.go.dev/cmd/vet- HTTP Range ヘッダー (MDN Web Docs): https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Range
- HTTP
Content-Type(MDN Web Docs): https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Type - Go言語のラベル付き
forループとcontinue(例): https://go.dev/tour/flowcontrol/14 (Go Tourの関連セクション)