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

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

コミット

コミットハッシュ: 448d89d67afdb8c448843efe9e687a944bacda59 作成者: Alex Brainman alex.brainman@gmail.com 日時: 2011年12月20日 11:51:31 +1100 (オーストラリア時間) コミットメッセージ: "old/template: close file in TestAll before deleting it"

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

https://github.com/golang/go/commit/448d89d67afdb8c448843efe9e687a944bacda59

元コミット内容

commit 448d89d67afdb8c448843efe9e687a944bacda59
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Tue Dec 20 11:51:31 2011 +1100

    old/template: close file in TestAll before deleting it

    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5491073
---
 src/pkg/old/template/template_test.go | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/pkg/old/template/template_test.go b/src/pkg/old/template/template_test.go
index 7ec04daa0d..f42a61a1b1 100644
--- a/src/pkg/old/template/template_test.go
+++ b/src/pkg/old/template/template_test.go
@@ -468,7 +468,11 @@ func TestAll(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer os.Remove(f.Name())
+	defer func() {
+		name := f.Name()
+		f.Close()
+		os.Remove(name)
+	}()
 	testAll(t, func(test *Test) (*Template, error) {
 		err := ioutil.WriteFile(f.Name(), []byte(test.in), 0600)
 		if err != nil {

変更の背景

この変更は、Go言語のold/templateパッケージのテストコードで発生していたプラットフォーム固有の問題を修正するためのものです。

Alex Brainmanは、Go言語のWindows移植において重要な貢献をしていた開発者で、2011年当時はGo 1.0のリリース前の開発段階において、WindowsでのGoの動作確保に精力的に取り組んでいました。この修正は、特にWindows環境でテストが正しく動作するための重要な改善でした。

2011年12月の時点では、Go言語は初期の実験段階を抜け出し、より安定したバージョンに向けた開発が進められていました。この修正は、小さな変更に見えますが、プラットフォーム間の互換性を保つための重要な一歩でした。

前提知識の解説

1. Go言語のold/templateパッケージについて

2011年当時、Go言語には「old/template」パッケージが存在していました。これは現在のtext/templatehtml/templateパッケージの前身となるものでした。old/templateパッケージは、テキストテンプレートを解析し、データを埋め込んでテキストを生成するためのパッケージでした。

2. deferの仕組み

Go言語のdefer文は、関数の実行が終了する際に呼び出される処理を予約するためのキーワードです。通常、ファイルを開いた後のクリーンアップ処理に使用されます。

file, err := os.Open("filename")
if err != nil {
    return err
}
defer file.Close() // 関数終了時に実行される

3. プラットフォーム間でのファイル操作の違い

UnixライクなOS(Linux、macOS)とWindowsでは、ファイルの削除に関する挙動が異なります:

  • Unix系: 開いているファイルでもunlinkシステムコールで削除可能。実際の削除は最後のハンドルが閉じられた時点で実行される
  • Windows: 開いているファイルは削除できない。削除前にファイルハンドルを閉じる必要がある

4. テストにおける一時ファイルの管理

テストコードでは、テンプレートの内容を一時ファイルに書き込み、それを読み込んでテストを実行した後、不要になったファイルを削除するという処理が一般的です。

技術的詳細

1. 修正前のコード

defer os.Remove(f.Name())

この実装では、os.Removeの呼び出しがdeferによって予約されているため、関数終了時にファイルの削除が実行されます。しかし、この時点でファイルハンドルfはまだ閉じられていない可能性があります。

2. 修正後のコード

defer func() {
    name := f.Name()
    f.Close()
    os.Remove(name)
}()

修正後では、匿名関数を使用してより明確な処理順序を定義しています:

  1. name := f.Name() - ファイル名を取得
  2. f.Close() - ファイルハンドルを明示的に閉じる
  3. os.Remove(name) - ファイルを削除

3. なぜこの修正が必要だったのか

Windowsでは、開いているファイルの削除が許可されていません。修正前のコードでは、defer os.Remove(f.Name())が実行される時点で、ファイルハンドルfがまだ開いている可能性があり、これがWindows環境でのテスト失敗の原因となっていました。

4. 匿名関数の使用理由

defer文では、引数が評価される時点での値が保存されます。ファイル名を事前に取得しておくことで、後でファイルハンドルが無効になった場合でも正しいファイル名を使用して削除処理を実行できます。

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

変更箇所はsrc/pkg/old/template/template_test.goTestAll関数内の468行目付近です:

// 修正前
defer os.Remove(f.Name())

// 修正後
defer func() {
    name := f.Name()
    f.Close()
    os.Remove(name)
}()

この変更により、以下の処理フローが実現されます:

  1. テスト実行中にファイルが使用される
  2. 関数終了時(テスト完了時)にdefer文が実行される
  3. ファイル名を保存
  4. ファイルハンドルを明示的に閉じる
  5. ファイルを削除

コアとなるコードの解説

1. defer文の実行順序

Go言語のdefer文は後入れ先出し(LIFO)の順序で実行されます。この修正では、単一のdefer文内で処理順序を明示的に制御しています。

2. エラーハンドリングの考慮

修正後のコードでは、f.Close()のエラーを明示的に処理していませんが、これはテストのクリーンアップ処理であり、ファイルのクローズが失敗してもテストの結果には影響しないためです。

3. プラットフォーム互換性の確保

この修正により、Unix系OSとWindows両方でテストが正常に動作するようになりました。プラットフォーム固有の条件分岐を使用する代わりに、より安全で汎用的なアプローチを採用しています。

4. テストの堅牢性向上

ファイルのクローズを明示的に行うことで、テストの実行後にファイルハンドルが残り続ける可能性を排除し、テストの独立性と再実行性を向上させています。

関連リンク

参考にした情報源リンク