[インデックス 11827] ファイルの概要
このコミットは、Go言語のコマンドラインツール cmd/go
におけるテスト実行時のタイムアウト処理に関する改善です。具体的には、go test
コマンドに渡される -timeout
フラグの値を cmd/go
自身が適切に解釈し、テスト全体の実行時間に対する内部的な「キルタイムアウト」を調整するように変更されています。これにより、テストが予期せずハングアップした場合でも、より正確な時間で強制終了できるようになります。
コミット
commit d318ab22647205c4d6d3aa32bb027a434f7b16d3
Author: Russ Cox <rsc@golang.org>
Date: Sun Feb 12 23:19:24 2012 -0500
cmd/go: respect test -timeout flag
I thought that -timeout was per-test, but it is for the
whole program execution, so cmd/go can adjust its timer
(also for whole program execution) accordingly.
Fixes #2993.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5650070
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d318ab22647205c4d6d3aa32bb027a434f7b16d3
元コミット内容
cmd/go: respect test -timeout flag
I thought that -timeout was per-test, but it is for the
whole program execution, so cmd/go can adjust its timer
(also for whole program execution) accordingly.
Fixes #2993.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5650070
変更の背景
この変更の背景には、go test
コマンドの -timeout
フラグの挙動に関する誤解と、それによって発生する可能性のある問題がありました。コミットメッセージによると、当初開発者は -timeout
フラグが個々のテスト関数に対して適用されるものと考えていたようです。しかし、実際にはこのフラグは go test
プロセス全体の実行時間に対して適用されるものでした。
この認識のずれが問題となるのは、cmd/go
自身がテストバイナリのハングアップを検出するために内部的なタイムアウト(「キルタイムアウト」)を設定している点です。もしユーザーが -timeout
フラグで短い時間を指定しているにもかかわらず、cmd/go
の内部タイムアウトがそれよりもはるかに長い時間(例えばデフォルトの10分)に設定されている場合、ユーザーが期待するよりもテストプロセスが長く実行され続ける可能性がありました。特に、テストが無限ループに陥ったり、デッドロックしたりした場合、ユーザーが指定したタイムアウトで終了せず、ビルドシステムやCI/CDパイプラインをブロックしてしまう恐れがありました。
このコミットは、この問題を解決するために、cmd/go
がユーザーが指定した -timeout
フラグの値を読み取り、その値に基づいて自身の内部的なキルタイムアウトを調整するように修正しています。これにより、ユーザーの意図がより正確に反映され、テストの信頼性と効率が向上します。コミットメッセージにある Fixes #2993
は、この問題がGoプロジェクトのイシュートラッカー(またはGerritの変更リスト)で報告されていたことを示しています。
前提知識の解説
go test
コマンド
go test
はGo言語の標準的なテスト実行ツールです。Goのテストは、_test.go
で終わるファイルに記述されたテスト関数(TestXxx
)、ベンチマーク関数(BenchmarkXxx
)、および例(ExampleXxx
)を実行します。
go test -timeout
フラグ
-timeout duration
フラグは、テストバイナリ全体の実行時間に対するタイムアウトを設定します。指定された duration
(例: 10s
, 1m
, 1h30m
)を超えてテストが実行された場合、go test
プロセスは強制終了されます。このフラグは、テストが無限ループに陥ったり、外部リソースの応答待ちでハングアップしたりするのを防ぐために重要です。
time.ParseDuration
関数
Go言語の time
パッケージに含まれる time.ParseDuration(s string) (Duration, error)
関数は、文字列で表現された期間(例: "300ms", "1.5h", "2h45m")を time.Duration
型の値に変換します。この関数は、ユーザーからの入力や設定ファイルから時間情報を読み取る際に非常に便利です。
テストの「キルタイムアウト」
テストの「キルタイムアウト」とは、テストプロセスが予期せずハングアップした場合に、それを強制的に終了させるための最終的なデッドラインを指します。go test
コマンド自体がテストバイナリを起動し、その実行を監視しています。テストバイナリが応答しなくなった場合や、指定されたタイムアウトを超過した場合に、go test
はテストバイナリを強制終了する責任があります。この「キルタイムアウト」は、ビルドシステムやCI/CD環境において、テストが無限に実行され続けることを防ぎ、リソースの枯渇やパイプラインの停止を回避するために不可欠な安全機構です。
技術的詳細
このコミットの技術的な核心は、cmd/go
がユーザー指定の -timeout
フラグの値を読み取り、その値に基づいて自身の内部的なテストキルタイムアウト testKillTimeout
を動的に調整する点にあります。
変更前は、testKillTimeout
は 10 * time.Minute
という固定値でハードコードされていました。これは、ユーザーが -timeout
で短い時間を指定しても、cmd/go
自身は最大10分間テストの終了を待つ可能性があることを意味していました。
変更後、src/cmd/go/test.go
の runTest
関数内で、testTimeout
変数(-timeout
フラグの値が格納される)が time.ParseDuration
を使って解析されます。
// If a test timeout was given and is parseable, set our kill timeout
// to that timeout plus one minute. This is a backup alarm in case
// the test wedges with a goroutine spinning and its background
// timer does not get a chance to fire.
if dt, err := time.ParseDuration(testTimeout); err == nil {
testKillTimeout = dt + 1*time.Minute
}
このコードスニペットは以下のロジックを実装しています。
testTimeout
(文字列) をtime.Duration
型に変換しようと試みます。- 変換が成功した場合(
err == nil
)、testKillTimeout
の値をdt + 1*time.Minute
に設定します。ここでdt
はユーザーが指定したタイムアウト期間です。 + 1*time.Minute
という加算は、ユーザーが指定したタイムアウトに加えて1分間の猶予期間を設けることを意味します。これは、コミットメッセージにもあるように、「テストがゴルーチンをスピンさせてウェッジし、そのバックグラウンドタイマーが発火する機会を得られない場合のバックアップアラーム」として機能します。つまり、テストバイナリ自身のタイムアウト機構が何らかの理由で機能しなかった場合でも、cmd/go
が少し遅れて確実にテストを強制終了するためのセーフティネットです。
この調整により、cmd/go
の内部的なキルタイムアウトがユーザーの意図により近づき、テストのハングアップに対する応答性が向上します。
また、src/cmd/go/testflag.go
では、-timeout
フラグの値を testTimeout
変数に適切に格納するための変更が加えられています。
case "timeout":
testTimeout = value
これにより、go test
コマンドに -timeout
フラグが渡された際に、その値が cmd/go
の内部で利用可能になります。
コアとなるコードの変更箇所
diff --git a/src/cmd/go/test.go b/src/cmd/go/test.go
index bc2af619c2..1633244556 100644
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -198,10 +198,13 @@ var (
testX bool // -x flag
testV bool // -v flag
testFiles []string // -file flag(s) TODO: not respected
+ testTimeout string // -timeout flag
testArgs []string
testBench bool
testStreamOutput bool // show output as it is generated
testShowPass bool // show passing output
+
+ testKillTimeout = 10 * time.Minute
)
func runTest(cmd *Command, args []string) {
@@ -217,6 +220,14 @@ func runTest(cmd *Command, args []string) {
fatalf("cannot use -c flag with multiple packages")
}
+ // If a test timeout was given and is parseable, set our kill timeout
+ // to that timeout plus one minute. This is a backup alarm in case
+ // the test wedges with a goroutine spinning and its background
+ // timer does not get a chance to fire.
+ if dt, err := time.ParseDuration(testTimeout); err == nil {
+ testKillTimeout = dt + 1*time.Minute
+ }
+
// show passing test output (after buffering) with -v flag.
// must buffer because tests are running in parallel, and
// otherwise the output will get mixed.
@@ -540,9 +551,7 @@ func (b *builder) runTest(a *action) error {
// This is a last-ditch deadline to detect and
// stop wedged test binaries, to keep the builders
// running.
- const deadline = 10 * time.Minute
-
- tick := time.NewTimer(deadline)
+ tick := time.NewTimer(testKillTimeout)
if err == nil {
done := make(chan error)
go func() {
diff --git a/src/cmd/go/testflag.go b/src/cmd/go/testflag.go
index 8913b9b504..7c9b7f16dd 100644
--- a/src/cmd/go/testflag.go
+++ b/src/cmd/go/testflag.go
@@ -133,6 +133,8 @@ func testFlags(args []string) (packageNames, passToTest []string) {
case "bench":
// record that we saw the flag; don't care about the value
testBench = true
+ case "timeout":
+ testTimeout = value
}
if extraWord {
i++
コアとなるコードの解説
src/cmd/go/test.go
の変更
-
testTimeout
変数の追加:var
ブロックにtestTimeout string // -timeout flag
が追加されました。これは、go test
コマンドに渡された-timeout
フラグの値を文字列として保持するための変数です。 -
testKillTimeout
の初期化:testKillTimeout = 10 * time.Minute
が追加されました。これは、cmd/go
がテストバイナリを強制終了する際のデフォルトのタイムアウトを10分に設定しています。この値は、ユーザーが-timeout
フラグを指定しない場合に適用されます。 -
runTest
関数内のタイムアウト調整ロジック:runTest
関数内に以下のコードブロックが追加されました。// If a test timeout was given and is parseable, set our kill timeout // to that timeout plus one minute. This is a backup alarm in case // the test wedges with a goroutine spinning and its background // timer does not get a chance to fire. if dt, err := time.ParseDuration(testTimeout); err == nil { testKillTimeout = dt + 1*time.Minute }
この部分が、ユーザーが指定した
-timeout
の値を解析し、testKillTimeout
を動的に設定する主要なロジックです。time.ParseDuration
で文字列のタイムアウト値をtime.Duration
型に変換し、成功すればその値に1分を加算してtestKillTimeout
に代入します。これにより、cmd/go
の内部タイムアウトがユーザーの意図に沿って調整されます。 -
time.NewTimer
の引数変更:func (b *builder) runTest(a *action) error
内で、time.NewTimer
の引数がconst deadline = 10 * time.Minute
からtestKillTimeout
に変更されました。- const deadline = 10 * time.Minute - - tick := time.NewTimer(deadline) + tick := time.NewTimer(testKillTimeout)
これにより、テストの監視タイマーが、固定値ではなく、ユーザーの指定またはデフォルト値に基づいて動的に設定された
testKillTimeout
の値を使用するようになります。
src/cmd/go/testflag.go
の変更
-timeout
フラグの処理追加:testFlags
関数内のswitch
ステートメントにcase "timeout":
が追加されました。
この変更により、case "timeout": testTimeout = value
go test
コマンドの引数を解析する際に-timeout
フラグが認識され、その値(value
)がtestTimeout
グローバル変数に格納されるようになります。これにより、test.go
でその値を利用できるようになります。
これらの変更により、cmd/go
は go test -timeout
フラグの意図を正確に反映し、テストの実行管理をより堅牢に行えるようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/d318ab22647205c4d6d3aa32bb027a434f7b16d3
- 関連するGo Gerrit Change-ID (CL):
https://golang.org/cl/5650070
- 注: このCLリンクは、Goプロジェクトがかつて使用していたGerritコードレビューシステムへの古い参照である可能性があり、現在の
go.dev/cl
の形式とは異なります。直接アクセスできない場合がありますが、コミットメッセージに記載されている元の参照としてここに含めます。Fixes #2993
は、Goプロジェクトの内部的なイシュートラッカーまたはGerritの変更リスト番号を指していると考えられます。
- 注: このCLリンクは、Goプロジェクトがかつて使用していたGerritコードレビューシステムへの古い参照である可能性があり、現在の
参考にした情報源リンク
- Go言語の公式ドキュメント (
go test
コマンド、time
パッケージなど) - Go言語のソースコード (特に
src/cmd/go/test.go
およびsrc/cmd/go/testflag.go
) - GoプロジェクトのGerritシステムに関する一般的な情報 (GoプロジェクトはGitHubのIssueトラッカーと並行してGerritをコードレビューに利用しています)
- Web検索による
golang/go issue 2993
およびgolang.org/cl/5650070
の調査結果 (これらの検索結果は、#2993
がGitHubのIssueではなくGerritのCLである可能性が高いこと、および古いCLリンクが直接アクセスできない場合があることを示唆しています。)