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

[インデックス 17703] ファイルの概要

このコミットは、Go言語のコマンドラインツール go におけるテスト実行時の不具合を修正するものです。具体的には、go test -c -test.bench=XXX fmt のようなコマンドがハングアップする問題を解決します。この問題は、ベンチマークテストのバイナリを生成する際に、特定の条件下でテストプロセスが終了しないことに起因していました。

コミット

commit 752960aa5c6ec12ca4d73b1d5b552466fdd77cce
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed Sep 25 16:18:33 2013 -0400

    cmd/go: "go test -c -test.bench=XX fmt" shouldn't hang
    Fixes #6480.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/13925043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/752960aa5c6ec12ca4d73b1d5b552466fdd77cce

元コミット内容

cmd/go: "go test -c -test.bench=XX fmt" shouldn't hang
Fixes #6480.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/13925043

変更の背景

このコミットは、Go言語のIssue #6480で報告された問題に対応しています。ユーザーが go test -c -test.bench=XXX <package> の形式でコマンドを実行した際に、プロセスがハングアップし、終了しないという問題が発生していました。

go test -c は、テストバイナリをコンパイルするだけで実行しないオプションです。一方、-test.bench=XXX はベンチマークテストを実行するためのパターンを指定するオプションです。通常、これらのオプションは組み合わせて使用されることはありません。なぜなら、-c は実行しないことを意味し、-test.bench は実行することを意味するからです。

しかし、Goのテストツール go test の内部ロジックでは、-test.bench オプションが指定された場合、テストの実行を前提とした処理パスに入ってしまい、-c オプションによる「コンパイルのみ」の意図が正しく反映されないケースがありました。特に、ベンチマークテストの実行を制御する内部フラグ testBenchtrue に設定されているにもかかわらず、テストバイナリの実行自体は抑制されているため、テストランナーが次のステップに進まずに待機状態となり、結果としてコマンドがハングアップしていました。

このハングアップは、CI/CD環境や自動化されたスクリプトでこのようなコマンドが誤って実行された場合に、ビルドプロセスが停止してしまうなどの深刻な影響を及ぼす可能性がありました。そのため、この不整合を解消し、go test コマンドの堅牢性を向上させる必要がありました。

前提知識の解説

このコミットを理解するためには、以下のGo言語のテストおよびビルドに関する知識が必要です。

  1. go test コマンド: Go言語の標準的なテスト実行ツールです。パッケージ内のテスト関数(TestXxx)、ベンチマーク関数(BenchmarkXxx)、サンプル関数(ExampleXxx)を実行します。

  2. go test -c オプション: このオプションは、テストバイナリをコンパイルしますが、実行はしません。コンパイルされたテストバイナリは、通常、カレントディレクトリに <package_name>.test という名前で保存されます。このバイナリは後で手動で実行することができます。例えば、go test -c -o mytest.exe mypackage とすると、mypackage のテストバイナリが mytest.exe として生成されます。

  3. go test -test.bench=PATTERN オプション: このオプションは、ベンチマークテストを実行するために使用されます。PATTERN には正規表現を指定し、そのパターンにマッチするベンチマーク関数のみが実行されます。ベンチマークテストは、関数の実行時間を測定し、パフォーマンスを評価するために使用されます。

  4. fmt パッケージ: Go言語の標準ライブラリの一つで、フォーマットI/O(入出力)を提供します。fmt.Printffmt.Println など、Goプログラムで最も頻繁に使用される関数が含まれています。このコミットの例では、fmt パッケージのテストを実行しようとしています。

  5. test.bash: Goプロジェクトのテストスクリプトの一部で、シェルスクリプトで書かれています。Goのビルドシステムやテストインフラストラクチャのテストケースを定義し、様々な go コマンドの挙動を検証するために使用されます。

  6. test.go: cmd/go ディレクトリ内のGoソースファイルで、go test コマンドの内部ロジックを実装しています。テストの実行フロー、オプションのパース、テストバイナリのビルドと実行の制御などが行われます。

技術的詳細

問題の根本原因は、src/cmd/go/test.go 内の runTest 関数にありました。この関数は go test コマンドの主要な実行ロジックを含んでいます。

以前のコードでは、testBench という内部フラグが true の場合(つまり、-test.bench オプションが指定された場合)、テストの実行をシリアル化するためのロジックが常に適用されていました。このシリアル化ロジックは、複数のベンチマークテストの実行順序を制御し、結果の出力が混ざらないようにするために存在します。

しかし、go test -c オプションが指定された場合、テストバイナリはコンパイルされるだけで実行はされません。この「実行しない」という意図は、testC という別の内部フラグによって示されます。

問題は、testCtrue であっても、testBenchtrue であれば、シリアル化ロジックが実行されてしまう点にありました。シリアル化ロジックは、テストの「実行」が完了するのを待機するような構造になっており、テストが実際に実行されない(-c オプションのため)場合、永遠に待機し続けてしまい、結果としてコマンドがハングアップしていました。

このコミットでは、この論理的な矛盾を解消するために、シリアル化ロジックの適用条件を変更しました。具体的には、testBenchtrue であり、かつ testCfalse である場合にのみ、シリアル化ロジックが適用されるように修正されました。これにより、-c オプションが指定されている場合は、ベンチマークオプションが指定されていてもテストの実行待機ロジックがスキップされ、ハングアップが回避されます。

また、src/cmd/go/test.bash には、この修正を検証するための新しいテストケースが追加されました。このテストケースは、実際に go test -c -test.bench=XXX fmt コマンドを実行し、それがハングアップせずに正常に終了することを確認します。

