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

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

このコミットは、Go言語のgo testコマンドにおけるバグ修正を目的としています。具体的には、regexpパッケージがregexp/syntaxをインポートし、さらにregexp/syntaxbytesパッケージをインポートするという依存関係の連鎖において、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/gogoコマンドの内部実装)において、すべてのテストがregexpパッケージをインポートするように変更するものです。これにより、Robがbytesパッケージを編集した際に遭遇したバグが修正されます。このバグは、regexpregexp/syntaxをインポートし、さらにregexp/syntaxbytesをインポートするという依存関係があるにもかかわらず、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コマンドの動作に関する知識が必要です。

  1. Goパッケージシステム: Goのコードはパッケージに分割され、他のパッケージをインポートして利用します。パッケージ間の依存関係は、コンパイル時に解決されます。
  2. go testコマンド:
    • go testは、Goプロジェクトのテストを実行するためのコマンドです。
    • テスト対象のパッケージとその依存関係をコンパイルし、テストバイナリを生成して実行します。
    • テストバイナリは、テスト関数(TestXxx)やベンチマーク関数(BenchmarkXxx)を含む一時的なメインパッケージとして生成されます。
    • この生成されたメインパッケージは、テストフレームワークであるtestingパッケージをインポートします。
  3. コンパイルと依存関係:
    • Goコンパイラは、ソースコードをコンパイルする際に、インポートされているパッケージの変更を検出すると、そのパッケージを再コンパイルします。
    • この再コンパイルの仕組みは、ビルドの効率性と正確性を保つために重要です。
  4. regexpregexp/syntaxbytesパッケージ:
    • bytesパッケージ: バイトスライスを操作するための基本的な関数を提供します。
    • regexp/syntaxパッケージ: 正規表現の構文解析ツリーを構築するための内部パッケージです。
    • regexpパッケージ: 正規表現のマッチング機能を提供します。このパッケージは、内部的にregexp/syntaxパッケージを利用して正規表現を解析します。
    • 依存関係: regexp -> regexp/syntax -> bytes

問題は、go testがテストバイナリを生成する際に、このregexpパッケージの依存関係ツリー(特にregexp/syntaxbytesに依存していること)を完全に考慮していなかった点にありました。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/syntaxbytesなど)が、テストバイナリのビルドグラフに明示的に含まれることになります。結果として、bytesパッケージが変更された場合でも、ビルドシステムはregexp/syntaxbytesに依存していることを認識し、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)
  1. // The generated main also imports testing and regexp.

    • このコメントは、生成されるテスト用のメインパッケージがtestingregexpの両方をインポートすることを示すものです。
  2. ptesting, err := loadPackage("testing")

    • loadPackage関数は、指定されたパッケージ名(ここでは"testing")に対応する*Package構造体をロードします。この構造体には、パッケージのパス、依存関係、ソースファイルなどの情報が含まれています。
    • エラーが発生した場合(例: testingパッケージが見つからない場合)、関数はエラーを返します。
  3. pregexp, err := loadPackage("regexp")

    • 同様に、"regexp"パッケージの*Package構造体をロードします。
    • ここがこのコミットの核心部分です。regexpパッケージを明示的にロードすることで、そのパッケージがテストバイナリの依存関係ツリーに確実に含まれるようになります。
  4. pmain.imports = append(pmain.imports, ptesting, pregexp)

    • pmainは、go testがテスト実行のために生成する一時的なメインパッケージを表す*Package構造体です。
    • pmain.importsは、このメインパッケージがインポートするパッケージのリストです。
    • この行では、ロードしたptestingtestingパッケージ)とpregexpregexpパッケージ)をpmain.importsリストに追加しています。

この変更により、go testによって生成されるすべてのテストバイナリは、たとえテスト対象のコードが直接regexpパッケージを使用していなくても、常にregexpパッケージをインポートするようになります。これにより、regexpパッケージとその推移的な依存関係(regexp/syntaxbytesなど)がビルドシステムによって常に考慮され、bytesパッケージのような下流の依存関係が変更された場合でも、regexp/syntaxが適切に再コンパイルされることが保証されます。結果として、Robが遭遇したような依存関係の再コンパイル漏れによるバグが解消されます。

関連リンク

参考にした情報源リンク