[インデックス 17541] ファイルの概要
このコミットは、Go言語のコマンドラインツールgo
におけるgo test
コマンドのバグ修正に関するものです。具体的には、package main
として定義されたバイナリ(実行可能コマンド)が既にインストールされている場合に、go test
がそのパッケージのアーカイブ(.a
ファイル)のビルドをスキップしてしまう問題を解決します。この問題は、main_test
という慣習的なテストパッケージ名を使用している場合に特に顕著でした。go test
はテスト実行のためにパッケージアーカイブを必要とするため、このスキップはテストの失敗や不正確な動作を引き起こす可能性がありました。
コミット
commit 627d17cf2980c97b76badf7893cfc2c4b1289738
Author: Russ Cox <rsc@golang.org>
Date: Tue Sep 10 14:43:35 2013 -0400
cmd/go: fix go test using package main_test
A package main binary (that is, a command) being installed
does not mean we can skip the build of the package archive
during a test.
Fixes #3417.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/13462046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/627d17cf2980c97b76badf7893cfc2c4b1289738
元コミット内容
このコミットは、go test
コマンドがpackage main_test
を使用する際の挙動を修正することを目的としています。package main
としてビルドされるバイナリ(実行可能ファイル)がシステムにインストールされている場合でも、go test
はそのパッケージのテストを実行するために、パッケージのアーカイブ(コンパイル済みコードのライブラリ形式)をビルドする必要があります。以前のバージョンでは、バイナリがインストール済みであるという理由で、このアーカイブのビルドが不適切にスキップされることがありました。この修正は、この誤った最適化を排除し、package main
のテストが常に正しく実行されるようにします。
変更の背景
Go言語のgo test
コマンドは、指定されたパッケージ内のテスト関数を実行するために設計されています。通常、テスト対象のパッケージがライブラリパッケージ(package foo
など)である場合、go test
はそのパッケージをコンパイルし、テストコードとリンクしてテストバイナリを生成します。
しかし、package main
として定義されたパッケージ(つまり、実行可能なコマンドを生成するパッケージ)の場合、go install
コマンドによって既にそのバイナリがGOBIN
などのパスにインストールされていることがあります。この状況下でgo test
を実行すると、Goツールチェインは「バイナリが既に存在し、インストールされているのだから、パッケージアーカイブのビルドは不要だろう」と誤って判断し、テストに必要なアーカイブのビルドをスキップしてしまうバグがありました。
特に、package main
のコードをテストする一般的な慣習として、main_test
という名前のパッケージを別途作成し、その中でmain
パッケージの関数をテストする手法があります。このmain_test
パッケージは、内部的にはmain
パッケージのコードに依存しています。go test main_test
を実行した際に、main
パッケージのアーカイブが適切にビルドされないと、main_test
パッケージがmain
パッケージの関数を呼び出そうとしたときにリンクエラーが発生したり、古いバージョンのコードでテストが実行されたりする問題が生じていました。
このコミットは、このgo test
の誤った挙動を修正し、package main
であってもテスト時には常に必要なパッケージアーカイブがビルドされるようにすることで、main_test
のようなテストパターンが正しく機能するようにします。
前提知識の解説
-
Goパッケージと
package main
:- Goのコードはパッケージにまとめられます。各Goファイルは
package <name>
という宣言で始まります。 package main
は特別なパッケージ名で、このパッケージに含まれるmain
関数はプログラムのエントリポイントとなります。package main
のビルド結果は実行可能なバイナリ(コマンド)になります。- それ以外のパッケージ(例:
package foo
)はライブラリパッケージと呼ばれ、他のプログラムからインポートされて利用されます。ビルド結果はアーカイブファイル(.a
ファイル)になります。
- Goのコードはパッケージにまとめられます。各Goファイルは
-
go test
コマンド:- Goの標準テストツールを実行するコマンドです。
go test <package_path>
のように使用し、指定されたパッケージ内の_test.go
ファイルに記述されたテスト関数(TestXxx
、BenchmarkXxx
、ExampleXxx
)を実行します。- テスト実行時には、テスト対象のパッケージとテストコードをコンパイルし、一つのテストバイナリを生成して実行します。
-
パッケージアーカイブ(
.a
ファイル):- Goのコンパイラがパッケージをコンパイルした際に生成される中間ファイルです。
- コンパイル済みのオブジェクトコードやメタデータが含まれており、他のパッケージや実行可能バイナリをビルドする際にリンクされます。
go install
やgo build
が実行されると、通常は$GOPATH/pkg
や$GOROOT/pkg
以下に保存されます。
-
main_test
パッケージの慣習:package main
のコードをテストする際、テストコードをpackage main
と同じパッケージに含めると、テストバイナリがmain
関数を複数持つことになり、ビルドエラーになる可能性があります。- これを避けるため、
main
パッケージのテストコードをpackage main_test
として別のパッケージに定義し、import . "main"
のようにしてmain
パッケージの公開された関数をテストする慣習があります。この場合、main_test
はmain
パッケージに依存します。
-
GOBIN
環境変数:go install
コマンドが生成した実行可能バイナリを配置するディレクトリを指定する環境変数です。GOBIN
が設定されていない場合、バイナリは$GOPATH/bin
または$GOROOT/bin
にインストールされます。
技術的詳細
go test
コマンドの内部では、テスト対象のパッケージがpackage main
であるかどうかに応じて、ビルドの挙動が異なります。通常のライブラリパッケージの場合、go test
はテスト対象パッケージのアーカイブをビルドし、そのアーカイブとテストコードをリンクしてテスト実行ファイルを生成します。
しかし、package main
の場合、Goツールチェインは、そのパッケージが最終的に実行可能なバイナリを生成することを知っています。以前のバージョンでは、go install
によってpackage main
のバイナリが既にインストールされていると、go test
は「このパッケージは既にビルドされ、インストールされているのだから、テストのために再度アーカイブをビルドする必要はないだろう」と判断していました。これは、go test
がテスト実行のために必要とするパッケージアーカイブ(.a
ファイル)のビルドをスキップしてしまう原因となっていました。
この問題は、src/cmd/go/test.go
内のbuilder.test
関数における条件分岐の不備に起因していました。この関数は、テストを実行するパッケージのビルドが必要かどうかを判断するロジックを含んでいます。修正前は、テスト対象のGoファイルが存在するか、またはローカルカバレッジが有効な場合にのみビルドを進める条件となっていました。しかし、package main
の場合、たとえTestGoFiles
がなくても、テストバイナリを生成するためにそのパッケージのアーカイブが必要となるケースがありました。
このコミットでは、この条件にp.Name == "main"
という条件を追加することで、テスト対象のパッケージがpackage main
である場合にも、常にパッケージアーカイブのビルドプロセスが実行されるように変更されました。これにより、go test
はpackage main
のテストを実行する際に、必要な依存関係(パッケージアーカイブ)を適切にビルドし、テストバイナリのリンクエラーや古いコードでのテスト実行といった問題を回避できるようになりました。
また、src/cmd/go/test.bash
に新しいテストケースが追加され、この修正が正しく機能することを確認しています。このテストケースは、package main
とpackage main_test
の組み合わせでgo test
とgo install
を実行し、go test
がgo install
後も正しく動作すること、そしてパッケージが不適切にstale
(古くなっている)と判断されないことを検証しています。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下の2点です。
-
src/cmd/go/test.go
の変更:--- a/src/cmd/go/test.go +++ b/src/cmd/go/test.go @@ -590,7 +590,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, localCover := testCover && testCoverPaths == nil // Test package. - if len(p.TestGoFiles) > 0 || localCover { + if len(p.TestGoFiles) > 0 || localCover || p.Name == "main" { ptest = new(Package) *ptest = *p ptest.GoFiles = nil
-
新しいテストデータの追加:
src/cmd/go/testdata/src/main_test/m.go
(新規ファイル)package main func F() {} func main() {}
src/cmd/go/testdata/src/main_test/m_test.go
(新規ファイル)package main_test import ( . "main_test" "testing" ) func Test1(t *testing.T) { F() }
-
src/cmd/go/test.bash
へのテストケース追加:test.bash
スクリプトに、package main_test
のシナリオを検証する新しいテストブロックが追加されました。
コアとなるコードの解説
-
src/cmd/go/test.go
の変更:builder.test
関数は、go test
コマンドがパッケージのテストを実行する際のビルドロジックを制御します。変更されたif
文は、テスト対象のパッケージをビルドする必要があるかどうかを判断する条件です。len(p.TestGoFiles) > 0
: パッケージにテスト用のGoファイル(_test.go
)が存在する場合。これは当然ビルドが必要です。localCover
: ローカルカバレッジが有効な場合。カバレッジ情報を収集するためには、パッケージのビルドが必要です。p.Name == "main"
(追加された条件): テスト対象のパッケージ名が"main"
である場合。この条件が追加されたことで、たとえ明示的なテストファイルがなくても、package main
のテスト実行に必要なパッケージアーカイブのビルドが強制されるようになりました。これにより、main_test
のような外部テストパッケージがmain
パッケージに依存している場合に、main
パッケージのアーカイブが常に最新の状態で利用可能となり、リンクエラーや古いコードでのテスト実行が防止されます。
-
新しいテストデータファイル (
m.go
,m_test.go
): これらのファイルは、package main
とそのテストパッケージpackage main_test
の典型的な構造を示しています。m.go
:package main
として定義されたシンプルなファイルで、F()
という関数とmain()
関数を含んでいます。これは、テスト対象となるmain
パッケージのコードをシミュレートします。m_test.go
:package main_test
として定義されたテストファイルで、m.go
で定義されたF()
関数をテストしています。import . "main_test"
は、main_test
パッケージがmain
パッケージの公開された要素にアクセスできるようにするためのものです。この構造は、go test
がpackage main
のテストをどのように処理すべきかを示す重要なテストケースとなります。
-
src/cmd/go/test.bash
へのテストケース追加: このシェルスクリプトは、go
コマンドの様々な挙動を検証するための統合テストです。追加されたテストブロックは、以下のシナリオを検証します。GOBIN
を設定し、main_test
パッケージを含むテストデータを準備します。./testgo test main_test
を最初に実行し、インストールされていない状態でのテストが成功することを確認します。./testgo install main_test
を実行し、main_test
パッケージ(およびそれに依存するmain
パッケージ)がインストールされることを確認します。./testgo list -f '{{.Stale}}' main_test
を実行し、インストール後にmain_test
がstale
(古くなっている)と誤って判断されないことを確認します。- 再度
./testgo test main_test
を実行し、インストール後もテストが正しく実行されることを確認します。 この一連のテストは、package main
のバイナリがインストールされた後でも、go test
がそのパッケージのアーカイブを適切にビルドし、テストを成功させるという修正の意図を直接的に検証しています。
関連リンク
- Go issue tracker (Go言語の公式Issue Tracker): このコミットメッセージに記載されている
Fixes #3417
は、Go言語の内部Issue Trackerにおける問題番号を指していると考えられます。ただし、公開されているGitHubのIssue Trackerでは、この番号は別のSQL Serverのエラーに関連付けられているため、直接的な公開リンクは特定できませんでした。これは、Goプロジェクトが内部で別のIssue管理システムを使用しているか、または過去のIssue番号が再利用されている可能性を示唆しています。
参考にした情報源リンク
- コミットメッセージと変更されたコード (
src/cmd/go/test.go
,src/cmd/go/test.bash
,src/cmd/go/testdata/src/main_test/m.go
,src/cmd/go/testdata/src/main_test/m_test.go
) - Go言語の公式ドキュメント (
go help test
,go help build
,go help install
など) - 一般的なGoコマンドの挙動に関する知識 - Go言語のパッケージングとテストに関する一般的な慣習(
package main_test
など)に関する知識 - Go言語のビルドシステムに関する一般的な知識