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

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

このコミットは、Go言語のコマンドラインツール cmd/go における go test コマンドの挙動を修正するものです。具体的には、go test -i (依存関係のインストール) オプションが go test -c (テスト実行ファイルのコンパイル) オプションの機能を意図せず無効にしてしまうバグを修正しています。

コミット

commit 56ae9032b23b089100e0e9c762f0e6326ec6990a
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Mon Feb 27 12:33:14 2012 -0500

    cmd/go: test -i should not disable -c
            Fixes #3104.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5699088

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

https://github.com/golang/go/commit/56ae9032b23b089100e0e9c762f0e6326ec6990a

元コミット内容

このコミットは、cmd/go パッケージ内の test コマンドに関するものです。コミットメッセージは「cmd/go: test -i should not disable -c」とあり、go test -i オプションが go test -c オプションの機能を無効にすべきではないという問題提起と、その修正を意図しています。また、「Fixes #3104.」とあることから、GoプロジェクトのIssue 3104を解決するものであることがわかります。

変更の背景

この変更の背景には、go test コマンドのオプションの相互作用における予期せぬ挙動がありました。 go test はGo言語のテストを実行するための主要なコマンドです。このコマンドには様々なオプションがあり、その中には以下の二つが含まれます。

  • -i (install dependencies): テスト対象のパッケージとその依存関係をインストールします。これにより、テスト実行前に必要なバイナリがビルドされ、キャッシュされます。
  • -c (compile test executable): テスト実行ファイルをコンパイルしますが、実行はしません。これは、テスト実行ファイルをデバッグしたり、特定の環境で手動で実行したりする場合に便利です。

本来、これら二つのオプションは独立して機能するか、あるいは互いに補完し合うべきです。しかし、このコミットが修正する問題は、-i オプションが指定された場合に、-c オプションが意図せず無効になってしまうというものでした。つまり、ユーザーが「依存関係をインストールし、かつテスト実行ファイルをコンパイルだけしたい」と意図しても、-i-c の機能を上書きしてしまい、コンパイルが行われない、または期待通りの動作にならないというバグが存在していました。

この問題は、GoプロジェクトのIssue 3104として報告されており、このコミットはその問題を解決するために作成されました。

前提知識の解説

go test コマンド

go test はGo言語の標準的なテストツールであり、パッケージ内のテスト関数を実行するために使用されます。Goのテストは、_test.go で終わるファイルに記述された TestXxx 関数や BenchmarkXxx 関数、ExampleXxx 関数を自動的に発見し、実行します。

go test の主要なオプション

  • -i (install dependencies): このオプションは、テスト対象のパッケージとその依存関係をビルドし、インストールします。これにより、後続のテスト実行が高速化される可能性があります。通常、go test は必要に応じて依存関係を自動的にビルドしますが、-i を明示的に指定することで、ビルドプロセスを制御できます。

  • -c (compile test executable): このオプションは、テスト実行ファイルをコンパイルしますが、その実行は行いません。コンパイルされたテスト実行ファイルは、通常、現在のディレクトリに [package.test] のような名前で保存されます(例: myproject.test)。これは、テスト実行ファイルをデバッガでステップ実行したり、特定の引数を渡して手動で実行したりする場合に役立ちます。

Goのビルドプロセスとキャッシュ

Goのビルドシステムは、依存関係の管理とビルドの高速化のためにキャッシュを積極的に利用します。パッケージが一度ビルドされると、その結果はキャッシュされ、変更がない限り再ビルドは行われません。go test -i は、このキャッシュメカニズムを利用して、テスト実行前に依存関係を明示的にビルドし、キャッシュに保存します。

cmd/go パッケージ

cmd/go は、Go言語の公式ツールチェーンの一部であり、go コマンドの実装が含まれています。go buildgo rungo test など、ユーザーが日常的に使用するすべてのサブコマンドのロジックがここに定義されています。このコミットで変更された src/cmd/go/test.go ファイルは、go test コマンドの具体的な挙動を制御する部分です。

技術的詳細

このコミットの技術的詳細は、go test コマンドの内部処理、特にオプションのパースと実行フローに起因する問題と、その修正方法にあります。

元のコードでは、go test -i が指定された場合、テスト実行ファイルをコンパイルするアクション(b.do(a))の後に、無条件に return してしまうロジックが存在していました。

