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

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

このコミットは、Goのcmd/goツールにおけるgo testコマンドの依存関係解決に関する問題を修正します。具体的には、testmain(テスト実行のために生成される特別なmainパッケージ)がregexpパッケージをその依存関係として正しく認識しないために発生していたビルド時の問題を解決します。特にgo test -aフラグを使用した場合に、regexpパッケージが適切にリビルドされないという問題に対処しています。

コミット

commit 52961b902fa89e89eff318e907e7f331bcf09736
Author: Robert Hencke <robert.hencke@gmail.com>
Date:   Fri May 9 12:19:00 2014 -0400

    cmd/go: mark regexp as dependency of testmain
    
    Fixes #6844.
    
    LGTM=rsc
    R=golang-codereviews, rsc
    CC=golang-codereviews
    https://golang.org/cl/97840043

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

https://github.com/golang/go/commit/52961b902fa89e89eff318e907e7f331bcf09736

元コミット内容

cmd/go: mark regexp as dependency of testmain

Fixes #6844.

LGTM=rsc
R=golang-codereviews, rsc
CC=golang-codereviews
https://golang.org/cl/97840043

変更の背景

このコミットは、GoのIssue 6844を修正するために行われました。元のIssueの詳細は現在参照できませんが、コミットメッセージとコードの変更内容から、go test -aコマンドがregexpパッケージをtestmainの依存関係として正しく扱わないという問題があったと推測されます。

go testコマンドは、テストを実行するために、テスト対象のパッケージとテストコードをリンクした特別な実行可能ファイル(testmain)を生成します。このtestmainは、テストコードが依存するすべてのパッケージを適切に含める必要があります。しかし、何らかの理由でregexpパッケージがtestmainの依存関係ツリーから漏れてしまうことがあり、特にgo test -a(すべてのパッケージを強制的にリビルドするフラグ)を使用した場合に、regexpパッケージが最新の状態にリビルドされず、テストの実行に問題が生じる可能性がありました。

この問題は、testmainの依存関係解決ロジックが、テストファイルが間接的に依存するパッケージ(この場合はregexp)を完全に追跡できていなかったことに起因すると考えられます。結果として、go test -aが期待通りに動作せず、開発者が古いバージョンのregexpパッケージにリンクされたテストを実行してしまう可能性がありました。

前提知識の解説

go testコマンド

go testはGo言語の標準的なテスト実行ツールです。指定されたパッケージ内のテスト関数(TestBenchmarkExampleで始まる関数)を検出し、実行します。go testは、テスト対象のパッケージとテストコードをコンパイルし、それらをリンクして単一の実行可能ファイルを作成します。

testmainパッケージ

go testがテストを実行する際、内部的にtestmainと呼ばれる特別なmainパッケージを生成します。このtestmainは、テスト対象のパッケージ、テストファイル自体、およびそれらの依存関係をすべて含み、テストランナーとして機能します。testmainは、テストのセットアップ、実行、結果の報告といった一連の処理を管理します。

Goのビルドプロセスと依存関係管理

Goのビルドシステムは、ソースコードから実行可能ファイルを生成する際に、パッケージ間の依存関係を自動的に解決します。あるパッケージが別のパッケージをimportしている場合、ビルドシステムはその依存関係を認識し、必要なすべてのパッケージをコンパイルしてリンクします。go testもこのビルドシステムを利用しており、testmainのビルド時にすべての必要な依存関係が解決される必要があります。

go test -aフラグ

go test -aは、go testコマンドのオプションの一つで、「すべてのパッケージを強制的にリビルドする」ことを意味します。通常、Goのビルドシステムは、ソースファイルが変更されていない限り、すでにコンパイルされたパッケージを再利用することでビルド時間を短縮します。しかし、-aフラグを使用すると、キャッシュされたビルド結果を無視し、すべての依存関係を含むすべてのパッケージを最初から再コンパイルします。これは、ビルドキャッシュに起因する可能性のある問題をデバッグする際や、クリーンなビルドを保証したい場合に役立ちます。

regexpパッケージ

regexpはGoの標準ライブラリに含まれるパッケージで、正規表現の操作を提供します。このコミットでは、regexpパッケージ自体が問題なのではなく、testmainがその依存関係を正しく追跡できていなかったことが問題でした。