コアとなるコードの変更箇所

このコミットによる変更は、主に以下の2つのファイルにわたります。

  1. src/cmd/go/test.bash:

    • テストスクリプトに新しいテストケースが追加されました。
    • 既存のテストケースで生成された一時ファイルのクリーンアップ処理が修正されました。
  2. src/cmd/go/test.go:

    • runTest 関数内の条件分岐が変更されました。

src/cmd/go/test.bash の変更点

--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -408,7 +408,7 @@ if ! grep -q '^hello world' hello.out; then
 	cat hello.out
 	ok=false
 fi
-rm -rf $d
+rm -rf $d hello.out
 
 TEST go test -cpuprofile leaves binary behind
 ./testgo test -cpuprofile strings.prof strings || ok=false
@@ -613,6 +613,13 @@ fi
 rm -rf $d
 unset GOPATH
 
+TEST 'Issue 6480: "go test -c -test.bench=XXX fmt" should not hang'
+if ! ./testgo test -c -test.bench=XXX fmt; then
+\techo build test failed
+\tok=false
+fi
+rm -f fmt.test
+\
 # clean up
 if $started; then stop; fi
 rm -rf testdata/bin testdata/bin1

src/cmd/go/test.go の変更点

--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -449,7 +449,7 @@ func runTest(cmd *Command, args []string) {
 	}
 
 	// Force benchmarks to run in serial.
-	if testBench {
+	if !testC && testBench {
 		// The first run must wait for all builds.
 		// Later runs must wait for the previous run's print.
 		for i, run := range runs {

コアとなるコードの解説

src/cmd/go/test.bash の解説

  1. rm -rf $d hello.out: 既存のテストケースで生成された hello.out ファイルのクリーンアップが追加されました。これは、以前のコミットで生成された可能性のある一時ファイルが、後続のテストに影響を与えないようにするための一般的なクリーンアップ処理です。

  2. 新しいテストケースの追加:

    TEST 'Issue 6480: "go test -c -test.bench=XXX fmt" should not hang'
    if ! ./testgo test -c -test.bench=XXX fmt; then
    	echo build test failed
    	ok=false
    fi
    rm -f fmt.test
    

    このブロックは、Issue #6480で報告された問題を直接検証するためのものです。

    • TEST '...': テストケースのタイトルを定義します。
    • ./testgo test -c -test.bench=XXX fmt: 修正された go test コマンドを実行します。./testgo は、Goのテストスクリプト内でビルドされた go コマンドのバイナリを指します。
      • -c: テストバイナリをコンパイルするが実行しない。
      • -test.bench=XXX: ベンチマークテストのパターンを指定。XXX は任意のパターンで、ベンチマークテストの実行をトリガーする目的で使用されます。
      • fmt: テスト対象のパッケージ。
    • if ! ...; then ... fi: コマンドが失敗した場合(つまり、ハングアップしたり、エラーで終了したりした場合)にエラーメッセージを出力し、テスト全体の ok フラグを false に設定します。これにより、テストが失敗したことが報告されます。
    • rm -f fmt.test: go test -c によって生成された fmt.test バイナリをクリーンアップします。

この新しいテストケースの追加により、将来的に同様の回帰バグが発生することを防ぎ、修正が正しく機能していることを継続的に検証できるようになります。

src/cmd/go/test.go の解説

// Force benchmarks to run in serial.
-	if testBench {
+	if !testC && testBench {
		// The first run must wait for all builds.
		// Later runs must wait for the previous run's print.
		for i, run := range runs {

この変更は runTest 関数内で行われています。

  • 変更前: if testBench { ... } testBench フラグが true であれば、常にベンチマークのシリアル実行ロジックが適用されていました。これは、-test.bench オプションが指定された場合に testBenchtrue になるため、-c オプションの有無にかかわらずこのブロックが実行されていました。

  • 変更後: if !testC && testBench { ... } 条件が !testC && testBench に変更されました。

    • !testC: testC フラグが false であること、つまり go test -c オプションが指定されていないことを意味します。
    • testBench: testBench フラグが true であること、つまり -test.bench オプションが指定されていることを意味します。

この新しい条件により、ベンチマークのシリアル実行ロジックは、ベンチマークオプションが指定されており、かつ、テストバイナリのコンパイルのみではなく実際に実行される場合にのみ適用されるようになります。

これにより、go test -c -test.bench=XXX のように -c オプションが指定されている場合は、!testCfalse となり、if 文の条件全体が false になるため、シリアル実行ロジックのブロックがスキップされます。結果として、テストランナーが実行されないテストの完了を待機することがなくなり、ハングアップが解消されます。

関連リンク

  • Go CL (Code Review) 13925043: https://golang.org/cl/13925043
  • Go Issue #6480: このコミットが修正した元の問題のIssue番号。GoのIssueトラッカーで詳細を確認できます。

参考にした情報源リンク

  • Go Issue #6480 (直接のリンクは提供されていませんが、コミットメッセージに記載されています)
  • Go言語の go test コマンドに関する公式ドキュメント (一般的な情報源として)
  • Go言語のソースコード (特に src/cmd/go/test.gosrc/cmd/go/test.bash の変更前後のコード)
  • Web検索: "Go issue 6480" (関連する情報や議論を探すため)
    • 検索結果は、より新しい関連Issue #68691を示唆していましたが、このコミットが直接修正しているのは #6480 です。