[インデックス 10729] ファイルの概要
このコミットは、Go言語の標準ライブラリtime
パッケージ内のテストコードsleep_test.go
に対する変更です。具体的には、time.Sleep
やtime.After
といった時間関連の関数が、非常に低速な仮想マシン(VM)環境下でもテストが失敗しないように、許容される時間のずれ(スロップ)を拡大する修正が行われています。
コミット
commit fc7b9fc26990e3a480f816e7c34d981488340c0c
Author: Russ Cox <rsc@golang.org>
Date: Mon Dec 12 18:33:47 2011 -0500
time: allow sleep tests to run for 200% too long
Some VMs are slow. Very slow.
Fixes #2421.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5482049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fc7b9fc26990e3a480f816e7c34d981488340c0c
元コミット内容
このコミットは、time
パッケージのテストにおいて、time.Sleep
やtime.After
などの時間計測が、想定よりも大幅に遅延した場合でもテストが失敗しないように、許容範囲を広げるものです。特に「一部のVMは非常に遅い」という問題に対処しています。
変更の背景
この変更の背景には、Go言語のテストスイートが様々な実行環境、特に仮想マシン(VM)上で実行される際に、時間関連のテストが不安定になるという問題がありました。time.Sleep
やtime.After
といった関数は、指定された期間だけ処理を停止したり、指定された期間後にイベントを発生させたりするものです。これらの関数の正確性は、テストによって検証されますが、VM環境ではホストOSのスケジューリング、VMのオーバーヘッド、I/Oの遅延など、様々な要因によって時間の精度が低下することがあります。
コミットメッセージにある「Some VMs are slow. Very slow.」という記述は、特定のVM環境下で、これらの時間関連の操作が期待される時間よりも大幅に長くかかることがあり、その結果、テストが「時間切れ」や「期待される時間内に完了しない」といった理由で失敗していたことを示唆しています。Fixes #2421
という記述がありますが、このIssueは公開されているGoリポジトリでは見つかりませんでした。これは、内部のIssueトラッカーの番号であるか、非常に古いIssueである可能性があります。いずれにせよ、このコミットは、テストの信頼性を向上させ、不安定なVM環境下でもテストがパスするようにするための実用的な調整です。
前提知識の解説
Go言語のtime
パッケージ
Go言語の標準ライブラリtime
パッケージは、時間の測定、表示、および操作のための機能を提供します。主要な機能には以下のようなものがあります。
time.Duration
: 時間の長さを表す型。ナノ秒単位で表現され、time.Second
,time.Minute
などの定数を使って人間が読みやすい形で時間を指定できます。time.Now()
: 現在の時刻をtime.Time
型で返します。time.Sleep(d Duration)
: 指定された期間d
だけ現在のゴルーチンをスリープさせます。time.After(d Duration)
: 指定された期間d
が経過した後に、現在の時刻を送信するチャネルを返します。これは非同期的な時間イベントの待機によく使われます。time.Tick(d Duration)
: 指定された期間d
ごとに現在の時刻を送信するチャネルを返します。定期的な処理に利用されます。time.Time.Sub(t Time)
: 2つのtime.Time
間の差をtime.Duration
として計算します。
テストにおける「スロップ(Slop)」
ソフトウェアテストにおいて、「スロップ」とは、許容される誤差範囲やずれを指します。特に時間に関連するテストでは、厳密な時間精度を期待することは難しいため、ある程度の誤差を許容する必要があります。例えば、「1秒スリープする」というテストでは、厳密に1.000000000秒でなくても、1.000000000秒±ε(イプシロン)の範囲内であれば成功とみなす、といった具合です。このεがスロップにあたります。
このコミットでは、このスロップの値を拡大することで、テストがより多くの環境でパスするように調整しています。
仮想マシン(VM)の特性
仮想マシンは、物理ハードウェア上にソフトウェア的に構築されたコンピュータシステムです。VMはホストOSのリソース(CPU、メモリ、I/Oなど)を共有して動作するため、以下のような特性があります。
- オーバーヘッド: VMのハイパーバイザーやゲストOS自体がリソースを消費するため、物理マシンに比べてパフォーマンスが低下する可能性があります。
- スケジューリングの不確実性: ホストOSが複数のVMや他のプロセスをスケジューリングするため、特定のVMに割り当てられるCPU時間やI/O処理のタイミングが不確実になることがあります。
- クロックのずれ: VM内のシステムクロックがホストOSのクロックと完全に同期しない場合があり、時間の測定にずれが生じることがあります。
これらの特性により、VM環境では時間関連の操作が物理マシンよりも遅延したり、不安定になったりすることがよくあります。
技術的詳細
このコミットは、src/pkg/time/sleep_test.go
ファイル内の2つのテスト関数TestAfterTick
とtestAfterQueuing
における時間計測の許容範囲を変更しています。
TestAfterTick
関数における変更
このテストは、time.Tick
関数が指定された間隔で正確に動作するかを検証します。元のコードでは、Delta * Count
で計算される期待される合計時間target
に対して、target * 2 / 10
(つまりtarget
の20%)のスロップを許容していました。
変更後、このスロップの計算が以下のように変わりました。
- 下限:
target * 9 / 10
(期待時間の90%) - 上限:
target * 30 / 10
(期待時間の300%、つまり3倍)
これにより、テストが完了するまでの時間が期待値の90%から300%の範囲内であれば、テストは成功とみなされます。特に上限がtarget * 2 / 10
からtarget * 30 / 10
へと大幅に拡大されており、これは「200% too long」(200%長すぎても許容)というコミットメッセージの意図と一致します。
testAfterQueuing
関数における変更
このテストは、time.After
関数が複数のイベントをキューイングした場合に、それぞれのイベントが正確なタイミングで発生するかを検証します。元のコードでは、Delta / 4
(Delta
の25%)のスロップを許容していました。
変更後、このスロップの計算が以下のように変わりました。
- 下限:
target - Delta / 2
(期待時間からDelta
の50%を引いた値) - 上限:
target + Delta * 10
(期待時間にDelta
の10倍を加えた値)
これにより、イベントの発生時間が期待値からDelta
の50%手前から、Delta
の10倍遅れてもテストは成功とみなされます。こちらも上限が大幅に拡大されており、特に遅延に対する許容度が高められています。
これらの変更は、テストのロジック自体を変更するものではなく、テストが成功と判断するための閾値を調整するものです。これにより、低速なVM環境下での時間的なずれが原因でテストが不必要に失敗するのを防ぎ、テストの安定性を向上させています。
コアとなるコードの変更箇所
--- a/src/pkg/time/sleep_test.go
+++ b/src/pkg/time/sleep_test.go
@@ -119,8 +119,7 @@ func TestAfterTick(t *testing.T) {
t1 := Now()
d := t1.Sub(t0)
target := Delta * Count
- slop := target * 2 / 10
- if d < target-slop || d > target+slop {
+ if d < target*9/10 || d > target*30/10 {
t.Fatalf("%d ticks of %s took %s, expected %s", Count, Delta, d, target)
}
}
@@ -197,9 +196,8 @@ func testAfterQueuing(t *testing.T) error {
}
dt := r.t.Sub(t0)
target := Duration(slot) * Delta
- slop := Delta / 4
- if dt < target-slop || dt > target+slop {
- return fmt.Errorf("After(%s) arrived at %s, expected [%s,%s]", target, dt, target-slop, target+slop)
+ if dt < target-Delta/2 || dt > target+Delta*10 {
+ return fmt.Errorf("After(%s) arrived at %s, expected [%s,%s]", target, dt, target-Delta/2, target+Delta*10)
}
}
return nil
コアとなるコードの解説
TestAfterTick
関数内の変更
元のコード:
slop := target * 2 / 10
if d < target-slop || d > target+slop {
変更後:
if d < target*9/10 || d > target*30/10 {
この変更では、slop
変数を削除し、許容範囲の計算を直接if
文の中に埋め込んでいます。
d < target*9/10
: 実際の経過時間d
が、期待される合計時間target
の90%未満である場合にテストが失敗します。これは、テストが早すぎる場合に失敗する条件です。d > target*30/10
: 実際の経過時間d
が、期待される合計時間target
の300%(3倍)を超える場合にテストが失敗します。これは、テストが遅すぎる場合に失敗する条件です。
これにより、テストの実行時間が期待値の90%から300%の範囲内であれば、テストは成功とみなされます。
testAfterQueuing
関数内の変更
元のコード:
slop := Delta / 4
if dt < target-slop || dt > target+slop {
return fmt.Errorf("After(%s) arrived at %s, expected [%s,%s]", target, dt, target-slop, target+slop)
変更後:
if dt < target-Delta/2 || dt > target+Delta*10 {
return fmt.Errorf("After(%s) arrived at %s, expected [%s,%s]", target, dt, target-Delta/2, target+Delta*10)
同様に、slop
変数を削除し、許容範囲の計算を直接if
文の中に埋め込んでいます。
dt < target-Delta/2
: 個々のイベントの実際の経過時間dt
が、期待される時間target
からDelta
の半分(50%)を引いた値よりも小さい場合にテストが失敗します。dt > target+Delta*10
: 個々のイベントの実際の経過時間dt
が、期待される時間target
にDelta
の10倍を加えた値よりも大きい場合にテストが失敗します。
これにより、個々のイベントの発生時間が、期待値からDelta
の50%手前から、Delta
の10倍遅れてもテストは成功とみなされます。エラーメッセージも新しい許容範囲に合わせて更新されています。
これらの変更は、テストの堅牢性を高め、特に時間精度が不安定な環境(低速なVMなど)での誤ったテスト失敗を防ぐことを目的としています。
関連リンク
- Go言語
time
パッケージのドキュメント: https://pkg.go.dev/time - Go言語のテストに関するドキュメント: https://go.dev/doc/code#testing
参考にした情報源リンク
- コミット情報:
/home/orange/Project/comemo/commit_data/10729.txt
- GitHubコミットページ: https://github.com/golang/go/commit/fc7b9fc26990e3a480f816e7c34d981488340c0c
- Go言語の公式ドキュメント (timeパッケージ、テストに関する情報)
- 仮想マシンに関する一般的な知識
- ソフトウェアテストにおけるスロップの概念