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

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

このコミットは、Go言語の標準ライブラリosパッケージ内のTestMkdirAllWithSymlinkテストの堅牢性を向上させるものです。具体的には、テストが実行される環境がクリーンであることを前提とせず、一時ディレクトリを適切に利用することで、テストの信頼性と移植性を高めています。これにより、FreeBSD環境でのビルド失敗が修正されました。

コミット

commit 54c109b122b2afe911882cc9f8c2dcaaa7e0005c
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Sun Oct 7 10:31:56 2012 -0700

    os: make TestMkdirAllWithSymlink more robust
    
    Don't assume the test has a clean environment within /tmp.
    Use an actual new tempdir for its tests.
    
    Fixes FreeBSD build failure as seen at:
    http://build.golang.org/log/396738676356d7fb6bab6eaf1b97cac820f8a90f
    
    --- FAIL: TestMkdirAllWithSymlink (0.00 seconds)
    path_test.go:178:                 Mkdir /tmp/dir: mkdir /tmp/dir: file exists
    FAIL
    FAIL    os      1.091s
    
    R=golang-dev, minux.ma
    CC=golang-dev
    https://golang.org/cl/6615057

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

https://github.com/golang/go/commit/54c109b122b2afe911882cc9f8c2dcaaa7e0005c

元コミット内容

このコミットの目的は、osパッケージのTestMkdirAllWithSymlinkテストをより堅牢にすることです。テストが/tmpディレクトリ内のクリーンな環境を前提としないように、テスト専用の新しい一時ディレクトリを使用するように変更されています。これにより、FreeBSDでのビルド失敗が修正されました。

具体的な失敗ログは以下の通りです。

--- FAIL: TestMkdirAllWithSymlink (0.00 seconds)
path_test.go:178:                 Mkdir /tmp/dir: mkdir /tmp/dir: file exists
FAIL
FAIL    os      1.091s

変更の背景

この変更は、Go言語のビルドシステムにおいてFreeBSD環境で発生していたTestMkdirAllWithSymlinkテストの失敗を修正するために行われました。テストの失敗ログが示すように、/tmp/dirというディレクトリが既に存在するためにMkdir呼び出しが失敗していました。

これは、テストが実行される環境が常にクリーンであるという誤った前提に基づいていたことが原因です。特に、共有の一時ディレクトリ(/tmpなど)を使用するテストでは、以前のテスト実行や他のプロセスが残したファイルやディレクトリが原因で、テストが非決定的に失敗する可能性があります。FreeBSDのビルド環境では、何らかの理由でテスト実行前に/tmp/dirが存在していたか、あるいはテストスイート内の他のテストが同じパスを使用し、クリーンアップが不十分であった可能性が考えられます。

このような問題は、テストの信頼性を損ない、CI/CDパイプラインでの誤った失敗を引き起こすため、早急な修正が必要でした。テストは、外部環境に依存せず、自己完結的で再現可能であるべきです。

前提知識の解説

このコミットを理解するためには、以下のGo言語の標準ライブラリの機能と、テストにおける一般的なプラクティスに関する知識が必要です。

  1. osパッケージ:

    • os.Mkdir(name string, perm FileMode) error: 指定されたパスnameにディレクトリを作成します。permは作成されるディレクトリのパーミッションを指定します。既にディレクトリが存在する場合、エラーを返します。
    • os.MkdirAll(path string, perm FileMode) error: 指定されたパスpathにディレクトリを作成します。パスの途中に存在しないディレクトリがあれば、それらもすべて作成します。既にディレクトリが存在してもエラーにはなりません。
    • os.Symlink(oldname, newname string) error: oldnameへのシンボリックリンクをnewnameとして作成します。
    • os.RemoveAll(path string) error: 指定されたパスpathにあるファイルまたはディレクトリ(およびその内容)をすべて削除します。
  2. io/ioutilパッケージ:

    • io/ioutil.TempDir(dir, pattern string) (name string, err error): 一時ディレクトリを作成します。dirが空文字列の場合、システムのデフォルトの一時ディレクトリ(通常は/tmp)内に作成されます。patternは作成されるディレクトリ名のプレフィックスとして使用されます。この関数は、テストや一時的なファイル操作において、他のテストやシステムの状態に影響を与えない独立した作業空間を確保するために非常に重要です。
  3. Go言語のテスト:

    • testingパッケージ: Go言語のテストフレームワークを提供します。
    • func TestXxx(t *testing.T): テスト関数はTestで始まり、*testing.T型の引数を取ります。
    • t.Fatal(args ...interface{}): テストを失敗としてマークし、実行を停止します。
    • テストの分離とクリーンアップ: 堅牢なテストを書く上で最も重要な原則の一つは、テストが互いに独立しており、実行環境に副作用を残さないことです。一時ファイルやディレクトリを作成するテストでは、テストの終了時にそれらを確実にクリーンアップする必要があります。deferステートメントとos.RemoveAllを組み合わせることで、このクリーンアップを確実に行うことができます。

