[インデックス 19345] ファイルの概要
このコミットは、Go言語のコマンドラインツール go test
の挙動に関する修正です。特に、サブモジュール(subrepo)のビルドにおけるテストの実行問題を解決することを目的としています。go test
コマンドはGoパッケージのテストを実行するための主要なツールであり、その正確な動作はGo開発ワークフローにおいて非常に重要です。
コミット
commit 95e4181b0c7c7f9e6db672067847b1152eafa58c
Author: Russ Cox <rsc@golang.org>
Date: Tue May 13 01:38:10 2014 -0400
cmd/go: fix go test again
Fixes subrepo builds.
LGTM=iant, mikioh.mikioh
R=golang-codereviews, iant, mikioh.mikioh
CC=golang-codereviews
https://golang.org/cl/96310043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/95e4181b0c7c7f9e6db672067847b1152eafa58c
元コミット内容
cmd/go: fix go test again
Fixes subrepo builds.
変更の背景
このコミットの背景には、go test
コマンドがサブモジュール(subrepo)として扱われるパッケージのテストを正しく処理できない問題がありました。Goのパッケージ管理において、外部リポジトリをサブディレクトリとして取り込むような構成(現在のGo Modulesとは異なる、当時のGOPATHベースのサブモジュール利用)の場合、go test
がテスト対象のパッケージとその外部テストパッケージ(_test
サフィックスを持つパッケージ)間の依存関係を適切に解決できないケースが発生していました。
具体的には、go test
はテストを実行するために、テスト対象のパッケージ(ptest
)、外部テストパッケージ(pxtest
)、そしてテストのエントリポイントとなるメインパッケージ(pmain
、通常は _testmain.go
として生成される)をビルドします。この際、pmain
が ptest
や pxtest
を正しくインポートし、それらの依存関係を解決する必要がありました。以前の実装では、この依存関係の解決ロジックに不備があり、特に pxtest
が ptest
と同じインポートパスを持つ場合に問題が生じていました。
この問題は、go test
がテストバイナリを生成する際に、テスト対象パッケージと外部テストパッケージの間の依存関係を誤って解釈し、結果としてテストが失敗したり、ビルドエラーが発生したりすることにつながっていました。
前提知識の解説
このコミットを理解するためには、以下のGo言語および go test
コマンドに関する前提知識が必要です。
- Goパッケージとインポートパス: Goのコードはパッケージにまとめられ、
import
ステートメントによって他のパッケージを参照します。パッケージはファイルシステム上のディレクトリに対応し、そのパスはインポートパスとして機能します。 go test
コマンド:go test
はGoパッケージのテストを実行するためのコマンドです。テストファイルは_test.go
というサフィックスを持ち、テスト関数はTestXxx
という命名規則に従います。- 内部テストと外部テスト:
- 内部テスト: テスト対象のパッケージと同じパッケージ名を持つ
_test.go
ファイルに記述されたテストです。これらのテストは、テスト対象パッケージの内部要素(非エクスポートされた関数や変数)にアクセスできます。 - 外部テスト: テスト対象のパッケージとは異なるパッケージ名(通常は
_test
サフィックスが付く、例:mypackage_test
)を持つ_test.go
ファイルに記述されたテストです。これらのテストは、テスト対象パッケージを外部からインポートしてテストするため、エクスポートされた要素のみにアクセスできます。このコミットで修正されているのは、主にこの外部テストに関する問題です。
- 内部テスト: テスト対象のパッケージと同じパッケージ名を持つ
_testmain.go
の生成:go test
コマンドは、テストを実行するための特別なGoソースファイル_testmain.go
を自動的に生成します。このファイルには、すべてのテスト関数を呼び出すmain
関数が含まれており、テストのセットアップ、実行、結果の報告などを担当します。この_testmain.go
は、テスト対象のパッケージ、内部テストパッケージ、外部テストパッケージを適切にインポートする必要があります。Package
構造体とビルドプロセス:src/cmd/go/test.go
内では、Package
構造体がGoパッケージのメタデータを表現します。builder
構造体はビルドプロセスを管理し、test
メソッドがテストのビルドと実行ロジックをカプセル化しています。ptest
: テスト対象のパッケージ(内部テストを含む)。pxtest
: 外部テストパッケージ。pmain
:_testmain.go
を生成するための仮想的なパッケージ。
p.XTestImports
: パッケージの外部テストがインポートするパッケージのリスト。pmain.imports
:_testmain.go
がインポートするパッケージのリスト。このリストは、_testmain.go
がビルドされる際に、必要な依存関係を解決するために使用されます。
技術的詳細
このコミットの技術的な詳細は、主に src/cmd/go/test.go
内の test
関数と writeTestmain
関数の変更に集約されます。
以前の実装では、writeTestmain
関数が _testmain.go
を書き込む際に、pmain.imports
を更新するロジックが不完全でした。特に、ptest
と pxtest
の関係性、および pxtest
が ptest
と同じインポートパスを持つ場合の処理に問題がありました。
このコミットでは、以下の主要な変更が行われています。
-
pxtestNeedsPtest
フラグの導入:test
関数内で、p.XTestImports
をループする際に、path == p.ImportPath
(つまり、外部テストがテスト対象パッケージ自身をインポートしている場合)にpxtestNeedsPtest
という新しいフラグをtrue
に設定するように変更されました。これは、外部テストがテスト対象パッケージに依存していることを明示的に示すためのものです。 -
ptest
のpxtest
へのインポートロジックの修正: 以前はif ptest != p
という条件でpxtest.imports
にptest
を追加していましたが、これをif pxtestNeedsPtest
に変更しました。これにより、外部テストが実際にテスト対象パッケージを必要とする場合にのみ、その依存関係がpxtest
に追加されるようになります。これは、サブモジュールのような構成で、外部テストがテスト対象パッケージをインポートするシナリオを正確に処理するために重要です。 -
pmain.imports
の更新ロジックの変更:pmain
がインポートするパッケージのリスト (pmain.imports
) を構築するロジックが大幅に修正されました。- 以前は
ptest
とpxtest
を無条件にpmain.imports
に追加していましたが、この部分が削除されました。 - 代わりに、
writeTestmain
関数が_testmain.go
を書き込む際に、実際にptest
とpxtest
が必要かどうかを判断し、その結果に基づいてpmain.imports
を更新するように変更されました。これは、writeTestmain
が_testmain.go
に書き込むインポートステートメントとpmain.imports
の内容を同期させるための重要な変更です。
- 以前は
-
writeTestmain
関数のシグネチャとロジックの変更:writeTestmain
関数のシグネチャがfunc writeTestmain(out string, pmain, p *Package) error
からfunc writeTestmain(out string, pmain, ptest, pxtest *Package) error
に変更され、ptest
とpxtest
の両方を直接受け取るようになりました。writeTestmain
内で、t.NeedTest
とt.NeedXtest
というフラグを使用して、それぞれptest
とpxtest
が_testmain.go
にインポートされる必要があるかを判断します。- これらのフラグに基づいて、
pmain.imports
にptest
およびpxtest
を追加するロジックが追加されました。これにより、pmain.imports
は_testmain.go
の実際のインポートと一致するようになります。 - カバー情報 (
coverInfo
) の収集ロジックも、pmain.imports
の更新後に実行されるように移動されました。
これらの変更により、go test
は、特に外部テストがテスト対象パッケージをインポートするような複雑なシナリオ(サブモジュールビルドでよく見られる)において、テストバイナリの依存関係をより正確に解決できるようになりました。
コアとなるコードの変更箇所
src/cmd/go/test.bash
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -789,6 +789,16 @@ if ! ./testgo test testdata/standalone_test.go; then
ok=false
fi
+TEST 'go test xtestonly works'
+export GOPATH=$(pwd)/testdata
+./testgo clean -i xtestonly
+if ! ./testgo test xtestonly >/dev/null; then
+ echo "go test xtestonly failed"
+ ok=false
+fi
+unset GOPATH
+
+
# 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
@@ -559,8 +559,10 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
}\n \tstk.pop()\n \tstk.push(p.ImportPath + \"_test\")\n+\tpxtestNeedsPtest := false\n \tfor _, path := range p.XTestImports {\n \t\tif path == p.ImportPath {\n+\t\t\tpxtestNeedsPtest = true\n \t\t\tcontinue\n \t\t}\n \t\tp1 := loadImport(path, p.Dir, &stk, p.build.XTestImportPos[path])\n@@ -666,7 +668,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\n \t\t\tfake: true,\n \t\t\tStale: true,\n \t\t}\n-\t\tif ptest != p {\n+\t\tif pxtestNeedsPtest {\n \t\t\tpxtest.imports = append(pxtest.imports, ptest)\n \t\t}\n \t}\n@@ -684,17 +686,13 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\n \t\tStale: true,\n \t\tomitDWARF: !testC && !testNeedBinary,\n \t}\n-\tif ptest != p {\n-\t\tpmain.imports = append(pmain.imports, ptest)\n-\t}\n-\tif pxtest != nil {\n-\t\tpmain.imports = append(pmain.imports, pxtest)\n-\t}\n \n \t// The generated main also imports testing and regexp.\n \tstk.push(\"testmain\")\n \tfor dep := range testMainDeps {\n-\t\tif ptest.ImportPath != dep {\n+\t\tif dep == ptest.ImportPath {\n+\t\t\tpmain.imports = append(pmain.imports, ptest)\n+\t\t} else {\n \t\t\tp1 := loadImport(dep, \"\", &stk, nil)\n \t\t\tif p1.Error != nil {\n \t\t\t\treturn nil, nil, nil, p1.Error\n@@ -717,6 +715,14 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\n \t\t}\n \t}\n \n+\t// writeTestmain writes _testmain.go but also updates\n+\t// pmain.imports to reflect the import statements written\n+\t// to _testmain.go. This metadata is needed for recompileForTest\n+\t// and the builds below.\n+\tif err := writeTestmain(filepath.Join(testDir, \"_testmain.go\"), pmain, ptest, pxtest); err != nil {\n+\t\treturn nil, nil, nil, err\n+\t}\n+\n \tif ptest != p && localCover {\n \t\t// We have modifications to the package p being tested\n \t\t// and are rebuilding p (as ptest), writing it to the testDir tree.\n@@ -733,10 +739,6 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\n \t\trecompileForTest(pmain, p, ptest, testDir)\n \t}\n \n-\tif err := writeTestmain(filepath.Join(testDir, \"_testmain.go\"), pmain, ptest); err != nil {\n-\t\treturn nil, nil, nil, err\n-\t}\n-\n \tcomputeStale(pmain)\n \n \tif ptest != p {\n@@ -1056,30 +1058,36 @@ type coverInfo struct {\n }\n \n // writeTestmain writes the _testmain.go file for package p to\n-// the file named out.\n-func writeTestmain(out string, pmain, p *Package) error {\n-\tvar cover []coverInfo\n-\tfor _, cp := range pmain.imports {\n-\t\tif len(cp.coverVars) > 0 {\n-\t\t\tcover = append(cover, coverInfo{cp, cp.coverVars})\n-\t\t}\n-\t}\n-\n+// the file named out. It also updates pmain.imports to include\n+// ptest and/or pxtest, depending on what it writes to _testmain.go.\n+func writeTestmain(out string, pmain, ptest, pxtest *Package) error {\n \tt := &testFuncs{\n-\t\tPackage: p,\n-\t\tCover: cover,\n+\t\tPackage: ptest,\n \t}\n-\tfor _, file := range p.TestGoFiles {\n-\t\tif err := t.load(filepath.Join(p.Dir, file), \"_test\", &t.NeedTest); err != nil {\n+\tfor _, file := range ptest.TestGoFiles {\n+\t\tif err := t.load(filepath.Join(ptest.Dir, file), \"_test\", &t.NeedTest); err != nil {\n \t\t\treturn err\n \t\t}\n \t}\n-\tfor _, file := range p.XTestGoFiles {\n-\t\tif err := t.load(filepath.Join(p.Dir, file), \"_xtest\", &t.NeedXtest); err != nil {\n+\tfor _, file := range ptest.XTestGoFiles {\n+\t\tif err := t.load(filepath.Join(ptest.Dir, file), \"_xtest\", &t.NeedXtest); err != nil {\n \t\t\treturn err\n \t\t}\n \t}\n \n+\tif t.NeedTest {\n+\t\tpmain.imports = append(pmain.imports, ptest)\n+\t}\n+\tif t.NeedXtest {\n+\t\tpmain.imports = append(pmain.imports, pxtest)\n+\t}\n+\n+\tfor _, cp := range pmain.imports {\n+\t\tif len(cp.coverVars) > 0 {\n+\t\t\tt.Cover = append(t.Cover, coverInfo{cp, cp.coverVars})\n+\t\t}\n+\t}\n+\n \tf, err := os.Create(out)\n \tif err != nil {\n \t\treturn err\n```
### `src/cmd/go/testdata/src/xtestonly/f.go` (新規ファイル)
```go
package xtestonly
func F() int { return 42 }
src/cmd/go/testdata/src/xtestonly/f_test.go
(新規ファイル)
package xtestonly_test
import (
"testing"
"xtestonly"
)
func TestF(t *testing.T) {
if x := xtestonly.F(); x != 42 {
t.Errorf("f.F() = %d, want 42", x)
}
}
コアとなるコードの解説
src/cmd/go/test.bash
の変更
このファイルは go
コマンドのテストスクリプトです。追加されたテストケースは、xtestonly
という新しいテストデータパッケージに対して go test
を実行し、その動作が期待通りであることを確認しています。これは、test.go
で行われた変更が、外部テストのみを持つパッケージ(サブモジュールのようなシナリオをシミュレート)で正しく機能することを確認するための統合テストです。
src/cmd/go/test.go
の変更
-
pxtestNeedsPtest
フラグの導入と利用:pxtestNeedsPtest := false
が追加され、外部テストがテスト対象パッケージ自身をインポートしている場合にtrue
に設定されます。pxtest.imports
にptest
を追加する条件がptest != p
からpxtestNeedsPtest
に変更されました。これにより、外部テストが実際にテスト対象パッケージを必要とする場合にのみ、その依存関係がpxtest
に追加されるようになり、より正確な依存関係グラフが構築されます。
-
pmain.imports
の初期化ロジックの変更:- 以前は
ptest
とpxtest
を無条件にpmain.imports
に追加していた行が削除されました。これは、pmain.imports
の更新をwriteTestmain
関数に一元化するためです。 testMainDeps
をループしてpmain.imports
に依存関係を追加する部分で、dep == ptest.ImportPath
の条件が追加されました。これにより、ptest
がtestMainDeps
に含まれる場合にのみpmain.imports
に追加されるようになります。
- 以前は
-
writeTestmain
の呼び出しとpmain.imports
の更新:writeTestmain
関数の呼び出しが、_testmain.go
の書き込みだけでなく、pmain.imports
を更新する役割も持つようになりました。この変更はコメントで明示されています。- 以前の
writeTestmain
呼び出しが削除され、新しいロジックが導入されました。
-
writeTestmain
関数の変更:- シグネチャの変更:
func writeTestmain(out string, pmain, p *Package) error
からfunc writeTestmain(out string, pmain, ptest, pxtest *Package) error
に変更され、ptest
とpxtest
を明示的に受け取るようになりました。これにより、関数内でこれらのパッケージの情報を直接利用できるようになります。 testFuncs
のPackage
フィールド:t.Package
がp
からptest
に変更されました。これは、testFuncs
がテスト対象のパッケージ(ptest
)の情報を基にテスト関数をロードするためです。- テストファイルのロードロジック:
p.TestGoFiles
とp.XTestGoFiles
をループしてテスト関数をロードする際に、p.Dir
ではなくptest.Dir
を使用するように変更されました。 pmain.imports
の動的な更新:t.NeedTest
とt.NeedXtest
というフラグが導入され、それぞれ内部テストと外部テストが存在するかどうかを示します。if t.NeedTest { pmain.imports = append(pmain.imports, ptest) }
とif t.NeedXtest { pmain.imports = append(pmain.imports, pxtest) }
が追加されました。これにより、_testmain.go
が実際にインポートするパッケージのみがpmain.imports
に追加され、recompileForTest
などの後続のビルドステップで正確な依存関係情報が利用されるようになります。
- カバー情報の収集: カバー情報の収集ロジックが
pmain.imports
の更新後に移動されました。これは、pmain.imports
が最終的な状態になってからカバー情報を収集するためです。
- シグネチャの変更:
これらの変更により、go test
は、特に外部テストがテスト対象パッケージをインポートするようなシナリオにおいて、_testmain.go
の生成と依存関係の解決をより正確に行えるようになり、サブモジュールビルドにおけるテストの失敗が修正されました。
src/cmd/go/testdata/src/xtestonly/f.go
および f_test.go
の新規追加
これらのファイルは、test.bash
で追加されたテストケースで使用される新しいテストデータです。
f.go
はxtestonly
パッケージのシンプルな関数F()
を定義しています。f_test.go
はxtestonly_test
パッケージに属する外部テストファイルで、xtestonly
パッケージをインポートしてF()
関数をテストしています。
このテストデータは、外部テストがテスト対象パッケージをインポートする典型的なサブモジュールシナリオを模倣しており、今回の修正が正しく機能することを確認するための重要な要素です。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
go test
コマンドのドキュメント:go help test
または https://pkg.go.dev/cmd/go#hdr-Test_packages- このコミットの変更が提案されたGo Code Review: https://golang.org/cl/96310043
参考にした情報源リンク
- Go言語のソースコード (
src/cmd/go/test.go
) - Go言語のテストに関する公式ドキュメントやブログ記事 (一般的な
go test
の動作理解のため) - Go Code Review の議論 (コミットメッセージに記載されている
golang.org/cl
リンク) - Go言語のパッケージとモジュールに関する一般的な知識# [インデックス 19345] ファイルの概要
このコミットは、Go言語のコマンドラインツール go test
の挙動に関する修正です。特に、サブモジュール(subrepo)のビルドにおけるテストの実行問題を解決することを目的としています。go test
コマンドはGoパッケージのテストを実行するための主要なツールであり、その正確な動作はGo開発ワークフローにおいて非常に重要です。
コミット
commit 95e4181b0c7c7f9e6db672067847b1152eafa58c
Author: Russ Cox <rsc@golang.org>
Date: Tue May 13 01:38:10 2014 -0400
cmd/go: fix go test again
Fixes subrepo builds.
LGTM=iant, mikioh.mikioh
R=golang-codereviews, iant, mikioh.mikioh
CC=golang-codereviews
https://golang.org/cl/96310043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/95e4181b0c7c7f9e6db672067847b1152eafa58c
元コミット内容
cmd/go: fix go test again
Fixes subrepo builds.
変更の背景
このコミットの背景には、go test
コマンドがサブモジュール(subrepo)として扱われるパッケージのテストを正しく処理できない問題がありました。Goのパッケージ管理において、外部リポジトリをサブディレクトリとして取り込むような構成(現在のGo Modulesとは異なる、当時のGOPATHベースのサブモジュール利用)の場合、go test
がテスト対象のパッケージとその外部テストパッケージ(_test
サフィックスを持つパッケージ)間の依存関係を適切に解決できないケースが発生していました。
具体的には、go test
はテストを実行するために、テスト対象のパッケージ(ptest
)、外部テストパッケージ(pxtest
)、そしてテストのエントリポイントとなるメインパッケージ(pmain
、通常は _testmain.go
として生成される)をビルドします。この際、pmain
が ptest
や pxtest
を正しくインポートし、それらの依存関係を解決する必要がありました。以前の実装では、この依存関係の解決ロジックに不備があり、特に pxtest
が ptest
と同じインポートパスを持つ場合に問題が生じていました。
この問題は、go test
がテストバイナリを生成する際に、テスト対象パッケージと外部テストパッケージの間の依存関係を誤って解釈し、結果としてテストが失敗したり、ビルドエラーが発生したりすることにつながっていました。
前提知識の解説
このコミットを理解するためには、以下のGo言語および go test
コマンドに関する前提知識が必要です。
- Goパッケージとインポートパス: Goのコードはパッケージにまとめられ、
import
ステートメントによって他のパッケージを参照します。パッケージはファイルシステム上のディレクトリに対応し、そのパスはインポートパスとして機能します。 go test
コマンド:go test
はGoパッケージのテストを実行するためのコマンドです。テストファイルは_test.go
というサフィックスを持ち、テスト関数はTestXxx
という命名規則に従います。- 内部テストと外部テスト:
- 内部テスト: テスト対象のパッケージと同じパッケージ名を持つ
_test.go
ファイルに記述されたテストです。これらのテストは、テスト対象パッケージの内部要素(非エクスポートされた関数や変数)にアクセスできます。 - 外部テスト: テスト対象のパッケージとは異なるパッケージ名(通常は
_test
サフィックスが付く、例:mypackage_test
)を持つ_test.go
ファイルに記述されたテストです。これらのテストは、テスト対象パッケージを外部からインポートしてテストするため、エクスポートされた要素のみにアクセスできます。このコミットで修正されているのは、主にこの外部テストに関する問題です。
- 内部テスト: テスト対象のパッケージと同じパッケージ名を持つ
_testmain.go
の生成:go test
コマンドは、テストを実行するための特別なGoソースファイル_testmain.go
を自動的に生成します。このファイルには、すべてのテスト関数を呼び出すmain
関数が含まれており、テストのセットアップ、実行、結果の報告などを担当します。この_testmain.go
は、テスト対象のパッケージ、内部テストパッケージ、外部テストパッケージを適切にインポートする必要があります。Package
構造体とビルドプロセス:src/cmd/go/test.go
内では、Package
構造体がGoパッケージのメタデータを表現します。builder
構造体はビルドプロセスを管理し、test
メソッドがテストのビルドと実行ロジックをカプセル化しています。ptest
: テスト対象のパッケージ(内部テストを含む)。pxtest
: 外部テストパッケージ。pmain
:_testmain.go
を生成するための仮想的なパッケージ。
p.XTestImports
: パッケージの外部テストがインポートするパッケージのリスト。pmain.imports
:_testmain.go
がインポートするパッケージのリスト。このリストは、_testmain.go
がビルドされる際に、必要な依存関係を解決するために使用されます。
技術的詳細
このコミットの技術的な詳細は、主に src/cmd/go/test.go
内の test
関数と writeTestmain
関数の変更に集約されます。
以前の実装では、writeTestmain
関数が _testmain.go
を書き込む際に、pmain.imports
を更新するロジックが不完全でした。特に、ptest
と pxtest
の関係性、および pxtest
が ptest
と同じインポートパスを持つ場合の処理に問題がありました。
このコミットでは、以下の主要な変更が行われています。
-
pxtestNeedsPtest
フラグの導入:test
関数内で、p.XTestImports
をループする際に、path == p.ImportPath
(つまり、外部テストがテスト対象パッケージ自身をインポートしている場合)にpxtestNeedsPtest
という新しいフラグをtrue
に設定するように変更されました。これは、外部テストがテスト対象パッケージに依存していることを明示的に示すためのものです。 -
ptest
のpxtest
へのインポートロジックの修正: 以前はif ptest != p
という条件でpxtest.imports
にptest
を追加していましたが、これをif pxtestNeedsPtest
に変更しました。これにより、外部テストが実際にテスト対象パッケージを必要とする場合にのみ、その依存関係がpxtest
に追加されるようになります。これは、サブモジュールのような構成で、外部テストがテスト対象パッケージをインポートするシナリオを正確に処理するために重要です。 -
pmain.imports
の更新ロジックの変更:pmain
がインポートするパッケージのリスト (pmain.imports
) を構築するロジックが大幅に修正されました。- 以前は
ptest
とpxtest
を無条件にpmain.imports
に追加していましたが、この部分が削除されました。 - 代わりに、
writeTestmain
関数が_testmain.go
を書き込む際に、実際にptest
とpxtest
が必要かどうかを判断し、その結果に基づいてpmain.imports
を更新するように変更されました。これは、writeTestmain
が_testmain.go
に書き込むインポートステートメントとpmain.imports
の内容を同期させるための重要な変更です。
- 以前は
-
writeTestmain
関数のシグネチャとロジックの変更:writeTestmain
関数のシグネチャがfunc writeTestmain(out string, pmain, p *Package) error
からfunc writeTestmain(out string, pmain, ptest, pxtest *Package) error
に変更され、ptest
とpxtest
の両方を直接受け取るようになりました。writeTestmain
内で、t.NeedTest
とt.NeedXtest
というフラグを使用して、それぞれptest
とpxtest
が_testmain.go
にインポートされる必要があるかを判断します。- これらのフラグに基づいて、
pmain.imports
にptest
およびpxtest
を追加するロジックが追加されました。これにより、pmain.imports
は_testmain.go
の実際のインポートと一致するようになります。 - カバー情報 (
coverInfo
) の収集ロジックも、pmain.imports
の更新後に実行されるように移動されました。
これらの変更により、go test
は、特に外部テストがテスト対象パッケージをインポートするような複雑なシナリオ(サブモジュールビルドでよく見られる)において、テストバイナリの依存関係をより正確に解決できるようになりました。
コアとなるコードの変更箇所
src/cmd/go/test.bash
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -789,6 +789,16 @@ if ! ./testgo test testdata/standalone_test.go; then
ok=false
fi
+TEST 'go test xtestonly works'
+export GOPATH=$(pwd)/testdata
+./testgo clean -i xtestonly
+if ! ./testgo test xtestonly >/dev/null; then
+ echo "go test xtestonly failed"
+ ok=false
+fi
+unset GOPATH
+
+
# 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
@@ -559,8 +559,10 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
}\n \tstk.pop()\n \tstk.push(p.ImportPath + \"_test\")\n+\tpxtestNeedsPtest := false\n \tfor _, path := range p.XTestImports {\n \t\tif path == p.ImportPath {\n+\t\t\tpxtestNeedsPtest = true\n \t\t\tcontinue\n \t\t}\n \t\tp1 := loadImport(path, p.Dir, &stk, p.build.XTestImportPos[path])\n@@ -666,7 +668,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\n \t\t\tfake: true,\n \t\t\tStale: true,\n \t\t}\n-\t\tif ptest != p {\n+\t\tif pxtestNeedsPtest {\n \t\t\tpxtest.imports = append(pxtest.imports, ptest)\n \t\t}\n \t}\n@@ -684,17 +686,13 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\n \t\tStale: true,\n \t\tomitDWARF: !testC && !testNeedBinary,\n \t}\n-\tif ptest != p {\n-\t\tpmain.imports = append(pmain.imports, ptest)\n-\t}\n-\tif pxtest != nil {\n-\t\tpmain.imports = append(pmain.imports, pxtest)\n-\t}\n \n \t// The generated main also imports testing and regexp.\n \tstk.push(\"testmain\")\n \tfor dep := range testMainDeps {\n-\t\tif ptest.ImportPath != dep {\n+\t\tif dep == ptest.ImportPath {\n+\t\t\tpmain.imports = append(pmain.imports, ptest)\n+\t\t} else {\n \t\t\tp1 := loadImport(dep, \"\", &stk, nil)\n \t\t\tif p1.Error != nil {\n \t\t\t\treturn nil, nil, nil, p1.Error\n@@ -717,6 +715,14 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\n \t\t}\n \t}\n \n+\t// writeTestmain writes _testmain.go but also updates\n+\t// pmain.imports to reflect the import statements written\n+\t// to _testmain.go. This metadata is needed for recompileForTest\n+\t// and the builds below.\n+\tif err := writeTestmain(filepath.Join(testDir, \"_testmain.go\"), pmain, ptest, pxtest); err != nil {\n+\t\treturn nil, nil, nil, err\n+\t}\n+\n \tif ptest != p && localCover {\n \t\t// We have modifications to the package p being tested\n \t\t// and are rebuilding p (as ptest), writing it to the testDir tree.\n@@ -733,10 +739,6 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\n \t\trecompileForTest(pmain, p, ptest, testDir)\n \t}\n \n-\tif err := writeTestmain(filepath.Join(testDir, \"_testmain.go\"), pmain, ptest); err != nil {\n-\t\treturn nil, nil, nil, err\n-\t}\n-\n \tcomputeStale(pmain)\n \n \tif ptest != p {\n@@ -1056,30 +1058,36 @@ type coverInfo struct {\n }\n \n // writeTestmain writes the _testmain.go file for package p to\n-// the file named out.\n-func writeTestmain(out string, pmain, p *Package) error {\n-\tvar cover []coverInfo\n-\tfor _, cp := range pmain.imports {\n-\t\tif len(cp.coverVars) > 0 {\n-\t\t\tcover = append(cover, coverInfo{cp, cp.coverVars})\n-\t\t}\n-\t}\n-\n+// the file named out. It also updates pmain.imports to include\n+// ptest and/or pxtest, depending on what it writes to _testmain.go.\n+func writeTestmain(out string, pmain, ptest, pxtest *Package) error {\n \tt := &testFuncs{\n-\t\tPackage: p,\n-\t\tCover: cover,\n+\t\tPackage: ptest,\n \t}\n-\tfor _, file := range p.TestGoFiles {\n-\t\tif err := t.load(filepath.Join(p.Dir, file), \"_test\", &t.NeedTest); err != nil {\n+\tfor _, file := range ptest.TestGoFiles {\n+\t\tif err := t.load(filepath.Join(ptest.Dir, file), \"_test\", &t.NeedTest); err != nil {\n \t\t\treturn err\n \t\t}\n \t}\n-\tfor _, file := range p.XTestGoFiles {\n-\t\tif err := t.load(filepath.Join(p.Dir, file), \"_xtest\", &t.NeedXtest); err != nil {\n+\tfor _, file := range ptest.XTestGoFiles {\n+\t\tif err := t.load(filepath.Join(ptest.Dir, file), \"_xtest\", &t.NeedXtest); err != nil {\n \t\t\treturn err\n \t\t}\n \t}\n \n+\tif t.NeedTest {\n+\t\tpmain.imports = append(pmain.imports, ptest)\n+\t}\n+\tif t.NeedXtest {\n+\t\tpmain.imports = append(pmain.imports, pxtest)\n+\t}\n+\n+\tfor _, cp := range pmain.imports {\n+\t\tif len(cp.coverVars) > 0 {\n+\t\t\tt.Cover = append(t.Cover, coverInfo{cp, cp.coverVars})\n+\t\t}\n+\t}\n+\n \tf, err := os.Create(out)\n \tif err != nil {\n \t\treturn err\n```
### `src/cmd/go/testdata/src/xtestonly/f.go` (新規ファイル)
```go
package xtestonly
func F() int { return 42 }
src/cmd/go/testdata/src/xtestonly/f_test.go
(新規ファイル)
package xtestonly_test
import (
"testing"
"xtestonly"
)
func TestF(t *testing.T) {
if x := xtestonly.F(); x != 42 {
t.Errorf("f.F() = %d, want 42", x)
}
}
コアとなるコードの解説
src/cmd/go/test.bash
の変更
このファイルは go
コマンドのテストスクリプトです。追加されたテストケースは、xtestonly
という新しいテストデータパッケージに対して go test
を実行し、その動作が期待通りであることを確認しています。これは、test.go
で行われた変更が、外部テストのみを持つパッケージ(サブモジュールのようなシナリオをシミュレート)で正しく機能することを確認するための統合テストです。
src/cmd/go/test.go
の変更
-
pxtestNeedsPtest
フラグの導入と利用:pxtestNeedsPtest := false
が追加され、外部テストがテスト対象パッケージ自身をインポートしている場合にtrue
に設定されます。pxtest.imports
にptest
を追加する条件がptest != p
からpxtestNeedsPtest
に変更されました。これにより、外部テストが実際にテスト対象パッケージを必要とする場合にのみ、その依存関係がpxtest
に追加されるようになり、より正確な依存関係グラフが構築されます。
-
pmain.imports
の初期化ロジックの変更:- 以前は
ptest
とpxtest
を無条件にpmain.imports
に追加していた行が削除されました。これは、pmain.imports
の更新をwriteTestmain
関数に一元化するためです。 testMainDeps
をループしてpmain.imports
に依存関係を追加する部分で、dep == ptest.ImportPath
の条件が追加されました。これにより、ptest
がtestMainDeps
に含まれる場合にのみpmain.imports
に追加されるようになります。
- 以前は
-
writeTestmain
の呼び出しとpmain.imports
の更新:writeTestmain
関数の呼び出しが、_testmain.go
の書き込みだけでなく、pmain.imports
を更新する役割も持つようになりました。この変更はコメントで明示されています。- 以前の
writeTestmain
呼び出しが削除され、新しいロジックが導入されました。
-
writeTestmain
関数の変更:- シグネチャの変更:
func writeTestmain(out string, pmain, p *Package) error
からfunc writeTestmain(out string, pmain, ptest, pxtest *Package) error
に変更され、ptest
とpxtest
を明示的に受け取るようになりました。これにより、関数内でこれらのパッケージの情報を直接利用できるようになります。 testFuncs
のPackage
フィールド:t.Package
がp
からptest
に変更されました。これは、testFuncs
がテスト対象のパッケージ(ptest
)の情報を基にテスト関数をロードするためです。- テストファイルのロードロジック:
p.TestGoFiles
とp.XTestGoFiles
をループしてテスト関数をロードする際に、p.Dir
ではなくptest.Dir
を使用するように変更されました。 pmain.imports
の動的な更新:t.NeedTest
とt.NeedXtest
というフラグが導入され、それぞれ内部テストと外部テストが存在するかどうかを示します。if t.NeedTest { pmain.imports = append(pmain.imports, ptest) }
とif t.NeedXtest { pmain.imports = append(pmain.imports, pxtest) }
が追加されました。これにより、_testmain.go
が実際にインポートするパッケージのみがpmain.imports
に追加され、recompileForTest
などの後続のビルドステップで正確な依存関係情報が利用されるようになります。
- カバー情報の収集: カバー情報の収集ロジックが
pmain.imports
の更新後に移動されました。これは、pmain.imports
が最終的な状態になってからカバー情報を収集するためです。
- シグネチャの変更:
これらの変更により、go test
は、特に外部テストがテスト対象パッケージをインポートするようなシナリオにおいて、_testmain.go
の生成と依存関係の解決をより正確に行えるようになり、サブモジュールビルドにおけるテストの失敗が修正されました。
src/cmd/go/testdata/src/xtestonly/f.go
および f_test.go
の新規追加
これらのファイルは、test.bash
で追加されたテストケースで使用される新しいテストデータです。
f.go
はxtestonly
パッケージのシンプルな関数F()
を定義しています。f_test.go
はxtestonly_test
パッケージに属する外部テストファイルで、xtestonly
パッケージをインポートしてF()
関数をテストしています。
このテストデータは、外部テストがテスト対象パッケージをインポートする典型的なサブモジュールシナリオを模倣しており、今回の修正が正しく機能することを確認するための重要な要素です。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
go test
コマンドのドキュメント:go help test
または https://pkg.go.dev/cmd/go#hdr-Test_packages- このコミットの変更が提案されたGo Code Review: https://golang.org/cl/96310043
参考にした情報源リンク
- Go言語のソースコード (
src/cmd/go/test.go
) - Go言語のテストに関する公式ドキュメントやブログ記事 (一般的な
go test
の動作理解のため) - Go Code Review の議論 (コミットメッセージに記載されている
golang.org/cl
リンク) - Go言語のパッケージとモジュールに関する一般的な知識