[インデックス 11964] ファイルの概要
このコミットは、Go言語の標準ライブラリos
パッケージ内のテストコードにおける一時ディレクトリの管理方法を変更するものです。具体的には、テスト実行時に作成される一時ファイルやディレクトリを、慣習的に使用されていた_test
プレフィックスを持つディレクトリではなく、ioutil.TempDir
関数やos.TempDir
関数によって生成されるシステムの一時ディレクトリを利用するように修正しています。これにより、テスト環境のクリーンアップをより確実かつ安全に行うことを目的としています。
コミット
commit edf1c038e327f6432286aa3036d0434ea8f53907
Author: Rob Pike <r@golang.org>
Date: Thu Feb 16 17:05:43 2012 +1100
os: remove use of _test
Part of issue 2573.
R=dsymonds, golang-dev
CC=golang-dev
https://golang.org/cl/5674064
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/edf1c038e327f6432286aa3036d0434ea8f53907
元コミット内容
このコミットは、src/pkg/os/os_test.go
とsrc/pkg/os/path_test.go
の2つのファイルに変更を加えています。主な変更点は、テスト内で一時ディレクトリを作成する際に、ハードコードされた_test
プレフィックスを持つパスを使用する代わりに、ioutil.TempDir
関数やos.TempDir
関数(コミット時点ではTempDir()
と記述されているが、これはos
パッケージ内のヘルパー関数か、ioutil.TempDir
のラッパーである可能性が高い)を利用するように変更している点です。これにより、テスト実行後の一時ディレクトリのクリーンアップがより堅牢になります。
具体的には、以下のテスト関数が影響を受けています。
TestStatDirWithTrailingSlash
(os_test.go
):MkdirAll
で_test/_TestStatDirWithSlash_
というパスを直接指定していた箇所を、ioutil.TempDir("", "/_TestStatDirWithSlash_")
に置き換え。defer RemoveAll(path)
で一時ディレクトリの削除を遅延実行するように変更。
TestMkdirAll
(path_test.go
):_test/_TestMkdirAll_/dir/./dir2
というパスを直接指定していた箇所を、tmpDir := TempDir(); path := tmpDir + "/_TestMkdirAll_/dir/./dir2"
のように、TempDir()
で取得した一時ディレクトリのパスを基点にするように変更。defer RemoveAll("_test/_TestMkdirAll_")
をdefer RemoveAll(tmpDir + "/_TestMkdirAll_")
に変更。- Windows環境でのパス指定も同様に
tmpDir
を基点にするように変更。
TestRemoveAll
(path_test.go
):_test/_TestRemoveAll_
というパスを直接指定していた箇所を、tmpDir := TempDir(); path := tmpDir + "/_TestRemoveAll_"
のように変更。
TestMkdirAllWithSymlink
(path_test.go
):_test/dir
や_test/link
といったパスを直接指定していた箇所を、tmpDir := TempDir()
で取得した一時ディレクトリのパスを基点にするように変更。
これらの変更により、テストが実行される環境に依存せず、一時ファイルが予測可能な場所に作成され、確実にクリーンアップされるようになります。
変更の背景
このコミットは、Go言語のIssue 2573「os
パッケージのテストが_test
ディレクトリを汚染する」の一部として行われました。
従来のGoのテストでは、一時ファイルやディレクトリを作成する際に、テストコードの近くに_test
というプレフィックスを持つディレクトリを作成し、その中に一時データを配置する慣習がありました。しかし、この方法はいくつかの問題を引き起こしていました。
- クリーンアップの不確実性: テストが異常終了した場合や、テストランナーが適切にクリーンアップを行わない場合、
_test
ディレクトリとその内容が残存し、ファイルシステムを汚染する可能性がありました。これは、特にCI/CD環境や開発者のローカル環境で、テストの再実行時に予期せぬ挙動を引き起こしたり、ディスクスペースを消費したりする原因となります。 - パスの衝突: 複数のテストが同じ
_test
ディレクトリ内のパスを使用しようとした場合、衝突が発生し、テストの信頼性が低下する可能性がありました。 - 環境依存性:
_test
ディレクトリの作成場所がテストコードの相対パスに依存するため、テストが実行されるカレントディレクトリによっては予期せぬ場所に作成される可能性がありました。
これらの問題を解決するため、Goの標準ライブラリでは、一時ファイルやディレクトリの作成に特化したio/ioutil
パッケージのTempDir
関数やTempFile
関数(Go 1.16以降はos.MkdirTemp
やos.CreateTemp
に移行)の使用が推奨されるようになりました。これらの関数は、OSが提供する一時ディレクトリの場所にユニークな名前でディレクトリやファイルを作成し、テスト終了後のクリーンアップを容易にするためのメカニズムを提供します。
このコミットは、os
パッケージのテストコードを、この新しい推奨される一時ディレクトリ管理のパラダイムに移行させるための具体的なステップでした。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とファイルシステム操作に関する知識が必要です。
-
Go言語のテスト:
- Go言語では、テストファイルは通常、テスト対象のソースファイルと同じディレクトリに
_test.go
というサフィックスを付けて配置されます。 - テスト関数は
Test
で始まり、*testing.T
型の引数を取ります。 t.Fatalf()
: テストを失敗させ、メッセージを出力してテスト関数を即座に終了します。defer
ステートメント:defer
に続く関数呼び出しは、その関数がリターンする直前に実行されます。これは、リソースのクリーンアップ(例: 一時ファイルの削除)によく使用されます。
- Go言語では、テストファイルは通常、テスト対象のソースファイルと同じディレクトリに
-
os
パッケージ:os.MkdirAll(path string, perm os.FileMode) error
: 指定されたパスにディレクトリを作成します。途中のディレクトリが存在しない場合は、それらも作成します。perm
は作成されるディレクトリのパーミッションを指定します。os.RemoveAll(path string) error
: 指定されたパスのファイルまたはディレクトリを、その内容すべてを含めて削除します。os.Stat(name string) (fi os.FileInfo, err error)
: 指定されたファイルまたはディレクトリの情報を返します。ファイルが存在しない場合やアクセスできない場合はエラーを返します。os.Symlink(oldname, newname string) error
:oldname
を指すnewname
というシンボリックリンクを作成します。os.TempDir() string
: システムの一時ディレクトリのデフォルトパスを返します。
-
io/ioutil
パッケージ (Go 1.16以降はos
パッケージに移行):ioutil.TempDir(dir, pattern string) (name string, err error)
:dir
で指定されたディレクトリ(空文字列の場合はシステムの一時ディレクトリ)内に、pattern
に基づいてユニークな名前の一時ディレクトリを作成します。作成されたディレクトリの絶対パスを返します。この関数は、テストやアプリケーションで一時的な作業スペースが必要な場合に非常に便利です。
-
ファイルパスの操作:
- Go言語では、パスの区切り文字はOSによって異なります(Unix系では
/
、Windowsでは\
)。path/filepath
パッケージを使用することで、OSに依存しないパス操作が可能です。このコミットでは、Windowsパスのテストケースでバッククォート( runtime.GOOS
: 現在のオペレーティングシステムを示す文字列定数(例:"linux"
,"windows"
,"darwin"
)。これを使用して、OS固有の処理を条件分岐させることができます。
- Go言語では、パスの区切り文字はOSによって異なります(Unix系では
-
Issue Tracking System (GoのIssue 2573):
- Goプロジェクトでは、バグ報告や機能改善の提案はIssueとして管理されます。Issue番号は、関連するコミットメッセージに記載されることがよくあります。Issue 2573は、
os
パッケージのテストが_test
ディレクトリを適切にクリーンアップしない問題に関するものでした。
- Goプロジェクトでは、バグ報告や機能改善の提案はIssueとして管理されます。Issue番号は、関連するコミットメッセージに記載されることがよくあります。Issue 2573は、
これらの知識を前提として、コミットの変更内容と意図を深く理解することができます。
技術的詳細
このコミットの技術的詳細な変更点は、テストにおける一時ディレクトリの生成と管理のパラダイムシフトにあります。
1. _test
ディレクトリからの脱却
以前のテストコードでは、一時ディレクトリを_test/
というプレフィックスを持つパス(例: _test/_TestStatDirWithSlash_
)で直接指定していました。これは、テストコードが実行されるディレクトリの直下に_test
というディレクトリを作成し、その中にテスト用の一時データを格納するという慣習的な方法でした。
しかし、この方法には以下の問題がありました。
- 手動クリーンアップの必要性: テストが正常終了した場合でも、
defer RemoveAll("_test/...")
のように明示的にRemoveAll
を呼び出す必要がありました。テストがクラッシュした場合、このdefer
が実行されず、一時ディレクトリが残存する可能性がありました。 - パスの衝突と予測不能性: 複数のテストが同じ
_test
ディレクトリ内のパスを使用しようとすると、競合状態や予期せぬ副作用が発生する可能性がありました。また、テストが実行されるカレントディレクトリによっては、_test
ディレクトリが開発者の意図しない場所に作成されることもありました。
2. ioutil.TempDir
の導入
このコミットでは、os_test.go
のTestStatDirWithTrailingSlash
関数において、ioutil.TempDir
関数が導入されました。
// 変更前
// path := "_test/_TestStatDirWithSlash_"
// err := MkdirAll(path, 0777)
// 変更後
path, err := ioutil.TempDir("", "/_TestStatDirWithSlash_")
if err != nil {
t.Fatalf("TempDir: %s", err)
}
defer RemoveAll(path)
ioutil.TempDir("", "/_TestStatDirWithSlash_")
の呼び出しは、以下の利点をもたらします。
- システムの一時ディレクトリの利用: 第一引数に空文字列
""
を渡すことで、OSが提供する標準の一時ディレクトリ(Linux/macOSでは/tmp
、Windowsでは%TEMP%
など)内に一時ディレクトリが作成されます。これにより、テストがファイルシステムを汚染するリスクが低減されます。 - ユニークなディレクトリ名の生成: 第二引数の
"/_TestStatDirWithSlash_"
は、作成されるディレクトリ名のプレフィックスとして使用されます。ioutil.TempDir
は、このプレフィックスにランダムな文字列を付加することで、ユニークなディレクトリ名を生成します。これにより、テスト間のパスの衝突が回避されます。 - 自動的なクリーンアップの容易さ:
defer RemoveAll(path)
と組み合わせることで、テスト関数が終了する際に、作成された一時ディレクトリが確実に削除されるようになります。ioutil.TempDir
が返すパスは、そのテスト実行に固有のものであるため、他のテストやシステムに影響を与えることなく安全に削除できます。
3. TempDir()
ヘルパー関数の利用
path_test.go
のテスト関数では、TempDir()
というヘルパー関数が導入され、これを使用して一時ディレクトリのベースパスを取得しています。
// 変更前
// path := "_test/_TestMkdirAll_/dir/./dir2"
// 変更後
tmpDir := TempDir() // おそらく os.TempDir() またはそのラッパー
path := tmpDir + "/_/_TestMkdirAll_/dir/./dir2"
このTempDir()
関数は、おそらくos.TempDir()
を呼び出すか、またはioutil.TempDir
と同様にユニークな一時ディレクトリを生成する内部ヘルパー関数であると考えられます。これにより、_test
というハードコードされたプレフィックスを削除し、OSが管理する一時ディレクトリのパスを基点としてテスト用の一時パスを構築できるようになります。
4. Windowsパスの扱い
TestMkdirAll
関数内には、runtime.GOOS == "windows"
という条件分岐があり、Windows固有のパス形式(バックスラッシュ\
)をテストしています。この部分も、tmpDir
を基点とするように修正されています。
// 変更前
// path := `_test\_TestMkdirAll_\dir\.\dir2\`
// 変更後
path := tmpDir + `\_TestMkdirAll_\dir\.\dir2\`
Goの生文字列リテラル(バッククォートで囲まれた文字列)を使用することで、バックスラッシュをエスケープせずにそのまま記述できるため、Windowsパスの表現が簡潔になります。
まとめ
このコミットは、Goのテストにおける一時ファイル管理のベストプラクティスへの移行を示しています。ioutil.TempDir
やos.TempDir
のような標準ライブラリの機能を利用することで、テストの信頼性、独立性、および環境への影響を大幅に改善しています。これにより、テストがより堅牢になり、開発者がテスト環境のクリーンアップについて心配する必要がなくなります。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、src/pkg/os/os_test.go
とsrc/pkg/os/path_test.go
内の、一時ディレクトリの作成とクリーンアップに関する部分です。
src/pkg/os/os_test.go
func TestStatDirWithTrailingSlash(t *testing.T)
--- a/src/pkg/os/os_test.go
+++ b/src/pkg/os/os_test.go
@@ -985,25 +985,24 @@ func TestAppend(t *testing.T) {
}
func TestStatDirWithTrailingSlash(t *testing.T) {
- // Create new dir, in _test so it will get
- // cleaned up by make if not by us.
- path := "_test/_TestStatDirWithSlash_"
- err := MkdirAll(path, 0777)
+ // Create new temporary directory and arrange to clean it up.
+ path, err := ioutil.TempDir("", "/_TestStatDirWithSlash_")
if err != nil {
- t.Fatalf("MkdirAll %q: %s", path, err)
+ t.Fatalf("TempDir: %s", err)
}
defer RemoveAll(path)
// Stat of path should succeed.
_, err = Stat(path)
if err != nil {
- t.Fatal("stat failed:", err)
+ t.Fatalf("stat %s failed: %s", path, err)
}
// Stat of path+"/" should succeed too.
- _, err = Stat(path + "/")
+ path += "/"
+ _, err = Stat(path)
if err != nil {
- t.Fatal("stat failed:", err)
+ t.Fatalf("stat %s failed: %s", path, err)
}
}
src/pkg/os/path_test.go
func TestMkdirAll(t *testing.T)
--- a/src/pkg/os/path_test.go
+++ b/src/pkg/os/path_test.go
@@ -12,14 +12,13 @@ import (
)
func TestMkdirAll(t *testing.T) {
- // Create new dir, in _test so it will get
- // cleaned up by make if not by us.
- path := "_test/_TestMkdirAll_/dir/./dir2"
+ tmpDir := TempDir()
+ path := tmpDir + "_/_TestMkdirAll_/dir/./dir2"
err := MkdirAll(path, 0777)
if err != nil {
t.Fatalf("MkdirAll %q: %s", path, err)
}
- defer RemoveAll("_test/_TestMkdirAll_")
+ defer RemoveAll(tmpDir + "/_TestMkdirAll_")
// Already exists, should succeed.
err = MkdirAll(path, 0777)
@@ -63,7 +62,7 @@ func TestMkdirAll(t *testing.T) {
}
if runtime.GOOS == "windows" {
- path := `_test\_TestMkdirAll_\dir\.\dir2\`
+ path := tmpDir + `\_TestMkdirAll_\dir\.\dir2\`
err := MkdirAll(path, 0777)
if err != nil {
t.Fatalf("MkdirAll %q: %s", path, err)
func TestRemoveAll(t *testing.T)
--- a/src/pkg/os/path_test.go
+++ b/src/pkg/os/path_test.go
@@ -72,8 +71,9 @@ func TestMkdirAll(t *testing.T) {
}
func TestRemoveAll(t *testing.T) {
+ tmpDir := TempDir()
// Work directory.
- path := "_test/_TestRemoveAll_"
+ path := tmpDir + "/_TestRemoveAll_"
fpath := path + "/file"
dpath := path + "/dir"
func TestMkdirAllWithSymlink(t *testing.T)
--- a/src/pkg/os/path_test.go
+++ b/src/pkg/os/path_test.go
@@ -170,19 +170,22 @@ func TestMkdirAllWithSymlink(t *testing.T) {
return
}
- err := Mkdir("_test/dir", 0755)
+ tmpDir := TempDir()
+ dir := tmpDir + "/dir"
+ err := Mkdir(dir, 0755)
if err != nil {
- t.Fatal(`Mkdir "_test/dir":`, err)
+ t.Fatalf("Mkdir %s: %s", dir, err)
}
- defer RemoveAll("_test/dir")
+ defer RemoveAll(dir)
- err = Symlink("dir", "_test/link")
+ link := tmpDir + "/link"
+ err = Symlink("dir", link)
if err != nil {
- t.Fatal(`Symlink "dir", "_test/link":`, err)
+ t.Fatalf("Symlink %s: %s", link, err)
}
- defer RemoveAll("_test/link")
+ defer RemoveAll(link)
- path := "_test/link/foo"
+ path := link + "/foo"
err = MkdirAll(path, 0755)
if err != nil {
t.Errorf("MkdirAll %q: %s", path, err)
コアとなるコードの解説
上記の変更箇所は、Go言語のテストにおける一時ファイル/ディレクトリの管理方法を、より堅牢で標準的なアプローチに移行させるためのものです。
-
ioutil.TempDir
の利用 (os_test.go
):- 変更前は、
_test
という固定のプレフィックスを持つディレクトリをMkdirAll
で作成していました。これは、テストが実行されるディレクトリに依存し、クリーンアップが不確実になる可能性がありました。 - 変更後は、
ioutil.TempDir("", "/_TestStatDirWithSlash_")
を使用しています。- 第一引数の
""
は、OSが提供する標準の一時ディレクトリ(例:/tmp
)を使用することを意味します。 - 第二引数の
"/_TestStatDirWithSlash_"
は、作成される一時ディレクトリ名のプレフィックスとなります。ioutil.TempDir
はこれにランダムな文字列を付加し、ユニークなディレクトリ名を生成します。
- 第一引数の
- これにより、テストはシステムの一時領域に独立した作業ディレクトリを持ち、他のテストやシステムへの影響を最小限に抑えられます。
defer RemoveAll(path)
は、テスト関数が終了する際に、作成された一時ディレクトリとその内容を確実に削除するためのGoのイディオムです。ioutil.TempDir
が返すユニークなパスに対してRemoveAll
を適用することで、クリーンアップの信頼性が向上します。- エラーメッセージも
MkdirAll
からTempDir
に、そしてstat failed
からstat %s failed
に、より具体的になるように修正されています。
- 変更前は、
-
TempDir()
ヘルパー関数の導入と利用 (path_test.go
):path_test.go
の複数のテスト関数(TestMkdirAll
,TestRemoveAll
,TestMkdirAllWithSymlink
)では、tmpDir := TempDir()
という行が追加されています。- この
TempDir()
関数は、おそらくos.TempDir()
を呼び出すか、またはioutil.TempDir
と同様にユニークな一時ディレクトリを生成する内部ヘルパー関数であると考えられます。これにより、テスト用の一時パスを構築する際の基点として、ハードコードされた_test
プレフィックスではなく、動的に取得される一時ディレクトリのパスを使用するようになります。 - 例えば、
path := tmpDir + "_/_TestMkdirAll_/dir/./dir2"
のように、tmpDir
を基点として相対パスを結合することで、テストが実行される環境に依存しない一時パスが生成されます。 - クリーンアップの
defer RemoveAll
も、_test/...
という固定パスからtmpDir + "/..."
という動的なパスに変更され、作成された一時ディレクトリが確実に削除されるように修正されています。 - Windows固有のパスをテストする箇所でも、同様に
tmpDir
を基点とするように変更されており、OS間のパス表現の違いを吸収しつつ、一時ディレクトリの管理を統一しています。
これらの変更は、Goのテストの堅牢性と信頼性を高める上で非常に重要です。一時ディレクトリの適切な管理は、テストの再現性を保証し、開発環境やCI/CDパイプラインの安定性を維持するために不可欠です。
関連リンク
- Go Issue 2573:
os
package tests pollute_test
directory: https://github.com/golang/go/issues/2573 - Go CL 5674064:
os: remove use of _test
: https://golang.org/cl/5674064 (Go Code Reviewサイトのリンク) io/ioutil
パッケージ (Go 1.16以降はos
パッケージに移行):TempDir
関数: https://pkg.go.dev/io/ioutil#TempDir (Go 1.16以前のドキュメント)os.MkdirTemp
関数 (Go 1.16以降): https://pkg.go.dev/os#MkdirTemp
os
パッケージ: https://pkg.go.dev/ostesting
パッケージ: https://pkg.go.dev/testing
参考にした情報源リンク
- Go言語の公式ドキュメント (
pkg.go.dev
) - Go言語のGitHubリポジトリ (特にIssueトラッカーとコミット履歴)
- Go言語のコードレビューシステム (
golang.org/cl
) - Go言語のテストに関する一般的なプラクティスとガイドライン
- ファイルシステム操作に関する一般的な知識
- Go言語の
defer
ステートメントの動作に関する知識 - Go言語の
runtime
パッケージに関する知識 - Go言語のパス操作に関する知識
- Go言語のIssue 2573に関する議論内容 (GitHub Issueページ)
io/ioutil
パッケージからos
パッケージへの一時ファイル/ディレクトリ関数の移行に関する情報 (Go 1.16のリリースノートなど)