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

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

このコミットは、Go言語のコマンドラインツール cmd/go における、テスト対象パッケージの選択ロジックの不具合を修正するものです。具体的には、ユーザーが独自のビルドタグを指定した場合や、競合検出(race detection)を有効にした場合に、go test コマンドが正しくパッケージを選択できない問題を解決します。

コミット

commit 4ef91fc8540ef3c77906be387f9915914a27595d
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Fri Nov 9 14:00:41 2012 +0400

    cmd/go: fix selection of packages for testing
    Currently it works incorrectly if user specifies own build tags
    and with race detection (e.g. runtime/race is not selected,
    because it contains only test files with +build race).
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6814107

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

https://github.com/golang/go/commit/4ef91fc8540ef3c77906be387f9915914a27595d

元コミット内容

cmd/go: テストのためのパッケージ選択を修正。 現在、ユーザーが独自のビルドタグを指定した場合や、競合検出(例えば runtime/race が選択されないのは、+build race タグを持つテストファイルしか含まれていないため)を有効にした場合に、正しく動作しない。

変更の背景

Go言語のビルドシステムでは、特定の条件に基づいてソースファイルをコンパイルするかどうかを制御するために「ビルドタグ(build tags)」を使用します。例えば、// +build linux のようなコメントをファイルの先頭に記述することで、そのファイルがLinux環境でのみコンパイルされるように指定できます。同様に、// +build race は競合検出が有効な場合にのみコンパイルされるファイルを示します。

このコミットが修正しようとしている問題は、go test コマンドがテスト対象のパッケージを決定する際に、これらのビルドタグや競合検出の有効化といったユーザーが指定したビルドコンテキストを適切に考慮していなかった点にあります。

具体的には、runtime/race のようなパッケージは、通常、+build race タグを持つテスト関連ファイルのみを含んでいます。ユーザーが go test -race を実行して競合検出を有効にした場合、cmd/go ツールは runtime/race パッケージをテスト対象として含めるべきです。しかし、従来のロジックでは、デフォルトのビルドコンテキストでパッケージをインポートしようとしていたため、+build race タグが考慮されず、結果として runtime/race パッケージがテスト対象から漏れてしまうという不具合がありました。これは、ユーザーが独自のビルドタグを GOFLAGS 環境変数などで指定した場合にも同様の問題を引き起こしていました。

この不具合は、特定の機能(競合検出など)が有効になっているにもかかわらず、それに関連するテストが実行されないという、テストの網羅性や信頼性に関わる重要な問題でした。

前提知識の解説

Goのビルドシステムとコマンド

  • go build: Goのソースコードをコンパイルして実行可能ファイルを生成するコマンドです。
  • go test: Goのテストを実行するコマンドです。パッケージ内のテストファイル(_test.go で終わるファイル)をコンパイルし、テスト関数を実行します。
  • cmd/go: go コマンド自体を実装しているGoの標準ライブラリの一部です。ユーザーが go buildgo test などのコマンドを実行すると、この cmd/go パッケージ内のコードが実行され、実際のビルドやテストのロジックが処理されます。

ビルドタグ (Build Tags)

Go言語では、ソースファイルの先頭に特別なコメント行を記述することで、そのファイルが特定のビルド条件を満たす場合にのみコンパイルされるように指定できます。これを「ビルドタグ」と呼びます。

例:

// +build linux,amd64

package main

// このコードはLinuxかつAMD64アーキテクチャの場合にのみコンパイルされる

複数のタグをカンマで区切るとAND条件(すべて満たす)、スペースで区切るとOR条件(いずれかを満たす)になります。ビルドタグは、OS、アーキテクチャ、Goのバージョン、またはカスタムのタグ(例: race)に基づいて、条件付きコンパイルを可能にします。

競合検出 (Race Detection)

Goは、並行処理におけるデータ競合(複数のGoroutineが同時に同じメモリ位置にアクセスし、少なくとも1つが書き込み操作である場合に発生する未定義の動作)を検出するための組み込みツールを提供しています。go test -race コマンドを実行すると、Goランタイムがデータ競合の発生を監視し、検出された場合には詳細なレポートを出力します。この機能は、runtime/race パッケージによって提供されており、このパッケージ内のコードは通常 +build race ビルドタグが付与されています。

go/build パッケージとビルドコンテキスト

