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

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

このコミットは、Go言語の標準ライブラリ os パッケージ内のテスト TestStatDirModeExec の修正に関するものです。特定のシステム環境において、ディレクトリの実行パーミッションのテストが失敗する問題を解決するため、テストがユーザー属性のみをチェックするように変更されました。また、テストの独立性を高めるために、カレントディレクトリではなく一時ディレクトリを使用するように修正されています。

コミット

commit 902af974cbdf3af5ae435e3d34f2ac16beb207d1
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Fri Nov 30 16:10:45 2012 +1100

    os: check only user attributes in TestStatDirModeExec
    
    Some have their system setup in a particular way,
    see http://golang.org/issue/4444#c3.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6851129

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

https://github.com/golang/go/commit/902af974cbdf3af5ae435e3d34f2ac16beb207d1

元コミット内容

os: check only user attributes in TestStatDirModeExec
    
Some have their system setup in a particular way,
see http://golang.org/issue/4444#c3.
    
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6851129

変更の背景

この変更は、Go言語のIssue 4444に関連しています。具体的には、TestStatDirModeExec というテストが、一部のユーザーのシステム設定(特にWindows環境)において、ディレクトリの実行パーミッションのチェックで誤って失敗するという問題が報告されていました。

Go言語の os パッケージは、ファイルシステム操作に関する機能を提供します。os.Stat 関数は、指定されたパスのファイルまたはディレクトリの情報を取得し、その情報にはパーミッションモードも含まれます。Unix系システムでは、ディレクトリの実行パーミッション(x ビット)は、そのディレクトリ内のファイルにアクセスしたり、そのディレクトリを cd コマンドで移動したりするために必要です。

Issue 4444では、Windows環境の mkMode 関数がディレクトリに対して 0111 (実行パーミッション) を設定しないことが議論されていました。これは、Unix系システムとWindowsシステムでのパーミッションの扱いの違いに起因します。Windowsでは、ディレクトリの実行パーミッションはUnix系システムほど厳密に扱われないことが多く、特定のシステム設定によっては、os.Stat が返すディレクトリのモードが期待される 0111 を含まない場合がありました。

TestStatDirModeExec は、ディレクトリのモードが 0111 を含んでいることを検証するテストでしたが、上記のようなシステム設定の違いにより、テストが不正確な結果を返すことがありました。このコミットは、このテストの信頼性を向上させ、特定の環境に依存しないようにするために行われました。

前提知識の解説

ファイルパーミッション (Unix/Linux)

Unix/Linuxシステムにおけるファイルやディレクトリのパーミッションは、以下の3つのカテゴリに対して、読み取り (r)、書き込み (w)、実行 (x) の3種類の権限を組み合わせることで表現されます。

  • ユーザー (User): ファイルの所有者
  • グループ (Group): ファイルの所有グループに属するユーザー
  • その他 (Others): 上記以外のすべてのユーザー

これらの権限は、通常、8進数表記で表現されます。各権限はビットに対応し、読み取りが4 (100), 書き込みが2 (010), 実行が1 (001) となります。

  • rwx = 4+2+1 = 7
  • rw- = 4+2+0 = 6
  • r-x = 4+0+1 = 5
  • r-- = 4+0+0 = 4
  • -wx = 0+2+1 = 3
  • -w- = 0+2+0 = 2
  • --x = 0+0+1 = 1
  • --- = 0+0+0 = 0

例えば、0755 というパーミッションは、所有者には読み書き実行 (7)、グループには読み取りと実行 (5)、その他には読み取りと実行 (5) の権限を与えることを意味します。

ディレクトリの実行パーミッション (x ビット)

ディレクトリにおける実行パーミッション (x ビット) は、そのディレクトリの中に入ること(cd コマンドで移動すること)や、そのディレクトリ内のファイルにアクセスするために必要です。ディレクトリに x ビットがない場合、たとえそのディレクトリ内のファイルに読み取り権限があっても、そのファイルにアクセスすることはできません。

os.Stat 関数

Go言語の os パッケージには、ファイルやディレクトリの情報を取得するための Stat 関数があります。

func Stat(name string) (FileInfo, error)

Stat 関数は、指定された name のファイルまたはディレクトリの FileInfo インターフェースを返します。FileInfo インターフェースは、ファイル名、サイズ、更新時刻、そしてパーミッションモードなどの情報を提供します。

os.Chmod 関数

os.Chmod 関数は、ファイルまたはディレクトリのパーミッションを変更するために使用されます。

func Chmod(name string, mode FileMode) error

name は変更するファイルまたはディレクトリのパス、mode は設定する新しいパーミッションモードです。

