[インデックス 16595] ファイルの概要
このコミットは、Go言語の標準ライブラリ os/exec
パッケージ内のテストファイル exec_test.go
における TestExtraFilesFDShuffle
テストのポータビリティ(移植性)を向上させるための変更です。特に、テスト実行環境で予期せず開かれたファイルディスクリプタ(FD)がテストの失敗を引き起こす問題を解決し、特定のオペレーティングシステム(macOS, NetBSD, Windows)での挙動を調整しています。
コミット
- Author: Cosmos Nicolaou cnicolaou@google.com
- Date: Tue Jun 18 08:55:32 2013 -0700
- Reviewers: golang-dev, minux.ma
- CC: golang-dev
- CL: https://golang.org/cl/9103045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f882bc8708e52ccc1a6844294ac29d73fc04dd41
元コミット内容
os/exec: make exec_test.go:TestExtraFilesFDShuffle portable.
R=golang-dev, minux.ma
CC=golang-dev
https://golang.org/cl/9103045
変更の背景
os/exec
パッケージは、外部コマンドを実行するための機能を提供します。このパッケージのテスト、特に TestExtraFilesFDShuffle
は、プロセス間でファイルディスクリプタをどのように引き渡すかを検証するものです。しかし、このテストは特定の環境、特にGoのビルドシステム(Go builder)上で実行された際に、予期せぬファイルディスクリプタのリークが原因で失敗するという問題に直面していました。
具体的には、以下のIssueが報告されていました。
- Issue 2603:
os/exec: TestExtraFilesFDShuffle leaks file descriptors on darwin
(macOS環境でファイルディスクリプタがリークする問題) - Issue 3955:
os/exec: TestExtraFilesFDShuffle fails on netbsd
(NetBSD環境でテストが失敗する問題)
これらの問題は、テストが実行される前に、テストプロセス自体やその親プロセスによって開かれたファイルディスクリプタが、テストの期待する状態を妨害するために発生していました。テストのポータビリティを確保し、異なるOSやビルド環境で安定して動作させるために、この変更が必要とされました。また、Windows環境ではこのテストが意図する機能がOSレベルでサポートされていないため、テストをスキップする必要がありました。
前提知識の解説
-
ファイルディスクリプタ (File Descriptor, FD): Unix系OSにおいて、ファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる非負の整数です。標準入力 (stdin) はFD 0、標準出力 (stdout) はFD 1、標準エラー出力 (stderr) はFD 2として予約されています。プロセスが新しいファイルを開いたり、ソケット接続を確立したりすると、新しいFDが割り当てられます。
-
os/exec
パッケージ: Go言語の標準ライブラリで、外部コマンドを実行するための機能を提供します。Cmd
構造体を通じて、実行するコマンド、引数、環境変数、標準入出力のリダイレクトなどを設定できます。特に、ExtraFiles
フィールドを使用すると、子プロセスに追加のファイルディスクリプタを引き渡すことができます。 -
syscall.StartProcess
:os/exec
パッケージの内部で利用される、低レベルなシステムコールです。新しいプロセスを開始し、そのプロセスにファイルディスクリプタのリスト(ProcAttr.Files
)を渡すことができます。このリストは、子プロセスのFD 0, 1, 2 (stdin, stdout, stderr) に対応し、その後ろにExtraFiles
で指定されたFDが続きます。 -
runtime.GOOS
: Go言語の組み込み変数で、プログラムがコンパイルまたは実行されているオペレーティングシステムの名前(例: "linux", "darwin", "windows", "netbsd" など)を文字列で返します。これにより、OS固有のコードパスを記述することができます。 -
テストのポータビリティ: ソフトウェアテストにおいて、異なる環境(OS、ハードウェア、設定など)で同じテストが期待通りに動作する能力を指します。特にシステムレベルのテストでは、OSの挙動やリソース管理の違いがテスト結果に影響を与えることがあり、ポータビリティの確保が重要になります。
技術的詳細
TestExtraFilesFDShuffle
テストは、os/exec
パッケージが子プロセスにファイルディスクリプタを正しく引き渡せるか、そしてそれらのFDが子プロセス内で正しくシャッフル(再配置)されるかを検証します。このテストは、親プロセスで複数のファイルディスクリプタを開き、それらを Cmd.ExtraFiles
を通じて子プロセスに渡し、子プロセスがそれらのFDを正しく読み書きできるかを確認します。
問題は、テストが実行される環境によっては、テスト開始時にすでに予期せぬファイルディスクリプタ(例えば、テストランナーやビルドシステムによって開かれたもの)が存在している場合があることでした。これらの「リークした」FDは、テストが期待するFDの番号と衝突したり、テストのロジックを妨害したりして、テストの失敗を引き起こしていました。
このコミットでは、この問題を解決するために以下の変更が行われました。
-
closeUnexpectedFds
関数の導入: この新しいヘルパー関数は、basefds()
で取得される最小のファイルディスクリプタ番号から、特定の最大値(この場合は101)までのすべてのファイルディスクリプタを繰り返し閉じようとします。os.NewFile(fd, "").Close()
を使用してFDを閉じ、エラーが発生しない場合は、そのFDが実際に開かれていたことを示します。これは、テストが開始される前に環境によって開かれた可能性のある予期せぬFDを積極的にクリーンアップするための予防策です。 -
OSごとのテスト挙動の調整:
darwin
(macOS):runtime.GOOS
が "darwin" の場合、closeUnexpectedFds
が呼び出されます。これは、Issue 2603で報告されたmacOS環境でのFDリーク問題への対応です。コメントには「TODO(cnicolaou): http://golang.org/issue/2603 leads to leaked file descriptors in this test when it's run from a builder.」とあり、ビルド環境でのリークが特に問題視されていたことがわかります。netbsd
:runtime.GOOS
が "netbsd" の場合も、closeUnexpectedFds
が呼び出されます。これは、Issue 3955で報告されたNetBSD環境でのテスト失敗への対応です。windows
:runtime.GOOS
が "windows" の場合、t.Skip("no operating system support; skipping")
が呼び出され、テストがスキップされます。これは、WindowsのプロセスモデルがUnix系OSとは異なり、このテストが依存する特定のファイルディスクリプタ操作がサポートされていないためです。
-
TestExtraFiles
のリファクタリング: 既存のTestExtraFiles
テストにも、同様のFDリークをクリーンアップするロジックが含まれていました。このコミットでは、その重複するロジックを新しく導入されたcloseUnexpectedFds
関数に置き換えることで、コードの重複を排除し、保守性を向上させています。
これらの変更により、TestExtraFilesFDShuffle
はより堅牢になり、異なるオペレーティングシステムやテスト環境においても安定して動作するようになりました。
コアとなるコードの変更箇所
--- a/src/pkg/os/exec/exec_test.go
+++ b/src/pkg/os/exec/exec_test.go
@@ -195,8 +195,28 @@ func basefds() uintptr {
return n
}
+func closeUnexpectedFds(t *testing.T, m string) {
+ for fd := basefds(); fd <= 101; fd++ {
+ err := os.NewFile(fd, "").Close()
+ if err == nil {
+ t.Logf("%s: Something already leaked - closed fd %d", m, fd)
+ }
+ }
+}
+
func TestExtraFilesFDShuffle(t *testing.T) {
-\tt.Skip("TODO: TestExtraFilesFDShuffle is too non-portable; skipping")
+\tswitch runtime.GOOS {\n+\tcase "darwin":\n+\t\t// TODO(cnicolaou): http://golang.org/issue/2603\n+\t\t// leads to leaked file descriptors in this test when it\'s\n+\t\t// run from a builder.\n+\t\tcloseUnexpectedFds(t, "TestExtraFilesFDShuffle")\n+\tcase "netbsd":\n+\t\t// http://golang.org/issue/3955\n+\t\tcloseUnexpectedFds(t, "TestExtraFilesFDShuffle")\n+\tcase "windows":\n+\t\tt.Skip("no operating system support; skipping")\n+\t}\n
// syscall.StartProcess maps all the FDs passed to it in
// ProcAttr.Files (the concatenation of stdin,stdout,stderr and
//
@@ -296,12 +316,7 @@ func TestExtraFiles(t *testing.T) {
// our environment.\n \tif !testedAlreadyLeaked {\n \t\ttestedAlreadyLeaked = true\n-\t\tfor fd := basefds(); fd <= 101; fd++ {\n-\t\t\terr := os.NewFile(fd, "").Close()\n-\t\t\tif err == nil {\n-\t\t\t\tt.Logf("Something already leaked - closed fd %d", fd)\n-\t\t\t}\n-\t\t}\n+\t\tcloseUnexpectedFds(t, "TestExtraFiles")\n \t}\n
// Force network usage, to verify the epoll (or whatever) fd
コアとなるコードの解説
-
closeUnexpectedFds
関数の追加:func closeUnexpectedFds(t *testing.T, m string) { for fd := basefds(); fd <= 101; fd++ { err := os.NewFile(fd, "").Close() if err == nil { t.Logf("%s: Something already leaked - closed fd %d", m, fd) } } }
この関数は、
basefds()
が返す最小のFD番号(通常は3、標準入出力エラーの次)から101までの範囲のファイルディスクリプタを順に閉じようとします。os.NewFile(fd, "")
は、指定されたFD番号に対応する*os.File
オブジェクトを作成します。そのClose()
メソッドを呼び出すことで、もしそのFDが実際に開かれていれば閉じられます。エラーがnil
であれば、FDが正常に閉じられたことを意味し、それは予期せぬFDがリークしていたことを示唆します。t.Logf
はテストログにその情報を出力します。 -
TestExtraFilesFDShuffle
の変更: 元のコードではt.Skip("TODO: TestExtraFilesFDShuffle is too non-portable; skipping")
となっており、テスト全体がスキップされていました。このコミットにより、このスキップが削除され、OSごとの条件分岐が追加されました。switch runtime.GOOS { case "darwin": // TODO(cnicolaou): http://golang.org/issue/2603 // leads to leaked file descriptors in this test when it's // run from a builder. closeUnexpectedFds(t, "TestExtraFilesFDShuffle") case "netbsd": // http://golang.org/issue/3955 closeUnexpectedFds(t, "TestExtraFilesFDShuffle") case "windows": t.Skip("no operating system support; skipping") }
darwin
とnetbsd
の場合、前述のcloseUnexpectedFds
関数を呼び出して、テスト開始前に存在する可能性のあるリークしたFDをクリーンアップします。これにより、これらのOSでのテストの安定性が向上します。windows
の場合、このテストが依存する低レベルのFD操作がOSでサポートされていないため、テスト自体をスキップします。
-
TestExtraFiles
の変更:TestExtraFiles
テストにも、同様のFDクリーンアップロジックが手動で記述されていました。// 変更前 // for fd := basefds(); fd <= 101; fd++ { // err := os.NewFile(fd, "").Close() // if err == nil { // t.Logf("Something already leaked - closed fd %d", fd) // } // } // 変更後 closeUnexpectedFds(t, "TestExtraFiles")
この変更により、重複するコードが
closeUnexpectedFds
関数への呼び出しに置き換えられ、コードの簡潔さと再利用性が向上しました。
これらの変更は、Goのテストスイートの堅牢性を高め、異なるプラットフォームでのCI/CDパイプラインの信頼性を向上させる上で重要な役割を果たします。
関連リンク
- Go CL (Code Review): https://golang.org/cl/9103045
- Issue 2603:
os/exec: TestExtraFilesFDShuffle leaks file descriptors on darwin
- https://golang.org/issue/2603 - Issue 3955:
os/exec: TestExtraFilesFDShuffle fails on netbsd
- https://golang.org/issue/3955
参考にした情報源リンク
- Go言語の公式ドキュメント (
os/exec
パッケージ): https://pkg.go.dev/os/exec - Go言語の公式ドキュメント (
os
パッケージ): https://pkg.go.dev/os - Go言語の公式ドキュメント (
runtime
パッケージ): https://pkg.go.dev/runtime - Unix系OSにおけるファイルディスクリプタに関する一般的な情報源 (例: Wikipedia, Linux man pages)
- Go Issue Tracker (上記Issueの参照)