[インデックス 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