技術的詳細

このコミットの技術的な核心は、テストの実行環境の分離と、それによるテストの堅牢性の向上にあります。

元のコードでは、os.TempDir()を使用してシステムの一時ディレクトリのパスを取得し、その中にdirという固定の名前のディレクトリを作成していました。

tmpDir := TempDir()
dir := tmpDir + "/dir"
err := Mkdir(dir, 0755)

このアプローチの問題点は、os.TempDir()が返すパスはシステム全体で共有される可能性があり、もし以前のテスト実行や他のプロセスが同じパス(例: /tmp/dir)にディレクトリを残していた場合、os.Mkdirが「file exists」エラーを返すことです。これは、テストが非決定的に失敗する原因となります。

新しいコードでは、io/ioutil.TempDir("", "TestMkdirAllWithSymlink-")を使用しています。

tmpDir, err := ioutil.TempDir("", "TestMkdirAllWithSymlink-")
if err != nil {
    t.Fatal(err)
}
defer RemoveAll(tmpDir)

ioutil.TempDirは、システムの一時ディレクトリ内に一意な名前を持つ新しいディレクトリを作成します。これにより、他のテストやシステムの状態に影響されることなく、テスト専用のクリーンな作業空間が確保されます。

さらに重要な変更点は、defer RemoveAll(tmpDir)が追加されたことです。これにより、テスト関数が終了する際に、tmpDirとその内容(dirlinkなど、テスト中に作成されたすべてのファイルやディレクトリ)が確実に削除されます。元のコードでは、defer RemoveAll(dir)defer RemoveAll(link)のように個別にクリーンアップを行っていましたが、新しいアプローチでは、ルートの一時ディレクトリを削除するだけで、その配下のすべてのリソースがクリーンアップされるため、よりシンプルで確実なクリーンアップが実現されます。これにより、テストが実行環境にゴミを残すことがなくなり、次のテスト実行の妨げになる可能性が排除されます。

この変更は、テストの冪等性(何度実行しても同じ結果になること)と独立性を保証するための標準的なプラクティスに従っています。

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

変更はsrc/pkg/os/path_test.goファイルに集中しています。