// src/cmd/go/test.go (変更前の一部)
func runTest(cmd *Command, args []string) {
    // ...
    if testI { // -i オプションが指定されている場合
        a.deps = append(a.deps, b.action(modeInstall, modeInstall, p))
        b.do(a) // ここでテスト実行ファイルのコンパイルと依存関係のインストールが実行される
        return // ここで関数が終了してしまう
    }
    // ...
}

この return ステートメントが問題でした。testC (つまり -c オプションが指定されているか) のチェックが行われる前に runTest 関数が終了してしまうため、-i-c が同時に指定された場合でも、-c の意図する「テスト実行ファイルのコンパイルのみ」という挙動が無視されてしまっていたのです。

修正は、この無条件の return を削除し、代わりに testC フラグの状態を確認する条件分岐を追加することによって行われました。

// src/cmd/go/test.go (変更後の一部)
func runTest(cmd *Command, args []string) {
    // ...
    if testI { // -i オプションが指定されている場合
        a.deps = append(a.deps, b.action(modeInstall, modeInstall, p))
        b.do(a)
        if !testC { // -c オプションが指定されていない場合のみ return
            return
        }
        b.init() // -c が指定されている場合は、後続の処理のために b を初期化
    }
    // ...
}

この変更により、以下の挙動が保証されます。

  1. go test -i のみの場合: b.do(a) が実行され、依存関係がインストールされた後、!testCtrue となるため return し、テスト実行ファイルのコンパイルは行われません(これは期待される挙動です)。
  2. go test -c のみの場合: testIfalse のため、この if ブロック全体がスキップされ、後続の testC 関連のロジックが実行され、テスト実行ファイルのコンパイルのみが行われます(これは期待される挙動です)。
  3. go test -i -c の場合: testItrue のため if ブロックに入り、b.do(a) が実行されます。その後、!testCfalse となるため return はスキップされ、b.init() が呼び出されます。これにより、testC の意図する「テスト実行ファイルのコンパイル」が後続の処理で適切に行われるようになります。

b.init() の呼び出しは、testC が指定されている場合に、テスト実行ファイルのコンパイルに必要な内部状態を適切に初期化するために重要です。これにより、-i-c の両方が指定された場合でも、go test が期待通りにテスト実行ファイルをコンパイルするようになります。

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

--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -299,7 +299,10 @@ func runTest(cmd *Command, args []string) {
 			a.deps = append(a.deps, b.action(modeInstall, modeInstall, p))
 		}
 		b.do(a)
-		return
+		if !testC {
+			return
+		}
+		b.init()
 	}
 
 	var builds, runs, prints []*action

コアとなるコードの解説

変更は src/cmd/go/test.go ファイルの runTest 関数内で行われています。

元のコードでは、testI (つまり go test -i オプションが指定されている) の条件ブロック内で、b.do(a) の呼び出しの直後に無条件に return がありました。

		b.do(a)
		return // ここで関数が終了していた

この return が、-c オプションが指定されていても、その後の処理が実行されない原因となっていました。

修正後のコードでは、この return の前に条件分岐が追加されています。

		b.do(a)
		if !testC { // testC (つまり -c オプション) が指定されていない場合のみ
			return // 関数を終了する
		}
		b.init() // testC が指定されている場合は、b を初期化して処理を続行する

この変更により、以下の挙動が実現されます。

  1. go test -i のみが指定された場合 (testItrue, testCfalse): b.do(a) が実行され、その後 !testCtrue となるため return が実行され、関数が終了します。これは -i のみの期待される挙動です。
  2. go test -i -c が指定された場合 (testItrue, testCtrue): b.do(a) が実行され、その後 !testCfalse となるため return はスキップされます。代わりに b.init() が呼び出され、testC オプションの意図するテスト実行ファイルのコンパイル処理が後続で適切に実行されるための準備が行われます。

b.init() は、おそらく test コマンドの内部状態を初期化し、テスト実行ファイルのコンパイルに必要な情報を設定する役割を担っています。この修正により、-i-c オプションが同時に使用された際の go test の挙動が、ユーザーの期待通りに、つまり依存関係のインストールとテスト実行ファイルのコンパイルの両方が行われるように改善されました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (go help test など)
  • Go言語のソースコード (src/cmd/go/test.go)
  • Gerrit Code Review System (golang.org/cl)
  • GitHub (golang/go repository)