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

[インデックス 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 として生成される)をビルドします。この際、pmainptestpxtest を正しくインポートし、それらの依存関係を解決する必要がありました。以前の実装では、この依存関係の解決ロジックに不備があり、特に pxtestptest と同じインポートパスを持つ場合に問題が生じていました。

この問題は、go test がテストバイナリを生成する際に、テスト対象パッケージと外部テストパッケージの間の依存関係を誤って解釈し、結果としてテストが失敗したり、ビルドエラーが発生したりすることにつながっていました。

前提知識の解説

このコミットを理解するためには、以下のGo言語および go test コマンドに関する前提知識が必要です。

  1. Goパッケージとインポートパス: Goのコードはパッケージにまとめられ、import ステートメントによって他のパッケージを参照します。パッケージはファイルシステム上のディレクトリに対応し、そのパスはインポートパスとして機能します。
  2. go test コマンド: go test はGoパッケージのテストを実行するためのコマンドです。テストファイルは _test.go というサフィックスを持ち、テスト関数は TestXxx という命名規則に従います。
  3. 内部テストと外部テスト:
    • 内部テスト: テスト対象のパッケージと同じパッケージ名を持つ _test.go ファイルに記述されたテストです。これらのテストは、テスト対象パッケージの内部要素(非エクスポートされた関数や変数)にアクセスできます。
    • 外部テスト: テスト対象のパッケージとは異なるパッケージ名(通常は _test サフィックスが付く、例: mypackage_test)を持つ _test.go ファイルに記述されたテストです。これらのテストは、テスト対象パッケージを外部からインポートしてテストするため、エクスポートされた要素のみにアクセスできます。このコミットで修正されているのは、主にこの外部テストに関する問題です。
  4. _testmain.go の生成: go test コマンドは、テストを実行するための特別なGoソースファイル _testmain.go を自動的に生成します。このファイルには、すべてのテスト関数を呼び出す main 関数が含まれており、テストのセットアップ、実行、結果の報告などを担当します。この _testmain.go は、テスト対象のパッケージ、内部テストパッケージ、外部テストパッケージを適切にインポートする必要があります。
  5. Package 構造体とビルドプロセス: src/cmd/go/test.go 内では、Package 構造体がGoパッケージのメタデータを表現します。builder 構造体はビルドプロセスを管理し、test メソッドがテストのビルドと実行ロジックをカプセル化しています。
    • ptest: テスト対象のパッケージ(内部テストを含む)。
    • pxtest: 外部テストパッケージ。
    • pmain: _testmain.go を生成するための仮想的なパッケージ。
  6. p.XTestImports: パッケージの外部テストがインポートするパッケージのリスト。
  7. pmain.imports: _testmain.go がインポートするパッケージのリスト。このリストは、_testmain.go がビルドされる際に、必要な依存関係を解決するために使用されます。

技術的詳細

このコミットの技術的な詳細は、主に src/cmd/go/test.go 内の test 関数と writeTestmain 関数の変更に集約されます。

以前の実装では、writeTestmain 関数が _testmain.go を書き込む際に、pmain.imports を更新するロジックが不完全でした。特に、ptestpxtest の関係性、および pxtestptest と同じインポートパスを持つ場合の処理に問題がありました。