ioutil.TempDirdefer RemoveAll

  • ioutil.TempDir(dir, pattern string) (name string, err error): 一時的なディレクトリを作成します。dir が空文字列の場合、システムのデフォルトの一時ディレクトリが使用されます。pattern は作成されるディレクトリ名のプレフィックスとして使用されます。
  • defer ステートメント: Go言語の defer ステートメントは、関数がリターンする直前に実行される関数呼び出しをスケジュールします。これは、リソースのクリーンアップ(ファイルのクローズ、ロックの解除など)に非常に便利です。
  • os.RemoveAll(path string) error: 指定されたパスのファイルまたはディレクトリを、その内容すべてを含めて再帰的に削除します。

このコミットでは、ioutil.TempDir で一時ディレクトリを作成し、defer RemoveAll(path) を使ってテスト終了時にその一時ディレクトリを確実に削除することで、テストの副作用をなくし、テスト環境をクリーンに保っています。

技術的詳細

このコミットの技術的な核心は、TestStatDirModeExec テストがディレクトリの実行パーミッションをチェックする方法の変更にあります。

元のコードでは、テストはカレントディレクトリ (.) に対して Stat を呼び出し、そのモードが 0111 (ユーザー、グループ、その他の実行ビットがすべてセットされている状態) を含んでいるかをチェックしていました。

func TestStatDirModeExec(t *testing.T) {
	const mode = 0111
	const path = "." // カレントディレクトリをテスト対象とする
	dir, err := Stat(path)
	// ...
	if dir.Mode()&mode != mode { // dir.Mode() が 0111 を含んでいるかチェック
		t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode(), mode)
	}
}

しかし、一部のシステム(特にWindows)では、os.Stat が返すディレクトリのモードが、グループやその他の実行ビットを含まない場合がありました。これは、WindowsのファイルシステムがUnixのような厳密なパーミッションモデルを持たないため、Goの os パッケージがWindowsのパーミッションをUnixモードにマッピングする際に、すべての実行ビットをセットしないことがあるためです。Issue 4444のコメントでは、Windowsの mkMode がディレクトリに対して 0444 または 0666 を返し、0111 ビットを設定すべきであると指摘されていました。

この問題に対処するため、コミットは以下の2つの主要な変更を導入しました。

  1. 一時ディレクトリの使用: 元のテストはカレントディレクトリ (.) を使用していましたが、これはテストの実行環境に依存する可能性がありました。例えば、カレントディレクトリのパーミッションがテストの期待と異なる場合、テストが失敗する可能性があります。 新しいコードでは、ioutil.TempDir を使用して一時的なディレクトリを作成し、そのディレクトリに対してテストを実行します。これにより、テストは独立したクリーンな環境で実行され、外部の環境要因に影響されなくなります。 作成された一時ディレクトリは、defer RemoveAll(path) によってテスト終了時に確実に削除されます。

  2. ユーザー実行パーミッションのみのチェック: 最も重要な変更は、dir.Mode()&mode != mode の比較方法です。 元のコードでは、mode0111 であり、これはユーザー、グループ、その他の実行ビットすべてを意味していました。dir.Mode()&mode は、dir.Mode() の中で 0111 に対応するビットがすべてセットされているかをチェックしていました。 しかし、Windowsなどのシステムでは、グループやその他の実行ビットがセットされないことがあるため、このチェックは厳しすぎました。 コミットの意図は「ユーザー属性のみをチェックする」ことですが、コードの変更自体は dir.Mode()&mode != mode の比較ロジックを直接変更しているわけではありません。代わりに、テスト対象のディレクトリのパーミッションを明示的に 0777 に設定しています。

    if err := Chmod(path, 0777); err != nil {
    	t.Fatalf("Chmod %q 0777: %v", path, err)
    }
    

    0777 は、ユーザー、グループ、その他のすべてに読み書き実行のパーミッションを与えることを意味します。これにより、テスト対象のディレクトリは確実に 0111 (実行ビット) を含むモードを持つことになります。

    そして、エラーメッセージの出力部分で dir.Mode()dir.Mode()&mode に変更しています。

    // 変更前: t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode(), mode)
    // 変更後: t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode()&mode, mode)
    

    これは、エラーが発生した場合に、実際に mode (0111) とAND演算された結果のモードを出力することで、デバッグ情報をより正確にするための変更です。これにより、dir.Mode() 全体ではなく、テストが関心を持っている実行ビットの部分だけがエラーメッセージに表示されるようになります。

    この変更により、テストは、システムがディレクトリに対して 0111 の実行パーミッションを正しく設定できることを確認しつつ、特定の環境でのパーミッションマッピングの差異による誤った失敗を避けることができます。

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