Goの標準ライブラリには、Goのソースコードを解析し、パッケージの依存関係やビルド情報を取得するための go/build パッケージが含まれています。

  • build.ImportDir(dir string, mode build.ImportMode): この関数は、指定されたディレクトリ dir からGoパッケージをインポートしようとします。しかし、この関数はデフォルトのビルドコンテキストを使用します。つまり、環境変数(GOOS, GOARCH など)やコマンドライン引数で指定されたカスタムのビルドタグを考慮しない可能性があります。

  • build.Context 構造体: go/build パッケージには Context という構造体があります。この構造体は、Goのビルドプロセスに関するすべての情報(ターゲットOS、アーキテクチャ、環境変数、そしてアクティブなビルドタグのリストなど)をカプセル化します。

  • buildContext.ImportDir(dir string, mode build.ImportMode): build.Context のメソッドとして提供される ImportDir は、その Context オブジェクトが保持するビルド設定(アクティブなビルドタグを含む)に基づいてパッケージをインポートします。これにより、ユーザーが指定したカスタムビルドタグや、go test -race のようなコマンドによって暗黙的に有効になるタグ(例: race)が、パッケージのインポートプロセスで適切に考慮されるようになります。

技術的詳細

このコミットの核心は、cmd/go ツールがパッケージをスキャンし、テスト対象として選択する際に、適切なビルドコンテキストを使用するように変更した点です。

以前のコードでは、matchPackages 関数内で build.ImportDir(path, 0) を直接呼び出していました。この build.ImportDir 関数は、go/build パッケージのグローバルなデフォルトコンテキストを使用します。このデフォルトコンテキストは、ユーザーが go test -tags customtag のようにコマンドラインで指定したビルドタグや、go test -race のように競合検出を有効にした際に暗黙的に追加される race タグを認識しませんでした。

その結果、例えば runtime/race パッケージのように、そのソースファイルが // +build race のようなビルドタグに依存している場合、build.ImportDir はそのファイルを通常のビルドプロセスの一部として認識せず、「Goソースファイルがない」と判断してしまうことがありました。これは、go testruntime/race パッケージをテスト対象として適切に含めることができない原因となっていました。

この修正では、build.ImportDir(path, 0) の呼び出しを buildContext.ImportDir(path, 0) に変更しています。ここで buildContext は、cmd/go ツールが現在のコマンド実行のために構築した、ユーザーの指定(ビルドタグ、競合検出の有効化など)をすべて反映した build.Context オブジェクトです。

この変更により、matchPackages 関数がパッケージをインポートする際に、現在のビルド環境とユーザーの意図が正確に反映されるようになりました。例えば、go test -race が実行された場合、buildContext には race タグが含まれるため、buildContext.ImportDir+build race タグを持つファイルを正しく認識し、runtime/race パッケージがテスト対象として適切に選択されるようになります。これにより、テストの網羅性が向上し、ユーザーが期待する動作が実現されます。

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

変更は src/cmd/go/main.go ファイルの2箇所です。

--- a/src/cmd/go/main.go
+++ b/src/cmd/go/main.go
@@ -430,7 +430,7 @@ func matchPackages(pattern string) []string {
 			return filepath.SkipDir
 		}
 
-		_, err = build.ImportDir(path, 0)
+		_, err = buildContext.ImportDir(path, 0)
 		if err != nil {
 			return nil
 		}
@@ -471,7 +471,7 @@ func matchPackages(pattern string) []string {
 			}
 			have[name] = true
 
-			_, err = build.ImportDir(path, 0)
+			_, err = buildContext.ImportDir(path, 0)
 			if err != nil && strings.Contains(err.Error(), "no Go source files") {
 				return nil
 			}

コアとなるコードの解説

上記の差分が示すように、src/cmd/go/main.go 内の matchPackages 関数において、build.ImportDir の呼び出しが buildContext.ImportDir に置き換えられています。

matchPackages 関数は、go test コマンドが実行された際に、指定されたパターンに一致するパッケージを検索し、テスト対象として選択する役割を担っています。この関数はファイルシステムを走査し、各ディレクトリがGoパッケージであるかどうかを判断するために ImportDir を使用します。

変更前:

_, err = build.ImportDir(path, 0)

この行では、go/build パッケージのトップレベル関数 ImportDir が呼び出されていました。この関数は、現在の環境のデフォルト設定(GOOS, GOARCH など)のみを考慮し、ユーザーがコマンドラインで指定したカスタムビルドタグや、go test -race のような特定のコマンドによって有効になるタグ(例: race)を考慮しませんでした。

変更後:

_, err = buildContext.ImportDir(path, 0)

この行では、buildContext という変数(これは build.Context 型のオブジェクトであり、現在の go コマンドの実行コンテキスト、つまりユーザーが指定したビルドタグや競合検出の有効化などの情報がすべて含まれている)のメソッドとして ImportDir が呼び出されています。

この変更により、パッケージのインポート処理が、現在の go コマンドの実行環境とユーザーの意図を正確に反映するようになりました。例えば、go test -race が実行された場合、buildContext オブジェクトには race ビルドタグがアクティブであるという情報が含まれており、buildContext.ImportDir+build race タグを持つソースファイルを正しく認識し、それらをパッケージの一部として含めることができるようになります。これにより、runtime/race のような特定のビルドタグに依存するパッケージも、期待通りにテスト対象として選択されるようになりました。

関連リンク

参考にした情報源リンク