技術的詳細

go testコマンドは、テスト対象のパッケージのテストファイル(_test.goで終わるファイル)を解析し、それらのファイルがインポートしているパッケージを特定します。これらのインポートされたパッケージは、testmain実行可能ファイルの依存関係となります。

このコミット以前のgo testの動作では、testmainの依存関係を解決する際に、テストファイルが直接インポートするパッケージは考慮されていましたが、それらのパッケージがさらにインポートする間接的な依存関係(推移的依存関係)の一部が適切に追跡されていなかった可能性があります。特に、regexpパッケージのように、多くのGoプログラムで広く利用される基本的なパッケージが、特定の条件下でtestmainの依存関係リストから漏れてしまうことがありました。

go test -aフラグがこの問題を顕在化させたのは、このフラグがすべてのパッケージの強制的なリビルドをトリガーするためです。もしregexptestmainの依存関係として認識されていなければ、go test -aが実行されてもregexpはリビルドされず、古いバージョンのregexptestmainにリンクされてしまう可能性がありました。これは、テストの実行結果に予期せぬ影響を与える可能性があります。

このコミットの修正は、testmainの依存関係解決ロジックを強化し、テストファイルがインポートするすべてのパッケージ(直接的および間接的)がtestmainのビルド時に確実に考慮されるようにすることで、この問題を解決しています。これにより、go test -aが期待通りに動作し、すべての依存関係が常に最新の状態でビルドされることが保証されます。

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

このコミットでは、主に以下の3つのファイルが変更されています。

  1. src/cmd/go/test.bash: go testコマンドのテストスクリプト。Issue 6844を再現し、修正を検証するための新しいテストケースが追加されました。
  2. src/cmd/go/test.go: go testコマンドのGoソースコード。testmainの依存関係解決ロジックが修正されました。
  3. src/cmd/go/testdata/dep_test.go: 新しく追加されたテストデータファイル。regexpパッケージへの依存関係をシミュレートし、Issue 6844のテストケースとして機能します。

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

--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -708,12 +708,28 @@ if ./testgo test notest >/dev/null 2>&1; then
 fi
 unset GOPATH
 
+<<<<<<< local
+TEST 'Issue 6844: cmd/go: go test -a foo does not rebuild regexp'
+if ! ./testgo test -x -a -c testdata/dep_test.go 2>deplist; then
+\techo "go test -x -a -c testdata/dep_test.go failed"
+\tok=false
+elif ! grep -q regexp deplist; then
+\techo "go test -x -a -c testdata/dep_test.go did not rebuild regexp"
+=======\n TEST list template can use context function
 if ! ./testgo list -f "GOARCH: {{context.GOARCH}}"; then 
  \techo unable to use context in list template
+>>>>>>> other
 \tok=false
 fi
+<<<<<<< local
+rm -f deplist
+rm -f deps.test
+=======\n+>>>>>>> other
+
+<<<<<<< local
+=======\n TEST build -i installs dependencies
 d=$(TMPDIR=/var/tmp mktemp -d -t testgoXXX)\n export GOPATH=$d
@@ -748,6 +764,7 @@ fi
 rm -rf $d
 unset GOPATH
 
+>>>>>>> other
 # clean up
 if $started; then stop; fi
 rm -rf testdata/bin testdata/bin1

このdiffは、マージコンフリクトの残骸を含んでいますが、重要なのはTEST 'Issue 6844: cmd/go: go test -a foo does not rebuild regexp'で始まる新しいテストブロックです。 このテストは、./testgo test -x -a -c testdata/dep_test.goを実行し、その出力(-xフラグによる実行コマンドのトレース)にregexpが含まれているかどうかをgrepで確認しています。regexpが含まれていれば、go test -aregexpをリビルド対象として認識したことを意味し、修正が機能していることを示します。

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

--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -418,6 +418,7 @@ func runTest(cmd *Command, args []string) {
 			var coverFiles []string
 			coverFiles = append(coverFiles, p.GoFiles...)
 			coverFiles = append(coverFiles, p.CgoFiles...)
+			coverFiles = append(coverFiles, p.TestGoFiles...)
 			p.coverVars = declareCoverVars(p.ImportPath, coverFiles...)
 		}
 	}
