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

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

このコミットは、Go言語のコマンドラインツール cmd/go におけるテストのインポート依存関係のバグを修正するものです。具体的には、src/cmd/go/build.gosrc/cmd/go/pkg.gosrc/cmd/go/test.go の3つのファイルが変更されています。合計で20行が追加され、10行が削除されています。

コミット

  • コミットハッシュ: d08a8848bb0833cfe0dcf6f0fcc3e9f0c1b05e10
  • Author: Russ Cox rsc@golang.org
  • Date: Fri Mar 2 11:27:36 2012 -0500

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

https://github.com/golang/go/commit/d08a8848bb0833cfe0dcf6f0fcc3e9f0c1b05e10

元コミット内容

cmd/go: fix test import dependency bug

Fixes a problem Rob is having with goprotobuf.
Cannot add a test because the same case is more broken
when using ./ imports. That still needs to be fixed,
and is one aspect of issue 3169.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5725043

変更の背景

このコミットは、Go言語のビルドツール cmd/go におけるテストのインポート依存関係に関する既存のバグを修正するために行われました。コミットメッセージによると、この問題は特にRob Pikeが goprotobuf を使用している際に直面したものであり、テストを追加しようとすると、./ (カレントディレクトリ相対) インポートを使用した場合にさらに問題が悪化するという状況でした。

このバグは、Goのテストフレームワークがパッケージの依存関係を正しく解決できないことに起因していると考えられます。特に、テストコードがテスト対象のパッケージ自体をインポートする場合や、テスト専用の外部テストパッケージ(_test サフィックスを持つパッケージ)が関連する依存関係を持つ場合に問題が発生しやすかったようです。

コミットメッセージには「That still needs to be fixed, and is one aspect of issue 3169」とあり、この修正が issue 3169 の一部であり、完全な解決ではないことが示唆されています。issue 3169 は、Goのビルドシステムにおけるより広範なインポートパスの解決に関する問題、特に相対パスや特殊なテストパッケージの扱いに関するものだった可能性があります。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびcmd/goツールの概念を理解しておく必要があります。

  1. cmd/goツール: Go言語の公式ビルドツールであり、パッケージのビルド、テスト、インストール、依存関係の管理など、Go開発における主要な操作を行います。
  2. Goパッケージ: Goのコードはパッケージにまとめられます。各パッケージは独自のインポートパスを持ち、他のパッケージからインポートされることで再利用されます。
  3. インポートパス: Goのパッケージを一意に識別するための文字列です。標準ライブラリのパッケージ(例: fmt, net/http)や、外部モジュールのパッケージ(例: github.com/user/repo/pkg)などがあります。
  4. テストパッケージ: Goでは、テストコードは通常、テスト対象のパッケージと同じディレクトリに配置され、_test.go というサフィックスを持つファイルに記述されます。
    • 内部テスト: テスト対象のパッケージと同じパッケージ名で定義され、パッケージの内部要素(非エクスポートされた関数や変数)にアクセスできます。
    • 外部テスト: テスト対象のパッケージとは異なるパッケージ名(通常は packagename_test)で定義され、パッケージのエクスポートされた要素のみにアクセスできます。これは、パッケージが外部からどのように見えるかをテストするのに役立ちます。
  5. go testコマンド: Goのテストを実行するためのコマンドです。テストファイルをコンパイルし、テストバイナリを実行します。このプロセス中に、テストパッケージとその依存関係がビルドされます。
  6. build.Package構造体: go/buildパッケージで定義されている構造体で、Goのパッケージに関するメタデータ(ファイルリスト、インポートパス、依存関係など)を保持します。cmd/goツール内部でパッケージのビルド情報を管理するために使用されます。
  7. Package構造体 (cmd/go内部): cmd/goツールが内部的に使用するパッケージ表現で、build.Packageをラップし、ビルドプロセスにおける追加の状態(例: Staleフラグ、依存関係グラフ)を管理します。
  8. computeStale関数: cmd/goツール内部で使用される関数で、パッケージが「stale」(古くなっている、再ビルドが必要)であるかどうかを計算します。これは、依存関係グラフを辿り、変更されたファイルや依存パッケージに基づいて再ビルドの必要性を判断するために重要です。ビルドの効率化のために、不要な再ビルドを避ける役割があります。
  9. ./ インポート: カレントディレクトリからの相対パスでパッケージをインポートする方法です。これは通常、モジュールモードが導入される前の古いGoのワークスペースで使われていましたが、問題を引き起こす可能性があり、推奨されません。

