[インデックス 17522] ファイルの概要
このコミットは、Go言語の標準ライブラリ path/filepath パッケージ内のテスト TestBug3486 における競合状態(race condition)を修正するものです。具体的には、filepath.Walk 関数を用いたファイルツリーの走査テストが、他のテストによってファイルが作成・削除されることで不安定になる問題を解決しています。
コミット
commit 5dc8c4dbfb6a04d9eb7a11c9c3fe698d33d0c0ee
Author: Russ Cox <rsc@golang.org>
Date: Mon Sep 9 16:42:18 2013 -0400
path/filepath: fix race with other tests
Bug3486 tried to walk the entire file tree, but other tests might
be creating and removing files in that tree. In particular, package os
creates and removes files in the os directory, and issue 5863
reports failures due to seeing those files appear and then disappear.
Change the test to walk just the test tree, which should not be
changing.
Fixes #5863.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/13467045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5dc8c4dbfb6a04d9eb7a11c9c3fe698d33d0c0ee
元コミット内容
path/filepath: fix race with other tests
Bug3486 はファイルツリー全体を走査しようとしましたが、他のテストがそのツリー内でファイルを生成したり削除したりする可能性がありました。特に、os パッケージは os ディレクトリ内でファイルを生成・削除し、Issue 5863 はそれらのファイルが出現したり消えたりすることによるテストの失敗を報告しています。
テストを、変更されるべきではないテストツリーのみを走査するように変更します。
Issue #5863 を修正します。
変更の背景
このコミットの背景には、Go言語のテストスイートにおける不安定性がありました。path/filepath パッケージの TestBug3486 というテストは、filepath.Walk 関数を使用してファイルシステムを走査し、特定のディレクトリ(lib や src)が適切に処理されることを検証していました。
しかし、このテストが runtime.GOROOT() で示されるGoのインストールディレクトリ全体を走査しようとした際、同じテストスイート内で並行して実行されている他のテスト(特に os パッケージのテストなど)が、その走査対象のディレクトリ内に一時的なファイルを生成したり削除したりすることがありました。
このようなファイルの動的な変更は、filepath.Walk が走査中に予期せぬファイルやディレクトリの出現・消失を検知し、テストが失敗する原因となっていました。これは、テストが外部環境(この場合は他のテストの副作用)に依存してしまい、再現性の低い「flaky test」(不安定なテスト)となる典型的な例です。
Issue 5863 はこの問題が具体的に報告されたもので、テストが os ディレクトリ内のファイルの出現・消失によって失敗するという内容でした。このコミットは、この不安定性を解消し、テストの信頼性を向上させることを目的としています。
前提知識の解説
1. path/filepath パッケージ
path/filepath パッケージは、Go言語でファイルパスを操作するためのユーティリティを提供します。OSに依存しないパス操作(結合、分割、クリーンアップなど)や、ファイルシステムを走査するための Walk 関数などが含まれます。
2. filepath.Walk 関数
filepath.Walk は、指定されたルートディレクトリからファイルシステムツリーを再帰的に走査する関数です。走査中に見つかった各ファイルやディレクトリに対して、ユーザーが定義したコールバック関数(WalkFunc)を実行します。この関数は、ファイルシステムの状態を検査したり、特定のファイルを検索したりするのに非常に便利です。
filepath.Walk(root string, walkFn WalkFunc) error
root: 走査を開始するルートディレクトリのパス。walkFn: 各ファイルまたはディレクトリに対して呼び出される関数。この関数は(path string, info os.FileInfo, err error) errorというシグネチャを持ちます。path: 現在走査中のファイルまたはディレクトリのパス。info: そのファイルまたはディレクトリのos.FileInfoインターフェース(ファイル名、サイズ、パーミッション、更新時刻などの情報を含む)。err: 走査中に発生したエラー。walkFnがfilepath.SkipDirを返すと、そのディレクトリの内容は走査されずにスキップされます。
3. runtime.GOROOT()
runtime.GOROOT() は、Go言語の標準ライブラリ runtime パッケージが提供する関数で、Goのインストールルートディレクトリの絶対パスを返します。このディレクトリには、Goのソースコード、標準ライブラリ、ツールなどが含まれています。
4. 競合状態 (Race Condition)
競合状態とは、複数の並行プロセスやスレッドが共有リソースにアクセスする際に、そのアクセス順序によって結果が非決定的に変わってしまう状態を指します。テストの文脈では、あるテストがファイルシステムを走査している最中に、別のテストが同じファイルシステム上のファイルを変更(作成、削除など)することで、走査結果が予期せぬものとなり、テストが失敗する現象がこれに該当します。これはテストの信頼性を著しく低下させます。
5. filepath.EvalSymlinks
filepath.EvalSymlinks は、指定されたパス内のシンボリックリンクを評価し、最終的な物理パスを返します。これにより、シンボリックリンクをたどった後の実際のファイルシステム上の位置を取得できます。
技術的詳細
このコミットの技術的な核心は、テストの対象範囲を限定することで、テストの外部依存性を排除し、競合状態を解消することにあります。
元の TestBug3486 は、runtime.GOROOT() を起点としてファイルシステム全体を走査していました。これは、Goのインストールディレクトリ全体がテストの対象となり、その中には他のテストが一時的にファイルを操作する可能性のあるディレクトリ(例: os パッケージのテストが使用するディレクトリ)も含まれていました。
修正後のテストでは、filepath.Walk の走査開始パスを runtime.GOROOT() + "/test" に変更しています。GOROOT/test ディレクトリは、Goのテストスイート専用のファイルやディレクトリを含む場所であり、通常、他のテストがこのディレクトリ内のファイルを動的に変更することはありません。これにより、TestBug3486 が走査するファイルツリーが安定し、他のテストによる副作用の影響を受けなくなります。
また、テストの検証ロジックも変更されています。元々は lib と src ディレクトリが走査中に見られることを期待していましたが、修正後は bugs と ken というディレクトリが走査されることを期待しています。これらは GOROOT/test ディレクトリ内に存在するテスト用のサブディレクトリであり、テストの意図に合致しています。
さらに、filepath.Walk のコールバック関数内で filepath.SkipDir を使用して、特定のディレクトリ(bugs ディレクトリ)の走査をスキップするロジックが追加されています。これは、テストの効率化や、特定のサブツリーがテストの対象外であることを明示するために用いられます。また、ken ディレクトリが bugs ディレクトリの後に走査されることを確認する順序チェックも追加されており、filepath.Walk の走査順序に関する暗黙の仮定が明示的にテストされています。
この変更により、TestBug3486 はより隔離された環境で実行されるようになり、テストの信頼性と再現性が大幅に向上しました。
コアとなるコードの変更箇所
src/pkg/path/filepath/path_test.go ファイルが変更されています。
--- a/src/pkg/path/filepath/path_test.go
+++ b/src/pkg/path/filepath/path_test.go
@@ -927,27 +927,32 @@ func TestDriveLetterInEvalSymlinks(t *testing.T) {
}
func TestBug3486(t *testing.T) { // http://code.google.com/p/go/issues/detail?id=3486
- root, err := filepath.EvalSymlinks(runtime.GOROOT())
+ root, err := filepath.EvalSymlinks(runtime.GOROOT() + "/test")
if err != nil {
t.Fatal(err)
}
- lib := filepath.Join(root, "lib")
- src := filepath.Join(root, "src")
- seenSrc := false
+ bugs := filepath.Join(root, "bugs")
+ ken := filepath.Join(root, "ken")
+ seenBugs := false
+ seenKen := false
filepath.Walk(root, func(pth string, info os.FileInfo, err error) error {
if err != nil {
t.Fatal(err)
}
switch pth {
- case lib:
+ case bugs:
+ seenBugs = true
return filepath.SkipDir
- case src:
- seenSrc = true
+ case ken:
+ if !seenBugs {
+ t.Fatal("filepath.Walk out of order - ken before bugs")
+ }
+ seenKen = true
}
return nil
})
- if !seenSrc {
- t.Fatalf("%q not seen", src)
+ if !seenKen {
+ t.Fatalf("%q not seen", ken)
}
}
コアとなるコードの解説
-
走査ルートの変更:
- 変更前:
root, err := filepath.EvalSymlinks(runtime.GOROOT())- Goのインストールルートディレクトリ全体を走査対象としていました。
- 変更後:
root, err := filepath.EvalSymlinks(runtime.GOROOT() + "/test")- 走査対象を
GOROOT内のtestディレクトリに限定しました。このディレクトリはGoのテストスイート専用のファイルを含んでおり、他のテストによる干渉が少ないと期待されます。
- 走査対象を
- 変更前:
-
期待するディレクトリパスの変更:
- 変更前:
lib := filepath.Join(root, "lib")とsrc := filepath.Join(root, "src")GOROOT内のlibとsrcディレクトリの存在を確認していました。
- 変更後:
bugs := filepath.Join(root, "bugs")とken := filepath.Join(root, "ken")GOROOT/test内のbugsとkenディレクトリの存在を確認するように変更されました。これらはテスト専用のディレクトリです。
- 変更前:
-
filepath.Walkコールバックロジックの変更:- 変更前は
libディレクトリでfilepath.SkipDirを返し、srcディレクトリが見られたらseenSrcをtrueにしていました。 - 変更後:
case bugs::seenBugs = trueとし、filepath.SkipDirを返してbugsディレクトリ以下の走査をスキップします。case ken::seenKen = trueとし、さらに!seenBugsであればt.Fatal("filepath.Walk out of order - ken before bugs")を呼び出してテストを失敗させます。これは、filepath.Walkがディレクトリを走査する順序(通常は辞書順)を暗黙的にテストしています。bugsがkenより前に走査されることを期待しています。
- 変更前は
-
最終的な検証の変更:
- 変更前:
if !seenSrc { t.Fatalf("%q not seen", src) }srcディレクトリが見られたことを検証していました。
- 変更後:
if !seenKen { t.Fatalf("%q not seen", ken) }kenディレクトリが見られたことを検証するように変更されました。
- 変更前:
これらの変更により、テストはより限定された、他のテストの副作用を受けにくいファイルツリーを対象とするようになり、テストの信頼性が向上しました。
関連リンク
- Go Issue 5863: https://code.google.com/p/go/issues/detail?id=5863 (元のGoプロジェクトのIssueトラッカーへのリンク。現在はGitHub Issuesに移行している可能性がありますが、コミットメッセージに記載されているため含めます。)
- Go CL 13467045: https://golang.org/cl/13467045 (Goのコードレビューシステムへのリンク)
参考にした情報源リンク
- Go言語
path/filepathパッケージドキュメント: https://pkg.go.dev/path/filepath - Go言語
runtimeパッケージドキュメント: https://pkg.go.dev/runtime - Go言語のテストに関するドキュメント (Goの公式ドキュメントやブログ記事など、一般的なテストのベストプラクティスに関する情報源)
- 競合状態 (Race Condition) に関する一般的な情報源 (例: Wikipedia, プログラミング関連の技術ブログなど)
- Go言語のソースコードリポジトリ (GitHub): https://github.com/golang/go
- Go言語のIssueトラッカー (GitHub Issues): https://github.com/golang/go/issues