このコミットでは、以下の主要な変更が行われています。

  1. pxtestNeedsPtest フラグの導入: test 関数内で、p.XTestImports をループする際に、path == p.ImportPath(つまり、外部テストがテスト対象パッケージ自身をインポートしている場合)に pxtestNeedsPtest という新しいフラグを true に設定するように変更されました。これは、外部テストがテスト対象パッケージに依存していることを明示的に示すためのものです。

  2. ptestpxtest へのインポートロジックの修正: 以前は if ptest != p という条件で pxtest.importsptest を追加していましたが、これを if pxtestNeedsPtest に変更しました。これにより、外部テストが実際にテスト対象パッケージを必要とする場合にのみ、その依存関係が pxtest に追加されるようになります。これは、サブモジュールのような構成で、外部テストがテスト対象パッケージをインポートするシナリオを正確に処理するために重要です。

  3. pmain.imports の更新ロジックの変更: pmain がインポートするパッケージのリスト (pmain.imports) を構築するロジックが大幅に修正されました。

    • 以前は ptestpxtest を無条件に pmain.imports に追加していましたが、この部分が削除されました。
    • 代わりに、writeTestmain 関数が _testmain.go を書き込む際に、実際に ptestpxtest が必要かどうかを判断し、その結果に基づいて pmain.imports を更新するように変更されました。これは、writeTestmain_testmain.go に書き込むインポートステートメントと pmain.imports の内容を同期させるための重要な変更です。
  4. writeTestmain 関数のシグネチャとロジックの変更:

    • writeTestmain 関数のシグネチャが func writeTestmain(out string, pmain, p *Package) error から func writeTestmain(out string, pmain, ptest, pxtest *Package) error に変更され、ptestpxtest の両方を直接受け取るようになりました。
    • writeTestmain 内で、t.NeedTestt.NeedXtest というフラグを使用して、それぞれ ptestpxtest_testmain.go にインポートされる必要があるかを判断します。
    • これらのフラグに基づいて、pmain.importsptest および 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 の変更

  1. pxtestNeedsPtest フラグの導入と利用:

    • pxtestNeedsPtest := false が追加され、外部テストがテスト対象パッケージ自身をインポートしている場合に true に設定されます。
    • pxtest.importsptest を追加する条件が ptest != p から pxtestNeedsPtest に変更されました。これにより、外部テストが実際にテスト対象パッケージを必要とする場合にのみ、その依存関係が pxtest に追加されるようになり、より正確な依存関係グラフが構築されます。
  2. pmain.imports の初期化ロジックの変更:

    • 以前は ptestpxtest を無条件に pmain.imports に追加していた行が削除されました。これは、pmain.imports の更新を writeTestmain 関数に一元化するためです。
    • testMainDeps をループして pmain.imports に依存関係を追加する部分で、dep == ptest.ImportPath の条件が追加されました。これにより、ptesttestMainDeps に含まれる場合にのみ pmain.imports に追加されるようになります。
  3. writeTestmain の呼び出しと pmain.imports の更新:

    • writeTestmain 関数の呼び出しが、_testmain.go の書き込みだけでなく、pmain.imports を更新する役割も持つようになりました。この変更はコメントで明示されています。
    • 以前の writeTestmain 呼び出しが削除され、新しいロジックが導入されました。
  4. writeTestmain 関数の変更:

    • シグネチャの変更: func writeTestmain(out string, pmain, p *Package) error から func writeTestmain(out string, pmain, ptest, pxtest *Package) error に変更され、ptestpxtest を明示的に受け取るようになりました。これにより、関数内でこれらのパッケージの情報を直接利用できるようになります。
    • testFuncsPackage フィールド: t.Packagep から ptest に変更されました。これは、testFuncs がテスト対象のパッケージ(ptest)の情報を基にテスト関数をロードするためです。
    • テストファイルのロードロジック: p.TestGoFilesp.XTestGoFiles をループしてテスト関数をロードする際に、p.Dir ではなく ptest.Dir を使用するように変更されました。
    • pmain.imports の動的な更新:
      • t.NeedTestt.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.goxtestonly パッケージのシンプルな関数 F() を定義しています。
  • f_test.goxtestonly_test パッケージに属する外部テストファイルで、xtestonly パッケージをインポートして F() 関数をテストしています。

このテストデータは、外部テストがテスト対象パッケージをインポートする典型的なサブモジュールシナリオを模倣しており、今回の修正が正しく機能することを確認するための重要な要素です。

関連リンク