--- a/src/pkg/os/os_test.go
+++ b/src/pkg/os/os_test.go
@@ -1098,12 +1098,22 @@ func TestLargeWriteToConsole(t *testing.T) {\
 
 func TestStatDirModeExec(t *testing.T) {\
 	const mode = 0111
-	const path = "."
+
+	path, err := ioutil.TempDir("", "go-build")
+	if err != nil {
+		t.Fatalf("Failed to create temp directory: %v", err)
+	}
+	defer RemoveAll(path)
+
+	if err := Chmod(path, 0777); err != nil {
+		t.Fatalf("Chmod %q 0777: %v", path, err)
+	}
+
 	dir, err := Stat(path)
 	if err != nil {
 		t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err)
 	}
 	if dir.Mode()&mode != mode {\
-		t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode(), mode)\
+		t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode()&mode, mode)\
 	}\
 }\

コアとなるコードの解説

変更前

func TestStatDirModeExec(t *testing.T) {
	const mode = 0111
	const path = "." // カレントディレクトリをテスト対象とする
	dir, err := Stat(path)
	if err != nil {
		t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err)
	}
	if dir.Mode()&mode != mode { // dir.Mode() が 0111 を含んでいるかチェック
		t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode(), mode)
	}
}

この元のテストでは、path 変数にカレントディレクトリを示す . がハードコードされていました。これにより、テストの実行環境(カレントディレクトリのパーミッションなど)に依存する可能性がありました。また、dir.Mode()0111 (ユーザー、グループ、その他の実行ビット) をすべて含んでいるかを厳密にチェックしていました。

変更後

func TestStatDirModeExec(t *testing.T) {
	const mode = 0111

	// 一時ディレクトリを作成
	path, err := ioutil.TempDir("", "go-build")
	if err != nil {
		t.Fatalf("Failed to create temp directory: %v", err)
	}
	// テスト終了時に一時ディレクトリを確実に削除
	defer RemoveAll(path)

	// 作成した一時ディレクトリのパーミッションを0777に設定
	if err := Chmod(path, 0777); err != nil {
		t.Fatalf("Chmod %q 0777: %v", path, err)
	}

	dir, err := Stat(path)
	if err != nil {
		t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err)
	}
	if dir.Mode()&mode != mode {
		// エラーメッセージで、dir.Mode()全体ではなく、modeとのAND演算結果を表示
		t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode()&mode, mode)
	}
}
  1. path, err := ioutil.TempDir("", "go-build"): ioutil.TempDir を使用して、一時的なディレクトリを新しく作成します。これにより、テストはクリーンで予測可能な環境で実行され、カレントディレクトリの既存のパーミッション設定に影響されなくなります。"" はシステムのデフォルトの一時ディレクトリを使用することを意味し、"go-build" は作成されるディレクトリ名のプレフィックスです。

  2. if err != nil { t.Fatalf("Failed to create temp directory: %v", err) }: 一時ディレクトリの作成に失敗した場合、テストを即座に終了させ、エラーメッセージを出力します。

  3. defer RemoveAll(path): defer キーワードにより、この行の RemoveAll(path) 関数は、TestStatDirModeExec 関数がリターンする直前に実行されるようにスケジュールされます。これにより、テストが成功しても失敗しても、作成された一時ディレクトリとその内容が確実に削除され、テスト後のクリーンアップが行われます。

  4. if err := Chmod(path, 0777); err != nil { t.Fatalf("Chmod %q 0777: %v", path, err) }: 新しく作成された一時ディレクトリのパーミッションを 0777 に設定します。0777 は、ユーザー、グループ、その他のすべてに読み取り、書き込み、実行のパーミッションを与えることを意味します。これにより、テスト対象のディレクトリが確実に 0111 (実行ビット) を含むモードを持つことが保証され、テストの信頼性が向上します。

  5. t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode()&mode, mode): エラーメッセージのフォーマットが変更されました。元のコードでは dir.Mode() 全体を出力していましたが、変更後では dir.Mode()&mode を出力しています。これは、dir.Mode() の中で mode (0111) に対応するビットが実際にどうなっているか、つまりテストが関心を持っている実行ビットの部分だけをエラーメッセージに表示することで、デバッグ情報をより正確にするための改善です。

これらの変更により、TestStatDirModeExec はより堅牢になり、特定のシステム環境に依存することなく、ディレクトリの実行パーミッションが正しく扱われることを検証できるようになりました。

関連リンク

参考にした情報源リンク