[インデックス 14900] ファイルの概要
このコミットは、Go言語の標準ライブラリos
パッケージのテストコードos_test.go
における変更です。具体的には、TestStartProcess
関数内でプロセスの実行結果を検証する際に、ファイルの同一性を比較するためにos.SameFile
関数を使用するように修正されています。これにより、異なるパス表記でも同じファイルを参照している場合にテストが正しくパスするようになります。
コミット
commit 40b3758864deab9e096b6ee8d102caf11ecce051
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Thu Jan 17 18:48:11 2013 +0800
os: use SameFile in TestStartProcess
Fixes #4650.
R=golang-dev, bradfitz, alex.brainman
CC=golang-dev
https://golang.org/cl/7085048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/40b3758864deab9e096b6ee8d102caf11ecce051
元コミット内容
このコミットの元のメッセージは以下の通りです。
os: use SameFile in TestStartProcess
Fixes #4650.
R=golang-dev, bradfitz, alex.brainman
CC=golang-dev
https://golang.org/cl/7085048
これは、os
パッケージのTestStartProcess
においてSameFile
を使用すること、そしてそれがIssue #4650を修正することを示しています。
変更の背景
この変更の背景には、TestStartProcess
テストが特定の環境(特にSolarisのような/bin
が/usr/bin
へのシンボリックリンクであるシステム)で失敗するという問題がありました。
元のコードでは、exec
ヘルパー関数内でプロセスの出力(通常は現在の作業ディレクトリのパス)と期待されるパスを文字列として直接比較していました。しかし、ファイルシステムによっては、同じディレクトリを指すパスが異なる文字列として表現されることがあります(例: /bin
と/usr/bin
が同じ実体を指す場合)。
具体的には、Solarisでは/bin
が/usr/bin
へのシンボリックリンクであるため、pwd
コマンドが/usr/bin
を返すことがありましたが、テストコードでは/bin
を期待していました。この文字列の不一致がテストの失敗を引き起こしていました。
この問題を解決するため、パスの文字列比較ではなく、それらのパスが指すファイルシステム上の実体が同一であるかどうかを比較するos.SameFile
関数が導入されました。これにより、パスの表記方法に依存せず、実体としての同一性を確認できるようになり、テストの堅牢性が向上しました。
前提知識の解説
os.SameFile
関数
os.SameFile(fi1, fi2 os.FileInfo) bool
は、Go言語のos
パッケージで提供される関数です。この関数は、2つのos.FileInfo
インターフェース(ファイルやディレクトリのメタデータを含む構造体)を受け取り、それらが同じファイルシステム上の同じファイルを指している場合にtrue
を返します。
SameFile
は、ファイル名やパスの文字列が異なっていても、それがシンボリックリンクやハードリンク、あるいはファイルシステム固有の特性によって同じ実体を指している場合に真を返します。これは、ファイルシステム上のinode番号やデバイスIDなどの低レベルな情報に基づいて比較を行うことで実現されます。
os.FileInfo
インターフェース
os.FileInfo
は、ファイルに関する情報(ファイル名、サイズ、パーミッション、最終更新時刻、ディレクトリかどうかなど)を提供するインターフェースです。os.Stat
やos.Lstat
などの関数を呼び出すことで、特定のパスに対応するos.FileInfo
オブジェクトを取得できます。
シンボリックリンクとハードリンク
- シンボリックリンク (Symbolic Link / Soft Link): 別のファイルやディレクトリへの参照(ポインタ)です。シンボリックリンク自体は小さなファイルで、リンク先のパスを格納しています。リンク先が削除されると、シンボリックリンクは「壊れたリンク」になります。
- ハードリンク (Hard Link): 同じファイルシステム上の既存のファイルへの追加のエントリです。ハードリンクは、元のファイルと同じinode番号を共有します。つまり、同じファイルシステム上の同じデータブロックを指す複数の名前が存在する状態です。いずれかのリンクが削除されても、他のリンクが存在する限りファイルデータは残ります。
このコミットの背景にある問題は、シンボリックリンクによって同じファイルが異なるパスで参照されるケースに関連しています。
技術的詳細
変更はsrc/pkg/os/os_test.go
ファイル内のexec
ヘルパー関数とTestStartProcess
関数に集中しています。
exec
ヘルパー関数の変更
元のexec
関数では、プロセスの出力output
と期待される値expect
を直接文字列比較していました。Solarisの/usr
プレフィックスを許容するための特別な条件も含まれていました。
// Before
if output != expect && output != "/usr"+expect {
t.Errorf("exec %q returned %q wanted %q",
strings.Join(append([]string{cmd}, args...), " "), output, expect)
}
この部分が以下のように変更されました。
output
とexpect
のそれぞれについて、os.Stat
を呼び出してos.FileInfo
オブジェクトを取得します。strings.TrimSpace(output)
は、出力に含まれる可能性のある改行コードなどを除去するためです。- 取得した2つの
os.FileInfo
オブジェクトをos.SameFile
関数に渡して比較します。 SameFile
がfalse
を返した場合(つまり、ファイルシステム上の実体が異なる場合)にテストエラーとします。
// After
fi1, _ := Stat(strings.TrimSpace(output))
fi2, _ := Stat(expect)
if !SameFile(fi1, fi2) {
t.Errorf("exec %q returned %q wanted %q",
strings.Join(append([]string{cmd}, args...), " "), output, expect)
}
この変更により、パスの文字列表現の違いを吸収し、ファイルシステム上の実体としての同一性を正確に検証できるようになりました。
TestStartProcess
関数の変更
TestStartProcess
関数では、WindowsとUnix系システムで異なる改行コード(\r\n
と\n
)を考慮するためにle
(line ending)変数を使用していました。
// Before
var dir, cmd, le string
// ...
if runtime.GOOS == "windows" {
le = "\r\n"
// ...
} else {
le = "\n"
// ...
}
// ...
exec(t, dir, cmd, args, dir+le)
exec(t, cmddir, cmdbase, args, filepath.Clean(cmddir)+le)
SameFile
による比較では、ファイルパスの文字列自体に改行コードが含まれている必要がないため、le
変数は不要になりました。
// After
var dir, cmd string // 'le' variable removed
// ...
// No 'le' assignment
// ...
exec(t, dir, cmd, args, dir) // 'le' removed from arguments
exec(t, cmddir, cmdbase, args, cmddir) // 'le' removed from arguments
これにより、テストコードが簡潔になり、プラットフォームごとの改行コードの違いを意識する必要がなくなりました。
コアとなるコードの変更箇所
変更はsrc/pkg/os/os_test.go
ファイルにあります。
--- a/src/pkg/os/os_test.go
+++ b/src/pkg/os/os_test.go
@@ -536,8 +536,10 @@ func exec(t *testing.T, dir, cmd string, args []string, expect string) {
var b bytes.Buffer
io.Copy(&b, r)
output := b.String()
- // Accept /usr prefix because Solaris /bin is symlinked to /usr/bin.
- if output != expect && output != "/usr"+expect {
+
+ fi1, _ := Stat(strings.TrimSpace(output))
+ fi2, _ := Stat(expect)
+ if !SameFile(fi1, fi2) {
t.Errorf("exec %q returned %q wanted %q",
strings.Join(append([]string{cmd}, args...), " "), output, expect)
}
@@ -545,15 +547,13 @@ func exec(t *testing.T, dir, cmd string, args []string, expect string) {
}
func TestStartProcess(t *testing.T) {
- var dir, cmd, le string
+ var dir, cmd string
var args []string
if runtime.GOOS == "windows" {
-\t\tle = "\r\n"
cmd = Getenv("COMSPEC")
dir = Getenv("SystemRoot")
args = []string{"/c", "cd"}
} else {
-\t\tle = "\n"
cmd = "/bin/pwd"
dir = "/"
args = []string{}
@@ -561,9 +561,9 @@ func TestStartProcess(t *testing.T) {
cmddir, cmdbase := filepath.Split(cmd)
args = append([]string{cmdbase}, args...)
// Test absolute executable path.
-\texec(t, dir, cmd, args, dir+le)
+\texec(t, dir, cmd, args, dir)
// Test relative executable path.
-\texec(t, cmddir, cmdbase, args, filepath.Clean(cmddir)+le)
+\texec(t, cmddir, cmdbase, args, cmddir)
}
func checkMode(t *testing.T, path string, mode FileMode) {
コアとなるコードの解説
exec
関数内の変更
-
変更前:
if output != expect && output != "/usr"+expect { // ... }
ここでは、
output
文字列がexpect
文字列と完全に一致するか、またはSolaris環境での特殊なケースとして/usr
プレフィックスが付いたexpect
と一致するかをチェックしていました。これは文字列ベースの比較であり、ファイルシステム上の同一性を保証するものではありませんでした。 -
変更後:
fi1, _ := Stat(strings.TrimSpace(output)) fi2, _ := Stat(expect) if !SameFile(fi1, fi2) { // ... }
この変更がこのコミットの核心です。
strings.TrimSpace(output)
: プロセスからの出力(output
)には末尾に改行コードが含まれる可能性があるため、strings.TrimSpace
で空白文字(改行含む)を除去し、純粋なパス文字列を取得します。Stat(...)
:os.Stat
関数は指定されたパスのos.FileInfo
を返します。これはファイルシステム上のファイルやディレクトリに関するメタデータを含みます。エラーハンドリングはテストコードなので省略されていますが、実際にはエラーチェックが必要です。SameFile(fi1, fi2)
: 取得した2つのos.FileInfo
オブジェクト(fi1
とfi2
)をos.SameFile
関数に渡します。この関数は、これら2つのFileInfo
がファイルシステム上の同じ実体を指している場合にtrue
を返します。!SameFile(fi1, fi2)
:SameFile
がfalse
を返した場合、つまり実体が異なる場合にテストエラーを発生させます。
この変更により、パスの文字列表現の違い(例: /bin
と/usr/bin
)を吸収し、ファイルシステム上の実体としての同一性を正確に検証できるようになりました。これにより、シンボリックリンクなどが絡む環境でもテストが安定して動作するようになります。
TestStartProcess
関数内の変更
-
変更前:
var dir, cmd, le string // ... if runtime.GOOS == "windows" { le = "\r\n" } else { le = "\n" } // ... exec(t, dir, cmd, args, dir+le) exec(t, cmddir, cmdbase, args, filepath.Clean(cmddir)+le)
WindowsとUnix系システムで異なる改行コード(
\r\n
と\n
)を考慮するためにle
変数を使用していました。exec
関数に渡す期待値のパスにこの改行コードを付加していました。 -
変更後:
var dir, cmd string // 'le'変数が削除された // ... // 'le'の割り当てがなくなった // ... exec(t, dir, cmd, args, dir) // 'le'が引数から削除された exec(t, cmddir, cmdbase, args, cmddir) // 'le'が引数から削除された
exec
関数がSameFile
を使用してファイルシステム上の実体を比較するようになったため、期待されるパス文字列に改行コードを含める必要がなくなりました。これにより、le
変数が不要になり、コードが簡潔になりました。
このコミットは、Goのテストコードの堅牢性を高め、異なるOS環境での互換性を向上させるための重要な改善です。
関連リンク
- Go Issue #4650: https://github.com/golang/go/issues/4650
- Go CL 7085048: https://golang.org/cl/7085048
os
パッケージのドキュメント: https://pkg.go.dev/osos.SameFile
のドキュメント: https://pkg.go.dev/os#SameFile
参考にした情報源リンク
- Go言語の公式ドキュメント
- GitHubのGoリポジトリのIssueとChange List (CL)
- Stack Overflowや技術ブログなど、Go言語の
os.SameFile
に関する一般的な解説