参考にした情報源リンク

  • 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 として生成される)をビルドします。この際、pmainptestpxtest を正しくインポートし、それらの依存関係を解決する必要がありました。以前の実装では、この依存関係の解決ロジックに不備があり、特に pxtestptest と同じインポートパスを持つ場合に問題が生じていました。

この問題は、go test がテストバイナリを生成する際に、テスト対象パッケージと外部テストパッケージの間の依存関係を誤って解釈し、結果としてテストが失敗したり、ビルドエラーが発生したりすることにつながっていました。

前提知識の解説

このコミットを理解するためには、以下のGo言語および go test コマンドに関する前提知識が必要です。

  1. Goパッケージとインポートパス: Goのコードはパッケージにまとめられ、import ステートメントによって他のパッケージを参照します。パッケージはファイルシステム上のディレクトリに対応し、そのパスはインポートパスとして機能します。
  2. go test コマンド: go test はGoパッケージのテストを実行するためのコマンドです。テストファイルは _test.go というサフィックスを持ち、テスト関数は TestXxx という命名規則に従います。
  3. 内部テストと外部テスト:
    • 内部テスト: テスト対象のパッケージと同じパッケージ名を持つ _test.go ファイルに記述されたテストです。これらのテストは、テスト対象パッケージの内部要素(非エクスポートされた関数や変数)にアクセスできます。
    • 外部テスト: テスト対象のパッケージとは異なるパッケージ名(通常は _test サフィックスが付く、例: mypackage_test)を持つ _test.go ファイルに記述されたテストです。これらのテストは、テスト対象パッケージを外部からインポートしてテストするため、エクスポートされた要素のみにアクセスできます。このコミットで修正されているのは、主にこの外部テストに関する問題です。
  4. _testmain.go の生成: go test コマンドは、テストを実行するための特別なGoソースファイル _testmain.go を自動的に生成します。このファイルには、すべてのテスト関数を呼び出す main 関数が含まれており、テストのセットアップ、実行、結果の報告などを担当します。この _testmain.go は、テスト対象のパッケージ、内部テストパッケージ、外部テストパッケージを適切にインポートする必要があります。
  5. Package 構造体とビルドプロセス: src/cmd/go/test.go 内では、Package 構造体がGoパッケージのメタデータを表現します。builder 構造体はビルドプロセスを管理し、test メソッドがテストのビルドと実行ロジックをカプセル化しています。
    • ptest: テスト対象のパッケージ(内部テストを含む)。
    • pxtest: 外部テストパッケージ。
    • pmain: _testmain.go を生成するための仮想的なパッケージ。
  6. p.XTestImports: パッケージの外部テストがインポートするパッケージのリスト。
  7. pmain.imports: _testmain.go がインポートするパッケージのリスト。このリストは、_testmain.go がビルドされる際に、必要な依存関係を解決するために使用されます。

技術的詳細

このコミットの技術的な詳細は、主に src/cmd/go/test.go 内の test 関数と writeTestmain 関数の変更に集約されます。

以前の実装では、writeTestmain 関数が _testmain.go を書き込む際に、pmain.imports を更新するロジックが不完全でした。特に、ptestpxtest の関係性、および pxtestptest と同じインポートパスを持つ場合の処理に問題がありました。