--- a/src/pkg/os/path_test.go
+++ b/src/pkg/os/path_test.go
@@ -5,6 +5,7 @@
  package os_test
  
  import (
+\t"io/ioutil"\n \t. "os"\n \t"path/filepath"\n \t"runtime"\n@@ -171,20 +172,23 @@ func TestMkdirAllWithSymlink(t *testing.T) {\n  \t\treturn\n  \t}\n  \n-\ttmpDir := TempDir()\n+\ttmpDir, err := ioutil.TempDir(\"\", \"TestMkdirAllWithSymlink-\")\n+\tif err != nil {\n+\t\tt.Fatal(err)\n+\t}\n+\tdefer RemoveAll(tmpDir)\n+\n  \tdir := tmpDir + \"/dir\"\n-\terr := Mkdir(dir, 0755)\n+\terr = Mkdir(dir, 0755)\n  \tif err != nil {\n  \t\tt.Fatalf(\"Mkdir %s: %s\", dir, err)\n  \t}\n-\tdefer RemoveAll(dir)\n  \n  \tlink := tmpDir + \"/link\"\n  \terr = Symlink(\"dir\", link)\n  \tif err != nil {\n  \t\tt.Fatalf(\"Symlink %s: %s\", link, err)\n  \t}\n-\tdefer RemoveAll(link)\n  \n  \tpath := link + \"/foo\"\n  \terr = MkdirAll(path, 0755)\n```

## コアとなるコードの解説

1.  **`io/ioutil`パッケージのインポート追加**:
    ```diff
    +\t"io/ioutil"\n
    ```
    `ioutil.TempDir`関数を使用するために、`io/ioutil`パッケージがインポートされました。

2.  **一時ディレクトリの作成方法の変更**:
    ```diff
    -\ttmpDir := TempDir()\n
    +\ttmpDir, err := ioutil.TempDir(\"\", \"TestMkdirAllWithSymlink-\")\n
    +\tif err != nil {\n
    +\t\tt.Fatal(err)\n
    +\t}\n
    +\tdefer RemoveAll(tmpDir)\n
    ```
    *   元のコードでは`os.TempDir()`を使ってシステムの一時ディレクトリのパスを取得していました。これは固定のパスを返すため、他のプロセスや以前のテスト実行が残したファイルと衝突する可能性がありました。
    *   新しいコードでは`ioutil.TempDir("", "TestMkdirAllWithSymlink-")`を使用しています。これにより、システムの一時ディレクトリ内に`TestMkdirAllWithSymlink-`で始まる一意な名前の一時ディレクトリが作成されます。このディレクトリはテスト専用であり、他のテストやシステムの状態に影響されません。
    *   `ioutil.TempDir`はエラーを返す可能性があるため、エラーチェックが追加され、エラーが発生した場合は`t.Fatal(err)`でテストを即座に終了させています。
    *   `defer RemoveAll(tmpDir)`が追加されました。これにより、`TestMkdirAllWithSymlink`関数が終了する際に、作成された一時ディレクトリ`tmpDir`とその配下のすべてのファイルやディレクトリが確実に削除されます。これは、テストがクリーンアップを確実に行い、システムにゴミを残さないようにするための重要な変更です。

3.  **`Mkdir`呼び出しの変更**:
    ```diff
    -\terr := Mkdir(dir, 0755)\n
    +\terr = Mkdir(dir, 0755)\n
    ```
    `tmpDir`の取得方法が変わったため、`dir`変数の初期化は不要になり、既存の`err`変数に再代入する形に変更されています。機能的な変更はありませんが、コードの整合性を保つための修正です。

4.  **個別の`defer RemoveAll`の削除**:
    ```diff
    -\tdefer RemoveAll(dir)\n
    ...
    -\tdefer RemoveAll(link)\n
    ```
    新しいコードでは、ルートの一時ディレクトリ`tmpDir`に対して`defer RemoveAll(tmpDir)`が設定されているため、その配下に作成された`dir`や`link`といったディレクトリやシンボリックリンクを個別に削除する必要がなくなりました。これにより、クリーンアップロジックが簡素化され、より堅牢になりました。

これらの変更により、`TestMkdirAllWithSymlink`テストは、実行環境の事前状態に依存せず、常に独立したクリーンな環境で実行されるようになり、FreeBSDでのビルド失敗が解消されました。

## 関連リンク

*   Go言語のコードレビューシステム (Gerrit): [https://golang.org/cl/6615057](https://golang.org/cl/6615057)
*   Go言語のビルドログ (FreeBSDでの失敗): [http://build.golang.org/log/396738676356d7fb6bab6eaf1b97cac820f8a90f](http://build.golang.org/log/396738676356d7fb6bab6eaf1b97cac820f8a90f)

## 参考にした情報源リンク

*   Go言語 `os` パッケージのドキュメント: [https://pkg.go.dev/os](https://pkg.go.dev/os)
*   Go言語 `io/ioutil` パッケージのドキュメント (Go 1.16以降は`io`と`os`に統合): [https://pkg.go.dev/io/ioutil](https://pkg.go.dev/io/ioutil)
*   Go言語 `testing` パッケージのドキュメント: [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
*   Go言語のテストに関する公式ブログ記事やドキュメント (一般的なテストプラクティス):
    *   [https://go.dev/blog/testing](https://go.dev/blog/testing)
    *   [https://go.dev/doc/tutorial/add-a-test](https://go.dev/doc/tutorial/add-a-test)
*   一時ディレクトリとファイルの扱いに関する一般的なプログラミングのベストプラクティス。
*   FreeBSDのビルド環境に関する情報 (一般的なビルドシステムにおける環境依存の問題): [https://www.freebsd.org/](https://www.freebsd.org/)
*   Go言語のIssue Tracker (関連する可能性のあるバグ報告): [https://github.com/golang/go/issues](https://github.com/golang/go/issues)
*   Go言語のコミット履歴とコードレビューの慣習。
*   `Mkdir /tmp/dir: mkdir /tmp/dir: file exists` エラーメッセージに関する一般的な情報。
*   `defer` ステートメントの動作と、リソースクリーンアップにおけるその重要性。
*   Go言語におけるエラーハンドリングの慣習。