技術的詳細

このコミットの技術的詳細は、cmd/goツールがテストパッケージの依存関係と「stale」状態をどのように管理しているか、そしてその管理における既存の欠陥をどのように修正したかに焦点を当てています。

主な問題は、go testコマンドがテスト対象のパッケージ、その内部テスト、および外部テストパッケージをビルドする際に、依存関係の解決と Stale フラグの計算が正しく行われていなかった点にあります。

  1. computeStale関数の引数変更:

    • 変更前: func computeStale(pkgs []*Package) (スライスを受け取る)
    • 変更後: func computeStale(pkgs ...*Package) (可変長引数を受け取る) この変更により、computeStale関数は単一のパッケージまたは複数のパッケージをより柔軟に受け取れるようになりました。これは、goFilesPackage関数やpackagesAndErrors関数からの呼び出しで、単一のpkgオブジェクトを直接渡せるようにするために行われました。
  2. packageList関数における依存関係のトラバース:

    • 変更前: for _, p1 := range p.deps
    • 変更後: for _, p1 := range p.imports packageList関数は、パッケージの依存関係グラフをトラバースして、ビルド順序を決定したり、関連するパッケージのリストを収集したりするために使用されます。以前はp.deps(おそらくビルド時の依存関係)を見ていましたが、p.imports(ソースコードで宣言されたインポート)を見るように変更されました。これにより、テストパッケージが実際にインポートしているパッケージに基づいて依存関係がより正確に解決されるようになります。
  3. テストパッケージの Stale フラグ設定と computeStale の呼び出し: src/cmd/go/test.goにおける最も重要な変更は、テスト実行時に生成される一時的なテストパッケージ(ptest, pxtest, pmain)のStaleフラグの扱いと、それらに対するcomputeStaleの呼び出しです。

    • p.XTestImportsの自己インポート回避: for _, path := range p.XTestImports ループ内で、if path == p.ImportPath { continue } という行が追加されました。これは、外部テストパッケージがテスト対象のパッケージ自体をインポートしようとした場合に、無限ループや不正確な依存関係の解決を防ぐためのガードです。
    • 生成されるテストパッケージの Stale フラグ設定: ptest (内部テストパッケージ)、pxtest (外部テストパッケージ)、pmain (テスト実行バイナリのメインパッケージ) の各生成時に、ptest.Stale = truepxtest.Stale = truepmain.Stale = true が明示的に設定されるようになりました。これにより、これらの生成されたパッケージは常に「stale」であるとマークされ、go testが実行されるたびに再ビルドされることが保証されます。これは、テストバイナリが常に最新のソースコードに基づいてビルドされるべきであるため、非常に重要です。
    • 生成されるテストパッケージに対する computeStale の呼び出し: ptestpxtestpmain の各パッケージが設定された後、それぞれに対して computeStale(ptest)computeStale(pxtest)computeStale(pmain) が呼び出されています。これにより、これらの生成されたテストパッケージとその依存関係ツリー全体に対して、Staleフラグが正しく伝播・計算されるようになります。以前は、これらの生成されたパッケージに対するStale計算が不十分だったため、テストの依存関係が正しく解決されず、Rob Pikeが直面したような問題が発生していたと考えられます。
    • pmainImportPath設定: pmainパッケージに ImportPath: "testmain" が追加されました。これは、生成されるテスト実行バイナリのメインパッケージに一意のインポートパスを与えることで、ビルドシステム内での識別と管理を容易にするためと考えられます。

