[インデックス 19091] ファイルの概要
このコミットは、go test
コマンドの動作を修正し、_test.go
ファイルが存在しないパッケージに対しても常にビルドを実行するように変更します。これにより、テストファイルがない場合でも、パッケージ自体のビルドエラーが確実に検出されるようになります。
コミット
commit c8f90979acf078d2cbb18a9bf5c6f8349fab296a
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Thu Apr 10 14:02:24 2014 +1000
cmd/go: always build package during "go test" command
even when there are no *_test.go files present.
rsc suggested this change
Fixes #7108
LGTM=r, adg
R=golang-codereviews, r, adg
CC=golang-codereviews
https://golang.org/cl/84300043
---
src/cmd/go/test.bash | 8 ++++++++\n src/cmd/go/test.go | 2 +-\n src/cmd/go/testdata/src/notest/hello.go | 6 ++++++\n 3 files changed, 15 insertions(+), 1 deletion(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c8f90979acf078d2cbb18a9bf5c6f8349fab296a
元コミット内容
cmd/go
: go test
コマンド実行時に常にパッケージをビルドする。
_test.go
ファイルが存在しない場合でもビルドを行う。
rsc がこの変更を提案した。
Fixes #7108
LGTM=r, adg R=golang-codereviews, r, adg CC=golang-codereviews https://golang.org/cl/84300043
変更の背景
この変更が行われる前は、go test
コマンドは、対象のパッケージ内に _test.go
という命名規則に従うテストファイルが存在しない場合、そのパッケージのビルドをスキップすることがありました。これは、テストを実行する必要がないと判断されたためです。
しかし、この挙動には問題がありました。もしパッケージ自体にコンパイルエラーやその他のビルドに関する問題があったとしても、テストファイルが存在しないためにビルドがスキップされてしまうと、go test
コマンドはエラーを報告せずに成功と見なしてしまう可能性があったのです。これは、開発者がパッケージの健全性を誤解する原因となり、潜在的なバグや問題を早期に発見できないという課題を抱えていました。
このコミットは、この問題を解決するために導入されました。go test
が常にパッケージのビルドを試みるようにすることで、テストファイルの有無にかかわらず、パッケージのビルドに関する問題を確実に検出できるようになります。コミットメッセージにある "Fixes #7108" は、この特定の挙動に関する問題報告に対応するものであることを示唆しています。ただし、このIssue番号は2014年当時のものであり、現在のGoのIssueトラッカーでは直接参照できない可能性があります。
前提知識の解説
go test
コマンド
go test
はGo言語の標準的なテスト実行ツールです。このコマンドは、指定されたパッケージ内のテスト関数(TestXxx
という命名規則に従う関数)を実行します。しかし、単にテストを実行するだけでなく、テスト対象のパッケージとその依存関係をビルドする役割も担っています。テストの実行前に、Goのツールチェインはソースコードをコンパイルし、実行可能なテストバイナリを生成します。
Goパッケージのビルド
Go言語では、ソースコードは「パッケージ」という単位で管理されます。パッケージは、関連するGoソースファイルの集合であり、通常は単一のディレクトリに配置されます。Goのビルドプロセスは、これらのパッケージをコンパイルし、実行可能なバイナリやライブラリを生成します。go test
コマンドも、テスト対象のパッケージが正しくビルドできることを前提としており、そのビルドプロセスの一部を自動的に行います。
_test.go
ファイル
Go言語のテストは、通常、テスト対象のソースファイルと同じパッケージ内に _test.go
というサフィックスを持つファイルとして記述されます。例えば、my_package.go
のテストは my_package_test.go
に記述されます。go test
コマンドは、これらの _test.go
ファイルを自動的に検出し、その中のテスト関数を実行します。
ビルドエラーの重要性
ソフトウェア開発において、ビルドエラーはコードの構文的な誤りや、依存関係の不整合など、プログラムが正しくコンパイルできない状態を示します。これらのエラーは、プログラムが実行される前に検出されるべき最も基本的な問題です。ビルドエラーが早期に検出されることで、開発者は問題を迅速に特定し、修正することができます。もしビルドエラーが検出されずに「成功」と報告されてしまうと、その後の実行時になって初めて問題が発覚し、デバッグが困難になる可能性があります。
技術的詳細
このコミットの核心は、go test
コマンドがパッケージをビルドする際の内部ロジックの変更にあります。go test
は、内部的に builder
という構造体を使用して、ビルドとテストの各ステップをアクションとして管理しています。
変更前の src/cmd/go/test.go
のコードでは、p.TestGoFiles
と p.XTestGoFiles
(それぞれ通常のテストファイルと外部テストファイル) の長さが0、つまりテストファイルが全く存在しない場合に、build
アクションを次のように定義していました。
if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
build := &action{p: p}
run := &action{p: p, deps: []*action{build}}
print := &action{f: (*builder).notest, p: p, deps: []*action{run}}
return build, run, print, nil
}
ここで注目すべきは、build := &action{p: p}
の部分です。この action
は、単にパッケージ p
に関連付けられたアクションとして定義されていますが、明示的に「ビルドモード」を指定していません。Goのビルドシステムでは、様々なモード(例えば、modeBuild
は通常のビルド、modeInstall
はインストールなど)が存在し、それぞれ異なる挙動をします。テストファイルがない場合、この action
はパッケージのビルドを強制するものではなく、単に「このパッケージに関連する何か」として扱われ、結果的にビルドがスキップされる可能性がありました。
変更後のコードでは、この部分が次のように修正されました。
if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
build := b.action(modeBuild, modeBuild, p)
run := &action{p: p, deps: []*action{build}}
print := &action{f: (*builder).notest, p: p, deps: []*action{run}}
return build, run, print, nil
}
新しいコード build := b.action(modeBuild, modeBuild, p)
では、b.action
メソッドが呼び出され、第一引数と第二引数に modeBuild
が明示的に渡されています。b.action
メソッドは、指定されたモードに基づいて適切なビルドアクションを生成します。modeBuild
を明示的に指定することで、テストファイルが存在しない場合でも、go test
は常にそのパッケージを通常のビルドモードでコンパイルしようとします。これにより、パッケージにコンパイルエラーがあれば、それが確実に報告されるようになります。
また、src/cmd/go/test.bash
に追加されたテストケースは、この変更が正しく機能することを確認するためのものです。_test.go
ファイルを持たないが、意図的にビルドエラーを含む(またはビルドが成功する)ダミーパッケージを作成し、go test
がそのパッケージをビルドしようとすることを確認しています。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下のファイルで行われています。
-
src/cmd/go/test.go
:go test
コマンドの主要なロジックが含まれるファイル。--- a/src/cmd/go/test.go +++ b/src/cmd/go/test.go @@ -524,7 +524,7 @@ func contains(x []string, s string) bool { func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, err error) { if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 { - build := &action{p: p} + build := b.action(modeBuild, modeBuild, p) run := &action{p: p, deps: []*action{build}} print := &action{f: (*builder).notest, p: p, deps: []*action{run}} return build, run, print, nil
-
src/cmd/go/test.bash
:go
コマンドのテストスクリプト。新しいテストケースが追加されています。--- a/src/cmd/go/test.bash +++ b/src/cmd/go/test.bash @@ -700,6 +700,14 @@ if ! ./testgo list -f "GOARCH: {{context.GOARCH}}"; then ok=false fi +TEST 'Issue 7108: cmd/go: "go test" should fail if package does not build' +export GOPATH=$(pwd)/testdata +if ./testgo test notest >/dev/null 2>&1; then + echo 'go test notest succeeded, but should fail' + ok=false +fi +unset GOPATH + # clean up if $started; then stop; fi rm -rf testdata/bin testdata/bin1
-
src/cmd/go/testdata/src/notest/hello.go
: 新しいテストケースで使用される、テストファイルを持たないダミーパッケージ。--- /dev/null +++ b/src/cmd/go/testdata/src/notest/hello.go @@ -0,0 +1,6 @@ +package notest + +func hello() { + println("hello world") +} +Hello world
コアとなるコードの解説
src/cmd/go/test.go
の変更
このファイルにおける変更は、go test
コマンドがテストファイルを持たないパッケージをどのように扱うかを根本的に変えるものです。
-
変更前:
build := &action{p: p}
この行は、build
というアクションを定義していますが、そのアクションが具体的に「ビルド」を行うことを明示的に指示していませんでした。action
構造体は、Goコマンドの内部で様々な操作(ビルド、実行、インストールなど)を表現するために使われます。テストファイルがない場合、このアクションは単にパッケージp
に関連付けられるだけで、ビルドフェーズがスキップされる可能性がありました。 -
変更後:
build := b.action(modeBuild, modeBuild, p)
この行は、builder
オブジェクトb
のaction
メソッドを呼び出しています。modeBuild
は、Goツールチェインがパッケージをビルドする際のモードを定義する定数です。b.action(modeBuild, modeBuild, p)
とすることで、go test
は、テストファイルが存在しないパッケージp
であっても、常にmodeBuild
でビルドを実行するように強制されます。これにより、パッケージのコンパイルエラーが確実に検出されるようになります。
この変更により、go test
は、テストの有無にかかわらず、対象パッケージのビルド健全性を常に検証するようになりました。
src/cmd/go/test.bash
の変更
このシェルスクリプトは、go
コマンド自体の動作をテストするために使用されます。追加されたテストケースは、このコミットの変更が意図通りに機能することを確認するためのものです。
TEST 'Issue 7108: cmd/go: "go test" should fail if package does not build'
export GOPATH=$(pwd)/testdata
if ./testgo test notest >/dev/null 2>&1; then
echo 'go test notest succeeded, but should fail'
ok=false
fi
unset GOPATH
TEST 'Issue 7108: ...'
:新しいテストケースのタイトルです。Issue 7108 に関連する問題が修正されたことを示しています。export GOPATH=$(pwd)/testdata
:テスト用のGOPATH
を設定し、testdata
ディレクトリ内のパッケージがGoツールチェインによって認識されるようにします。./testgo test notest >/dev/null 2>&1
:testgo
(テスト用のgo
コマンドバイナリ) を使用して、notest
パッケージに対してgo test
を実行します。標準出力と標準エラー出力を/dev/null
にリダイレクトし、コマンドの成功/失敗のみをチェックします。if ...; then echo 'go test notest succeeded, but should fail'; ok=false; fi
:この条件文が重要です。もしgo test notest
が成功してしまった場合(つまり、ビルドエラーが検出されなかった場合)、それは期待される動作ではないため、エラーメッセージを出力し、テストの全体的な結果をfalse
に設定します。このテストケースは、notest
パッケージがビルドエラーを含むように設計されているため、go test
が失敗することを期待しています。
src/cmd/go/testdata/src/notest/hello.go
の追加
このファイルは、上記のテストケースで使用される新しいGoパッケージです。
package notest
func hello() {
println("hello world")
}
Hello world
このファイルは _test.go
サフィックスを持たないため、テストファイルは含まれていません。しかし、このコードには Hello world
という行があり、これはGoの有効な構文ではありません。したがって、このパッケージはコンパイルエラーを引き起こします。
この notest
パッケージに対して go test
を実行すると、変更前はテストファイルがないためにビルドがスキップされ、go test
が成功してしまう可能性がありました。しかし、このコミットの変更後は、go test
がこのパッケージのビルドを強制するため、Hello world
の構文エラーが検出され、go test
は失敗するようになります。これにより、go test
がパッケージのビルド健全性を常に検証するという新しい挙動が確認されます。
関連リンク
- Go言語公式ドキュメント: https://go.dev/
go test
コマンドのドキュメント: https://go.dev/cmd/go/#hdr-Test_packages
参考にした情報源リンク
- golang/go GitHubリポジトリのコミット履歴
- コミットメッセージに記載された情報
- Go言語のソースコード(
src/cmd/go/test.go
,src/cmd/go/test.bash
) - Go言語の一般的なビルドシステムとテストの知識
- (注: コミットメッセージに記載された Issue #7108 は、現在のGoのIssueトラッカーでは直接見つけることができませんでした。これは、Issueトラッカーのシステム変更や古いIssueのアーカイブによるものと考えられます。そのため、解説は主にコミットメッセージとコードの変更内容から推測して記述しています。)