@@ -676,7 +677,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\n 	stk.push("testmain")
 	for dep := range testMainDeps {
 		if ptest.ImportPath != dep {
-			p1 := loadImport("testing", "", &stk, nil)
+			p1 := loadImport(dep, "", &stk, nil)
 			if p1.Error != nil {
 				return nil, nil, nil, p1.Error
 			}

このファイルには2つの重要な変更があります。

  1. runTest関数内の変更: coverFiles = append(coverFiles, p.TestGoFiles...) これは、カバレッジ分析の対象となるファイルリストに、テストファイル自体(p.TestGoFiles)を追加しています。これは直接的な依存関係解決の修正とは異なりますが、テスト関連のファイル処理の網羅性を高めるための改善です。

  2. builder.test関数内の変更: - p1 := loadImport("testing", "", &stk, nil) + p1 := loadImport(dep, "", &stk, nil) これがこのコミットの核心的な修正です。以前は、testmainの依存関係として常に"testing"パッケージをロードしようとしていました。しかし、testMainDepsセットにはtestmainが依存する可能性のあるすべてのパッケージが含まれるべきです。この変更により、testMainDepsから取得した実際の依存パッケージ名depを使用してloadImportが呼び出されるようになります。これにより、testmaintestingだけでなく、regexpのような他の推移的依存関係も正しくロードし、ビルドプロセスに含めることが保証されます。

src/cmd/go/testdata/dep_test.goの変更点

--- /dev/null
+++ b/src/cmd/go/testdata/dep_test.go
@@ -0,0 +1,7 @@
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package deps
+
+import _ "testing"

これは新しく追加されたテストファイルです。import _ "testing"という行は、testingパッケージをインポートしていますが、そのパッケージ内の関数を直接使用しないことを示しています(ブランクインポート)。testingパッケージは、Goのテストフレームワークの基盤であり、通常はregexpパッケージに依存しています。このテストファイルが存在することで、testmaintestingパッケージを介してregexpパッケージに間接的に依存するシナリオが作成され、以前のバグが再現されるようになります。このファイルは、test.bashスクリプトで実行されるテストケースの一部として使用されます。

コアとなるコードの解説

このコミットの主要な修正は、src/cmd/go/test.go内のbuilder.test関数にあります。

builder.test関数は、go testコマンドがテスト実行可能ファイル(testmain)をビルドする際のロジックを管理しています。この関数内で、testMainDepsというセットが、testmainが依存するすべてのパッケージを追跡するために使用されます。

修正前のコードでは、testMainDepsをイテレートする際に、loadImport("testing", "", &stk, nil)という固定の呼び出しがありました。これは、testmainが常に"testing"パッケージに依存しているという前提に基づいていました。しかし、testMainDepsには"testing"以外のパッケージも含まれる可能性があり、特にテストファイルが間接的に依存するパッケージ(例: regexp)が含まれるべきでした。

新しいコードp1 := loadImport(dep, "", &stk, nil)では、testMainDepsから取得した実際の依存パッケージ名depを使用してloadImportが呼び出されます。これにより、testmainのビルド時に、testMainDepsに含まれるすべてのパッケージが適切にロードされ、依存関係として考慮されるようになります。

この変更により、regexpパッケージがtestmainの推移的依存関係として正しく認識され、go test -aが実行された際に、regexpパッケージも必要に応じてリビルドされることが保証されます。結果として、go testのビルドプロセスがより堅牢になり、依存関係の欠落による潜在的な問題が解消されます。

src/cmd/go/test.bashに追加されたテストケースは、この修正が正しく機能することを確認するためのものです。testdata/dep_test.goという新しいテストファイルは、testingパッケージをインポートすることで、regexpへの間接的な依存関係を作成します。test.bashスクリプトは、go test -x -a -cを実行し、ビルドトレースにregexpが含まれていることを確認することで、regexpが正しくリビルドされたことを検証します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(go testコマンド、パッケージ管理、ビルドプロセスに関する一般的な情報)
  • Go言語のソースコード(src/cmd/go/test.goの関連部分)
  • (注: コミットメッセージに記載されているGo CLリンク https://golang.org/cl/97840043 は現在利用できませんでした。)