これらの変更により、cmd/goはテストパッケージの依存関係をより正確に追跡し、テストバイナリが常に最新の状態であることを保証できるようになりました。特に、Staleフラグの適切な設定とcomputeStaleの呼び出しは、テストのビルドプロセスにおけるキャッシュの無効化と正確な再ビルドを保証するために不可欠です。

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

src/cmd/go/build.go

--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -363,7 +363,7 @@ func goFilesPackage(gofiles []string) *Package {
 	pkg.Target = ""
 	pkg.Stale = true
 
-	computeStale([]*Package{pkg})
+	computeStale(pkg)
 	return pkg
 }

src/cmd/go/pkg.go

--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -376,7 +376,7 @@ func packageList(roots []*Package) []*Package {
 			return
 		}
 		seen[p] = true
-		for _, p1 := range p.deps {
+		for _, p1 := range p.imports {
 			walk(p1)
 		}
 		all = append(all, p)
@@ -389,7 +389,7 @@ func packageList(roots []*Package) []*Package {
 
 // computeStale computes the Stale flag in the package dag that starts
 // at the named pkgs (command-line arguments).
-func computeStale(pkgs []*Package) {
+func computeStale(pkgs ...*Package) {
 	topRoot := map[string]bool{}
 	for _, p := range pkgs {
 		topRoot[p.Root] = true
@@ -579,7 +579,7 @@ func packagesAndErrors(args []string) []*Package {
 		pkgs = append(pkgs, loadPackage(arg, &stk))
 	}
 
-	computeStale(pkgs)
+	computeStale(pkgs...)
 
 	return pkgs
 }

src/cmd/go/test.go

--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -396,6 +396,9 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 		imports = append(imports, p1)
 	}
 	for _, path := range p.XTestImports {
+		if path == p.ImportPath {
+			continue
+		}
 		p1 := loadImport(path, p.Dir, &stk, p.build.XTestImportPos[path])
 		if p1.Error != nil {
 			return nil, nil, nil, p1.Error
@@ -447,6 +450,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 		ptest.imports = append(append([]*Package{}, p.imports...), imports...)
 		ptest.pkgdir = testDir
 		ptest.fake = true
+		ptest.Stale = true
 		ptest.build = new(build.Package)
 		*ptest.build = *p.build
 		m := map[string][]token.Position{}
@@ -457,6 +461,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 		m[k] = append(m[k], v...)
 		}
 		ptest.build.ImportPos = m
+		computeStale(ptest)
 		a := b.action(modeBuild, modeBuild, ptest)
 		a.objdir = testDir + string(filepath.Separator)
 		a.objpkg = ptestObj
@@ -480,7 +485,9 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 			imports: append(ximports, ptest),
 			pkgdir:  testDir,
 			fake:    true,
+			Stale:   true,
 		}
+		computeStale(pxtest)
 		a := b.action(modeBuild, modeBuild, pxtest)
 		a.objdir = testDir + string(filepath.Separator)
 		a.objpkg = buildToolchain.pkgpath(testDir, pxtest)
@@ -489,12 +496,14 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 
 	// Action for building pkg.test.
 	pmain = &Package{
-		Name:    "main",
-		Dir:     testDir,
-		GoFiles: []string{"_testmain.go"},
-		imports: []*Package{ptest},
-		build:   &build.Package{},
-		fake:    true,
+		Name:       "main",
+		Dir:        testDir,
+		GoFiles:    []string{"_testmain.go"},
+		ImportPath: "testmain",
+		imports:    []*Package{ptest},
+		build:      &build.Package{},
+		fake:       true,
+		Stale:      true,
 	}
 	if pxtest != nil {
 		pmain.imports = append(pmain.imports, pxtest)
@@ -511,6 +520,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 		return nil, nil, nil, pregexp.Error
 	}
 	pmain.imports = append(pmain.imports, ptesting, pregexp)
+	computeStale(pmain)
 
 	a := b.action(modeBuild, modeBuild, pmain)
 	a.objdir = testDir + string(filepath.Separator)

コアとなるコードの解説

src/cmd/go/build.go の変更

  • goFilesPackage 関数内の computeStale の呼び出しが computeStale([]*Package{pkg}) から computeStale(pkg) に変更されました。これは、computeStale のシグネチャがスライスではなく可変長引数を受け取るように変更されたことに対応しています。これにより、単一のパッケージを渡す際に冗長なスライス作成が不要になります。

src/cmd/go/pkg.go の変更

  • packageList 関数内の依存関係のトラバースロジックが p.deps から p.imports を参照するように変更されました。p.imports はソースコードで明示的にインポートされているパッケージのリストであり、p.deps はビルド時に解決された依存関係のリストである可能性があります。この変更により、パッケージの依存関係グラフの構築が、よりソースコードの記述に忠実な形で行われるようになります。テストパッケージの依存関係を正確に追跡するために重要です。
  • computeStale 関数のシグネチャが func computeStale(pkgs []*Package) から func computeStale(pkgs ...*Package) に変更されました。これにより、この関数は任意の数の *Package 引数を受け取れるようになり、呼び出し元での柔軟性が向上します。
  • packagesAndErrors 関数内の computeStale の呼び出しが computeStale(pkgs) から computeStale(pkgs...) に変更されました。これは、computeStale のシグネチャ変更に対応するもので、スライスを可変長引数として展開して渡しています。

src/cmd/go/test.go の変更

このファイルは、go test コマンドの動作を定義する上で最も重要な変更が含まれています。

  • 外部テストの自己インポート回避: for _, path := range p.XTestImports ループ内に、if path == p.ImportPath { continue } という条件が追加されました。これは、外部テストパッケージ(XTestImports にリストされる)が、テスト対象のパッケージ自体をインポートしようとする場合に、そのインポートをスキップするためのものです。これにより、循環依存や不正確な依存関係の解決を防ぎ、特に ../ を使った相対インポートで発生しがちな問題を緩和します。

  • 生成されるテストパッケージの Stale フラグ設定と computeStale の呼び出し: go test は、テストを実行するためにいくつかの一時的な Package オブジェクトを内部的に生成します。これらは ptest (内部テストパッケージ)、pxtest (外部テストパッケージ)、pmain (テスト実行バイナリのメインパッケージ) です。

    • これらの生成されたパッケージに対して、明示的に Stale = true が設定されるようになりました。これは、テストが実行されるたびにこれらのパッケージが常に再ビルドされるべきであることを保証します。テストバイナリは常に最新のソースコードとテストコードを反映している必要があります。
    • それぞれのパッケージが設定された後、computeStale(ptest)computeStale(pxtest)computeStale(pmain) が呼び出されています。これにより、これらの生成されたパッケージだけでなく、それらが依存するすべてのパッケージに対しても Stale フラグが適切に計算され、必要に応じて再ビルドがトリガーされるようになります。これにより、テストの依存関係がより正確に解決され、Rob Pikeが直面したような「テストインポート依存関係バグ」が修正されます。
  • pmain パッケージへの ImportPath の追加: pmain パッケージの定義に ImportPath: "testmain" が追加されました。これは、生成されるテスト実行バイナリのメインパッケージに一意のインポートパスを与えることで、ビルドシステム内での識別と管理を容易にするためと考えられます。

これらの変更は、Goのビルドシステムがテストパッケージの複雑な依存関係をより堅牢に処理できるようにするためのものであり、特に go test コマンドの信頼性と正確性を向上させることを目的としています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (パッケージ、テスト、goコマンドに関する情報)
  • Go言語のソースコード (src/cmd/go ディレクトリ内の関連ファイル)
  • Go Issue Tracker (特に issue 3169 の議論)
  • goprotobuf プロジェクトに関する一般的な情報 (Rob Pikeが直面した問題の文脈理解のため)
  • Goのビルドシステムと依存関係解決に関する一般的な知識