このコミットでは、以下の主要な変更が行われています。

  1. pxtestNeedsPtest フラグの導入: test 関数内で、p.XTestImports をループする際に、path == p.ImportPath(つまり、外部テストがテスト対象パッケージ自身をインポートしている場合)に pxtestNeedsPtest という新しいフラグを true に設定するように変更されました。これは、外部テストがテスト対象パッケージに依存していることを明示的に示すためのものです。

  2. ptestpxtest へのインポートロジックの修正: 以前は if ptest != p という条件で pxtest.importsptest を追加していましたが、これを if pxtestNeedsPtest に変更しました。これにより、外部テストが実際にテスト対象パッケージを必要とする場合にのみ、その依存関係が pxtest に追加されるようになります。これは、サブモジュールのような構成で、外部テストがテスト対象パッケージをインポートするシナリオを正確に処理するために重要です。

  3. pmain.imports の更新ロジックの変更: pmain がインポートするパッケージのリスト (pmain.imports) を構築するロジックが大幅に修正されました。

    • 以前は ptestpxtest を無条件に pmain.imports に追加していましたが、この部分が削除されました。
    • 代わりに、writeTestmain 関数が _testmain.go を書き込む際に、実際に ptestpxtest が必要かどうかを判断し、その結果に基づいて pmain.imports を更新するように変更されました。これは、writeTestmain_testmain.go に書き込むインポートステートメントと pmain.imports の内容を同期させるための重要な変更です。
  4. writeTestmain 関数のシグネチャとロジックの変更:

    • writeTestmain 関数のシグネチャが func writeTestmain(out string, pmain, p *Package) error から func writeTestmain(out string, pmain, ptest, pxtest *Package) error に変更され、ptestpxtest の両方を直接受け取るようになりました。
    • writeTestmain 内で、t.NeedTestt.NeedXtest というフラグを使用して、それぞれ ptestpxtest_testmain.go にインポートされる必要があるかを判断します。
    • これらのフラグに基づいて、pmain.importsptest および 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 の変更

  1. pxtestNeedsPtest フラグの導入と利用:

    • pxtestNeedsPtest := false が追加され、外部テストがテスト対象パッケージ自身をインポートしている場合に true に設定されます。
    • pxtest.importsptest を追加する条件が ptest != p から pxtestNeedsPtest に変更されました。これにより、外部テストが実際にテスト対象パッケージを必要とする場合にのみ、その依存関係が pxtest に追加されるようになり、より正確な依存関係グラフが構築されます。
  2. pmain.imports の初期化ロジックの変更:

    • 以前は ptestpxtest を無条件に pmain.imports に追加していた行が削除されました。これは、pmain.imports の更新を writeTestmain 関数に一元化するためです。
    • testMainDeps をループして pmain.imports に依存関係を追加する部分で、dep == ptest.ImportPath の条件が追加されました。これにより、ptesttestMainDeps に含まれる場合にのみ pmain.imports に追加されるようになります。
  3. writeTestmain の呼び出しと pmain.imports の更新:

    • writeTestmain 関数の呼び出しが、_testmain.go の書き込みだけでなく、pmain.imports を更新する役割も持つようになりました。この変更はコメントで明示されています。
    • 以前の writeTestmain 呼び出しが削除され、新しいロジックが導入されました。
  4. writeTestmain 関数の変更:

    • シグネチャの変更: func writeTestmain(out string, pmain, p *Package) error から func writeTestmain(out string, pmain, ptest, pxtest *Package) error に変更され、ptestpxtest を明示的に受け取るようになりました。これにより、関数内でこれらのパッケージの情報を直接利用できるようになります。
    • testFuncsPackage フィールド: t.Packagep から ptest に変更されました。これは、testFuncs がテスト対象のパッケージ(ptest)の情報を基にテスト関数をロードするためです。
    • テストファイルのロードロジック: p.TestGoFilesp.XTestGoFiles をループしてテスト関数をロードする際に、p.Dir ではなく ptest.Dir を使用するように変更されました。
    • pmain.imports の動的な更新:
      • t.NeedTestt.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.goxtestonly パッケージのシンプルな関数 F() を定義しています。
  • f_test.goxtestonly_test パッケージに属する外部テストファイルで、xtestonly パッケージをインポートして F() 関数をテストしています。

このテストデータは、外部テストがテスト対象パッケージをインポートする典型的なサブモジュールシナリオを模倣しており、今回の修正が正しく機能することを確認するための重要な要素です。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (src/cmd/go/test.go)
  • Go言語のテストに関する公式ドキュメントやブログ記事 (一般的な go test の動作理解のため)
  • Go Code Review の議論 (コミットメッセージに記載されている golang.org/cl リンク)
  • Go言語のパッケージとモジュールに関する一般的な知識