[インデックス 12335] ファイルの概要
このコミットは、Go言語の標準ライブラリnet
パッケージ内のテストコードに対する修正です。具体的には、ネットワークタイムアウトのテストにおいて、testing.Short()
フラグが有効な(つまり、短時間で実行されるべき)テストの場合に、期待よりも長いタイムアウトを許容しないようにする変更が加えられています。これにより、テストの実行効率が向上し、不要な待機時間が削減されます。
コミット
- コミットハッシュ:
a3caf073a584a17eb0a7a17e1ce2ec014bae9a5c
- Author: Shenghou Ma minux.ma@gmail.com
- Date: Sat Mar 3 00:50:18 2012 +0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a3caf073a584a17eb0a7a17e1ce2ec014bae9a5c
元コミット内容
net: during short test, don't bother timeout longer than expected
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5716053
変更の背景
この変更の背景には、Go言語のテストフレームワークにおけるtesting.Short()
フラグの利用と、ネットワーク関連のテストにおけるタイムアウト処理の効率化があります。
Goのテストでは、go test -short
コマンドを使用することで、実行時間の短いテストのみを実行するモードに切り替えることができます。これは、CI/CDパイプラインでの高速なフィードバックや、開発者がローカルで素早くテストを実行したい場合に非常に有用です。
src/pkg/net/timeout_test.go
は、ネットワーク操作におけるタイムアウトが正しく機能するかを検証するためのテストファイルです。これらのテストは、意図的に一定時間(例: 0.1秒)のタイムアウトを発生させ、その時間が期待通りであることを確認します。
しかし、testing.Short()
モードで実行される場合、テストの目的は「タイムアウトが機能すること」の基本的な確認であり、「タイムアウトの正確な時間」を厳密に検証することではありません。元のコードでは、dt > 250*time.Millisecond
という条件で、タイムアウト時間が期待値(0.1秒)よりも大幅に長い場合にエラーとしていました。これは通常モードでは問題ありませんが、testing.Short()
モードでは、システム負荷や環境要因によってわずかにタイムアウト時間が長引いたとしても、それがテストの失敗につながる可能性があります。
このコミットは、testing.Short()
モードの場合には、タイムアウトが期待よりも長かったとしても、それをエラーとしないようにすることで、テストの安定性を向上させ、不要なテスト失敗を避けることを目的としています。これにより、go test -short
の実行がより信頼性の高いものになります。
前提知識の解説
Go言語のtesting
パッケージとtesting.Short()
Go言語には、標準で強力なテストフレームワークが組み込まれています。testing
パッケージは、ユニットテスト、ベンチマークテスト、例(Example)テストなどを記述するための機能を提供します。
- テスト関数:
func TestXxx(t *testing.T)
という形式で定義され、go test
コマンドで実行されます。 *testing.T
: テストの状態を管理し、テストの失敗を報告したり、ログを出力したりするためのメソッドを提供します。testing.Short()
: この関数は、go test -short
コマンドが指定された場合にtrue
を返します。開発者はこのフラグを利用して、テストの実行時間を短縮するためのロジックをテストコード内に組み込むことができます。例えば、時間のかかるネットワークテストやファイルI/Oテストをtesting.Short()
がtrue
の場合はスキップする、あるいは簡略化するといった用途で使われます。
Go言語のnet
パッケージとネットワークタイムアウト
net
パッケージは、TCP/IP、UDP、IP、UnixドメインソケットなどのネットワークI/Oプリミティブを提供します。ネットワークアプリケーションを構築する上で非常に重要なパッケージです。
- ネットワーク接続:
net.Dial
などの関数を使用して、リモートホストへの接続を確立します。 - タイムアウト: ネットワーク操作(接続、読み込み、書き込みなど)は、相手の応答がない場合に無限に待機してしまう可能性があります。これを防ぐために、タイムアウトを設定することが一般的です。Goの
net
パッケージでは、SetReadDeadline
やSetWriteDeadline
などのメソッドを使用して、特定の時間内に操作が完了しない場合にエラーを返すように設定できます。 time.Duration
: Goのtime
パッケージで提供される型で、時間の長さを表します。ミリ秒(time.Millisecond
)や秒(time.Second
)などの単位で時間の長さを表現できます。
t1.Sub(t0)
と時間の計測
Go言語のtime
パッケージには、時間の計測に便利な機能が多数あります。
time.Now()
: 現在の時刻をtime.Time
型で返します。time.Time.Sub(u time.Time)
:time.Time
型のメソッドで、別のtime.Time
型の値u
との差をtime.Duration
型で返します。このコミットでは、t1.Sub(t0)
によって、ネットワーク操作にかかった実際の時間(dt
)を計測しています。
技術的詳細
このコミットの技術的な核心は、testing.Short()
フラグを利用して、テストの振る舞いを条件分岐させている点にあります。
元のコードでは、ネットワーク操作にかかった時間dt
が50*time.Millisecond
未満であるか、または250*time.Millisecond
を超えている場合にエラーとしていました。これは、タイムアウトが約0.1秒(100ミリ秒)であることを期待しているため、その前後で許容範囲を設けていると考えられます。
// 変更前
if dt := t1.Sub(t0); dt < 50*time.Millisecond || dt > 250*time.Millisecond {
errc <- fmt.Errorf("fd.%s on %s %s took %s, expected 0.1s", what, network, addr, dt)
return
}
変更後のコードでは、dt > 250*time.Millisecond
の条件に!testing.Short()
が追加されています。
// 変更後
if dt := t1.Sub(t0); dt < 50*time.Millisecond || !testing.Short() && dt > 250*time.Millisecond {
errc <- fmt.Errorf("fd.%s on %s %s took %s, expected 0.1s", what, network, addr, dt)
return
}
この論理式を分解すると以下のようになります。
dt < 50*time.Millisecond
: これは変更されていません。タイムアウトが期待よりも短すぎる場合は、常にエラーとなります。これは、タイムアウトが全く機能していない可能性を示唆するため、short
モードであっても重要なチェックです。!testing.Short() && dt > 250*time.Millisecond
:!testing.Short()
:go test -short
が指定されていない場合(つまり、フルテストモードの場合)にtrue
となります。dt > 250*time.Millisecond
: タイムアウトが期待よりも長すぎる場合です。
したがって、この条件全体は「go test -short
が指定されておらず、かつタイムアウトが250ミリ秒を超えている場合」にtrue
となります。
これにより、以下の挙動が実現されます。
- 通常モード (
go test
):!testing.Short()
がtrue
になるため、dt < 50*time.Millisecond || dt > 250*time.Millisecond
という元の条件がそのまま適用されます。つまり、タイムアウトが短すぎても長すぎてもエラーになります。 - ショートモード (
go test -short
):!testing.Short()
がfalse
になるため、!testing.Short() && dt > 250*time.Millisecond
の部分全体がfalse
になります。結果として、dt < 50*time.Millisecond
の条件のみが評価されます。つまり、タイムアウトが短すぎる場合のみエラーとなり、長すぎる場合はエラーとはなりません。
この変更は、testing.Short()
の意図、すなわち「高速なテスト実行」を尊重しつつ、テストの基本的な健全性チェック(タイムアウトが全く発生しないなど)は維持するという、バランスの取れたアプローチを示しています。
コアとなるコードの変更箇所
--- a/src/pkg/net/timeout_test.go
+++ b/src/pkg/net/timeout_test.go
@@ -40,7 +40,7 @@ func testTimeout(t *testing.T, network, addr string, readFrom bool) {
errc <- fmt.Errorf("fd.%s on %s %s did not return 0, timeout: %v, %v", what, network, addr, n, err1)
return
}
- if dt := t1.Sub(t0); dt < 50*time.Millisecond || dt > 250*time.Millisecond {
+ if dt := t1.Sub(t0); dt < 50*time.Millisecond || !testing.Short() && dt > 250*time.Millisecond {
errc <- fmt.Errorf("fd.%s on %s %s took %s, expected 0.1s", what, network, addr, dt)
return
}
コアとなるコードの解説
変更はsrc/pkg/net/timeout_test.go
ファイルの1行のみです。
元の行:
if dt := t1.Sub(t0); dt < 50*time.Millisecond || dt > 250*time.Millisecond {
変更後の行:
if dt := t1.Sub(t0); dt < 50*time.Millisecond || !testing.Short() && dt > 250*time.Millisecond {
この変更は、dt > 250*time.Millisecond
という条件の前に!testing.Short() &&
を追加しています。
dt := t1.Sub(t0)
: ネットワーク操作にかかった時間(dt
)を計測し、その場でdt
変数に代入しています。dt < 50*time.Millisecond
: タイムアウト時間が50ミリ秒未満の場合。これは、タイムアウトが期待よりも早く発生した、あるいは全く発生しなかった可能性を示唆します。この条件はtesting.Short()
の有無にかかわらず常に評価されます。||
: 論理OR演算子。左辺または右辺のどちらかがtrue
であれば、全体がtrue
になります。!testing.Short()
:testing.Short()
がfalse
の場合(つまり、go test -short
が指定されていない場合)にtrue
となります。&&
: 論理AND演算子。左辺と右辺の両方がtrue
の場合に全体がtrue
になります。dt > 250*time.Millisecond
: タイムアウト時間が250ミリ秒を超過した場合。これは、タイムアウトが期待よりも遅く発生したことを示します。
この修正により、go test -short
実行時には、タイムアウトが250ミリ秒を超過してもテストは失敗しなくなります。これにより、テスト環境のわずかな変動による誤った失敗が減り、ショートテストの信頼性と実用性が向上します。
関連リンク
- Go言語の
testing
パッケージに関する公式ドキュメント: https://pkg.go.dev/testing - Go言語の
net
パッケージに関する公式ドキュメント: https://pkg.go.dev/net - Go言語の
time
パッケージに関する公式ドキュメント: https://pkg.go.dev/time
参考にした情報源リンク
- Go言語の公式ドキュメント (上記リンク)
- Go言語のテストに関する一般的な情報源 (例: Go by Example: Testing, A Tour of Go)
testing.Short()
の利用例に関するGoコミュニティの議論やブログ記事 (具体的なURLは検索結果によるため省略)- Goのソースコードリポジトリ (GitHub)
- Goのコードレビューシステム (Gerrit) のCL (Change List) 5716053: https://golang.org/cl/5716053 (コミットメッセージに記載されているリンク)