[インデックス 11308] ファイルの概要
このコミットは、Go言語のgo test
コマンドにおけるバグ修正を目的としています。具体的には、regexp
パッケージがregexp/syntax
をインポートし、さらにregexp/syntax
がbytes
パッケージをインポートするという依存関係の連鎖において、bytes
パッケージの変更時にregexp/syntax
が適切に再コンパイルされない問題に対処しています。この修正により、すべてのテスト実行時にregexp
パッケージが明示的にインポートされるようになり、依存関係の再コンパイルが保証されます。
コミット
commit e56dc0ad37b2c736668b6069d3f39a3b5f309e87
Author: Russ Cox <rsc@golang.org>
Date: Fri Jan 20 23:35:28 2012 -0500
cmd/go: every test imports regexp
This fixes the bug Rob ran into when editing package bytes.
Regexp imports regexp/syntax, which imports bytes, and
regexp/syntax was not being properly recompiled during a
test of a change to package bytes.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5555065
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e56dc0ad37b2c736668b6069d3f39a3b5f309e87
元コミット内容
このコミットは、cmd/go
(go
コマンドの内部実装)において、すべてのテストがregexp
パッケージをインポートするように変更するものです。これにより、Robがbytes
パッケージを編集した際に遭遇したバグが修正されます。このバグは、regexp
がregexp/syntax
をインポートし、さらにregexp/syntax
がbytes
をインポートするという依存関係があるにもかかわらず、bytes
パッケージの変更時にregexp/syntax
がテスト中に適切に再コンパイルされないというものでした。
変更の背景
Go言語のビルドシステム、特にgo test
コマンドは、テスト対象のパッケージとその依存関係を適切にコンパイルし、リンクする必要があります。このコミットの背景にある問題は、Goのパッケージ管理とコンパイルの仕組みに起因しています。
Robという開発者がbytes
パッケージに変更を加えた際、go test
を実行しても、その変更がregexp/syntax
パッケージに正しく反映されず、結果としてテストが失敗するという現象が発生しました。これは、regexp
パッケージが正規表現の構文解析を行うregexp/syntax
パッケージに依存し、さらにregexp/syntax
パッケージがバイトスライス操作を行うbytes
パッケージに依存しているためです。
通常のGoのビルドプロセスでは、依存関係が変更された場合、その依存関係を使用しているパッケージも再コンパイルされます。しかし、go test
コマンドがテスト実行のために生成する一時的なメインパッケージ(テストバイナリ)のビルドプロセスにおいて、この特定の依存関係の連鎖(bytes
-> regexp/syntax
-> regexp
)が正しく追跡されず、bytes
の変更がregexp/syntax
の再コンパイルをトリガーしないというバグが存在していました。
この問題は、テストの信頼性を損なうだけでなく、開発者がパッケージの変更をテストする際に予期せぬ挙動に遭遇する原因となっていました。そのため、このバグを修正し、go test
のビルドプロセスがすべての依存関係の変更を確実に検出して再コンパイルするようにすることが急務でした。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とgo
コマンドの動作に関する知識が必要です。
- Goパッケージシステム: Goのコードはパッケージに分割され、他のパッケージをインポートして利用します。パッケージ間の依存関係は、コンパイル時に解決されます。
go test
コマンド:go test
は、Goプロジェクトのテストを実行するためのコマンドです。- テスト対象のパッケージとその依存関係をコンパイルし、テストバイナリを生成して実行します。
- テストバイナリは、テスト関数(
TestXxx
)やベンチマーク関数(BenchmarkXxx
)を含む一時的なメインパッケージとして生成されます。 - この生成されたメインパッケージは、テストフレームワークである
testing
パッケージをインポートします。
- コンパイルと依存関係:
- Goコンパイラは、ソースコードをコンパイルする際に、インポートされているパッケージの変更を検出すると、そのパッケージを再コンパイルします。
- この再コンパイルの仕組みは、ビルドの効率性と正確性を保つために重要です。
regexp
、regexp/syntax
、bytes
パッケージ:bytes
パッケージ: バイトスライスを操作するための基本的な関数を提供します。regexp/syntax
パッケージ: 正規表現の構文解析ツリーを構築するための内部パッケージです。regexp
パッケージ: 正規表現のマッチング機能を提供します。このパッケージは、内部的にregexp/syntax
パッケージを利用して正規表現を解析します。- 依存関係:
regexp
->regexp/syntax
->bytes
問題は、go test
がテストバイナリを生成する際に、このregexp
パッケージの依存関係ツリー(特にregexp/syntax
がbytes
に依存していること)を完全に考慮していなかった点にありました。bytes
パッケージが変更されても、regexp/syntax
が再コンパイルされないため、古いバージョンのregexp/syntax
がテストバイナリに含まれてしまい、結果としてregexp
パッケージを使用するテストが期待通りに動作しない、またはコンパイルエラーになる可能性がありました。
技術的詳細
このバグの根本原因は、go test
コマンドがテスト実行のために生成する「メインパッケージ」(テストバイナリのエントリポイントとなるパッケージ)の依存関係解決の仕組みにありました。
通常、go test
はテスト対象のパッケージをコンパイルし、そのパッケージ内のテスト関数を呼び出すための特別なmain
関数を持つ一時的なGoソースファイルを生成します。この生成されたmain
パッケージは、テスト対象のパッケージやtesting
パッケージなど、テストに必要なパッケージをインポートします。
問題は、regexp
パッケージが直接テスト対象のパッケージによってインポートされていなかった場合、またはregexp
が間接的にインポートされている場合(今回のケースのようにregexp
-> regexp/syntax
-> bytes
という依存関係の連鎖で、bytes
が変更された場合)、go test
が生成するメインパッケージがregexp
パッケージを明示的にインポートしない限り、その依存関係の変更がビルドシステムによって適切に検出されない可能性があったことです。
Goのビルドシステムは、インポートパスに基づいてパッケージの依存関係を追跡します。もし、あるパッケージがテストバイナリの直接的または間接的な依存関係として認識されない場合、そのパッケージのソースコードが変更されても、ビルドシステムはそれを再コンパイルする必要があると判断しないことがあります。
今回のケースでは、bytes
パッケージの変更がregexp/syntax
の再コンパイルをトリガーしなかったのは、regexp/syntax
がテストバイナリのビルドグラフにおいて、bytes
の変更を検出するための十分な「可視性」を持っていなかったためと考えられます。つまり、go test
が生成するメインパッケージがregexp
を直接インポートしていなかったため、regexp
の依存関係ツリー全体がビルドシステムによって完全に考慮されず、bytes
の変更がregexp/syntax
の再コンパイルに繋がらなかったのです。
この修正は、この問題を回避するために、go test
が生成するすべてのテストバイナリが常にregexp
パッケージをインポートするように変更します。これにより、regexp
パッケージとそのすべての推移的な依存関係(regexp/syntax
やbytes
など)が、テストバイナリのビルドグラフに明示的に含まれることになります。結果として、bytes
パッケージが変更された場合でも、ビルドシステムはregexp/syntax
がbytes
に依存していることを認識し、regexp/syntax
を再コンパイルし、その変更がテストバイナリに確実に反映されるようになります。
これは、特定の依存関係の連鎖におけるビルドシステムの盲点を補うための、堅牢な解決策と言えます。
コアとなるコードの変更箇所
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -406,6 +406,18 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
if pxtest != nil {
pmain.imports = append(pmain.imports, pxtest)
}
+
+ // The generated main also imports testing and regexp.
+ ptesting, err := loadPackage("testing")
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ pregexp, err := loadPackage("regexp")
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ pmain.imports = append(pmain.imports, ptesting, pregexp)
+
a := b.action(modeBuild, modeBuild, pmain)
a.objdir = testDir + string(filepath.Separator)
a.objpkg = filepath.Join(testDir, "main.a")
コアとなるコードの解説
変更はsrc/cmd/go/test.go
ファイル内のtest
関数にあります。この関数は、go test
コマンドがテストを実行するために必要なビルドアクションを生成する部分です。
追加されたコードは以下の通りです。
// The generated main also imports testing and regexp.
ptesting, err := loadPackage("testing")
if err != nil {
return nil, nil, nil, err
}
pregexp, err := loadPackage("regexp")
if err != nil {
return nil, nil, nil, err
}
pmain.imports = append(pmain.imports, ptesting, pregexp)
-
// The generated main also imports testing and regexp.
- このコメントは、生成されるテスト用のメインパッケージが
testing
とregexp
の両方をインポートすることを示すものです。
- このコメントは、生成されるテスト用のメインパッケージが
-
ptesting, err := loadPackage("testing")
loadPackage
関数は、指定されたパッケージ名(ここでは"testing"
)に対応する*Package
構造体をロードします。この構造体には、パッケージのパス、依存関係、ソースファイルなどの情報が含まれています。- エラーが発生した場合(例:
testing
パッケージが見つからない場合)、関数はエラーを返します。
-
pregexp, err := loadPackage("regexp")
- 同様に、
"regexp"
パッケージの*Package
構造体をロードします。 - ここがこのコミットの核心部分です。
regexp
パッケージを明示的にロードすることで、そのパッケージがテストバイナリの依存関係ツリーに確実に含まれるようになります。
- 同様に、
-
pmain.imports = append(pmain.imports, ptesting, pregexp)
pmain
は、go test
がテスト実行のために生成する一時的なメインパッケージを表す*Package
構造体です。pmain.imports
は、このメインパッケージがインポートするパッケージのリストです。- この行では、ロードした
ptesting
(testing
パッケージ)とpregexp
(regexp
パッケージ)をpmain.imports
リストに追加しています。
この変更により、go test
によって生成されるすべてのテストバイナリは、たとえテスト対象のコードが直接regexp
パッケージを使用していなくても、常にregexp
パッケージをインポートするようになります。これにより、regexp
パッケージとその推移的な依存関係(regexp/syntax
、bytes
など)がビルドシステムによって常に考慮され、bytes
パッケージのような下流の依存関係が変更された場合でも、regexp/syntax
が適切に再コンパイルされることが保証されます。結果として、Robが遭遇したような依存関係の再コンパイル漏れによるバグが解消されます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/e56dc0ad37b2c736668b6069d3f39a3b5f309e87
- Gerrit Change-ID: https://golang.org/cl/5555065 (Goプロジェクトのコードレビューシステムへのリンク)
参考にした情報源リンク
- Go言語公式ドキュメント (go test): https://pkg.go.dev/cmd/go#hdr-Test_packages
- Go言語公式ドキュメント (regexpパッケージ): https://pkg.go.dev/regexp
- Go言語公式ドキュメント (bytesパッケージ): https://pkg.go.dev/bytes
- Go言語のビルドシステムに関する一般的な情報 (Goのコンパイルとリンクの仕組み): 必要に応じてGoの公式ブログや関連する技術記事を参照。