[インデックス 17233] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet
パッケージ内のテストファイルsrc/pkg/net/timeout_test.go
に対する変更です。具体的には、TestDeadlineRace
というテスト関数の実行時間を短縮することを目的としています。
コミット
commit 9bbf1e1b725e12e207e6791da9d84f1f647df1d6
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed Aug 14 21:20:11 2013 +0400
net: make TestDeadlineRace shorter
1. Do less iterations in short mode
2. Bound number of times SetDeadline is executed
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/12937043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9bbf1e1b725e12e207e6791da9d84f1f647df1d6
元コミット内容
このコミットは、TestDeadlineRace
というテストの実行時間を短縮することを目的としています。具体的には、以下の2つの変更が行われました。
testing.Short()
モードの場合に、テストのイテレーション回数を減らす。SetDeadline
が実行される回数に上限を設ける。
変更の背景
TestDeadlineRace
は、ネットワーク接続のデッドライン(タイムアウト)設定と、そのデッドラインに達した際の読み取り操作が同時に行われる際の競合状態(レースコンディション)をテストするためのものです。このような競合状態のテストは、再現性が低く、多くのイテレーションを必要とすることが多いため、実行時間が長くなりがちです。
テストスイート全体の実行時間を最適化することは、開発サイクルにおいて非常に重要です。特に、継続的インテグレーション(CI)システムや開発者のローカル環境でのテスト実行が遅いと、フィードバックループが長くなり、生産性が低下します。このコミットは、テストの網羅性を維持しつつ、実行時間を短縮することで、開発効率の向上に貢献することを目的としています。
前提知識の解説
Go言語のnet
パッケージとデッドライン
Go言語のnet
パッケージは、ネットワークI/Oのプリミティブを提供します。TCP/UDP接続やUnixドメインソケットなど、様々なネットワーク通信を扱うことができます。
net.Conn
インターフェースは、ネットワーク接続を表し、以下のメソッドを含みます。
Read(b []byte) (n int, err error)
: データを読み取ります。Write(b []byte) (n int, err error)
: データを書き込みます。SetDeadline(t time.Time) error
: 読み取りと書き込みの両方に適用される絶対的なタイムアウトを設定します。指定された時刻までにI/O操作が完了しない場合、操作はタイムアウトエラーを返します。SetReadDeadline(t time.Time) error
: 読み取り操作のみに適用される絶対的なタイムアウトを設定します。SetWriteDeadline(t time.Time) error
: 書き込み操作のみに適用される絶対的なタイムアウトを設定します。
これらのデッドライン設定は、ネットワークの応答がない場合や、悪意のあるピアからの攻撃などによってI/O操作がブロックされ続けることを防ぐために重要です。
レースコンディション(競合状態)
レースコンディションとは、複数のゴルーチン(またはスレッド)が共有リソースに同時にアクセスし、そのアクセス順序によって結果が変わってしまうような状況を指します。ネットワーク接続のデッドライン設定は、通常、接続オブジェクトの内部状態を変更するため、複数のゴルーチンが同時にSetDeadline
を呼び出したり、SetDeadline
とRead
/Write
を同時に呼び出したりすると、予期せぬ動作やバグ(例えば、デッドラインが正しく適用されない、I/O操作が誤ってタイムアウトする/しないなど)が発生する可能性があります。TestDeadlineRace
は、このような潜在的な問題を検出するために設計されたテストです。
testing.Short()
Go言語の標準テストパッケージtesting
には、Short()
という関数があります。これは、go test -short
コマンドでテストを実行した場合にtrue
を返します。この機能は、開発者がテストスイート全体を素早く実行したい場合に、時間のかかるテスト(例えば、ネットワークI/Oを伴うテスト、大量の計算を行うテスト、外部リソースに依存するテストなど)の一部をスキップしたり、イテレーション回数を減らしたりするために使用されます。これにより、CI/CDパイプラインの高速化や、ローカル開発時の生産性向上が図られます。
技術的詳細
このコミットは、TestDeadlineRace
テストの実行ロジックを変更することで、その実行時間を短縮しています。
変更前は、SetDeadline
を呼び出すゴルーチンは無限ループfor {}
で実行され、c.Read(buf[:])
を呼び出すメインのゴルーチンは1024回イテレーションしていました。これは、競合状態を十分に引き起こすために多くの試行回数を必要とするためですが、テストの実行時間が長くなる原因でもありました。
変更後は、N
という変数が導入されました。このN
はデフォルトで1000
に設定されていますが、testing.Short()
がtrue
(つまり、go test -short
で実行された場合)の際には50
に設定されます。
SetDeadline
の実行回数の制限:SetDeadline
を呼び出すゴルーチン内の無限ループfor {}
が、for i := 0; i < N; i++
に変更されました。これにより、SetDeadline
の呼び出し回数がN
回に制限されます。go test -short
モードでは、この回数が50回に大幅に削減されます。Read
操作のイテレーション回数の制限: メインのゴルーチンで行われるc.Read(buf[:])
のループも、for i := 0; i < 1024; i++
からfor i := 0; i < N; i++
に変更されました。これにより、Read
操作のイテレーション回数もN
回に制限され、go test -short
モードでは50回になります。
これらの変更により、go test -short
コマンドで実行された場合、TestDeadlineRace
は大幅に少ないイテレーションで完了し、テストスイート全体の実行時間が短縮されます。通常のテスト実行(go test
)では、N
は1000のままであり、十分なイテレーションが確保されるため、テストの網羅性は維持されます。
コアとなるコードの変更箇所
変更はsrc/pkg/net/timeout_test.go
ファイル内のTestDeadlineRace
関数に集中しています。
--- a/src/pkg/net/timeout_test.go
+++ b/src/pkg/net/timeout_test.go
@@ -710,6 +710,10 @@ func TestDeadlineRace(t *testing.T) {
t.Skipf("skipping test on %q", runtime.GOOS)
}
+ N := 1000
+ if testing.Short() {
+ N = 50
+ }
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
ln := newLocalListener(t)
defer ln.Close()
@@ -721,7 +725,7 @@ func TestDeadlineRace(t *testing.T) {
done := make(chan bool)
go func() {
t := time.NewTicker(2 * time.Microsecond).C
- for {
+ for i := 0; i < N; i++ {
if err := c.SetDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
break
}
@@ -730,7 +734,7 @@ func TestDeadlineRace(t *testing.T) {
done <- true
}()
var buf [1]byte
- for i := 0; i < 1024; i++ {
+ for i := 0; i < N; i++ {
c.Read(buf[:]) // ignore possible timeout errors
}
c.Close()
コアとなるコードの解説
-
N
変数の導入とtesting.Short()
による値の変更:N := 1000 if testing.Short() { N = 50 }
この部分で、テストのイテレーション回数を制御する
N
変数が定義されます。デフォルト値は1000
ですが、go test -short
オプションが指定されている場合は50
に上書きされます。これにより、短いテスト実行モードでのイテレーション回数が大幅に削減されます。 -
SetDeadline
呼び出しループの変更:// 変更前: for { for i := 0; i < N; i++ { if err := c.SetDeadline(time.Now().Add(2 * time.Microsecond)); err != nil { break } <-t // Tickerからの信号を待つ }
SetDeadline
を繰り返し呼び出すゴルーチン内のループが、無限ループfor {}
からfor i := 0; i < N; i++
に変更されました。これにより、SetDeadline
の呼び出し回数がN
回に制限され、特にshort
モードでのテスト実行が高速化されます。 -
Read
操作ループの変更:// 変更前: for i := 0; i < 1024; i++ { for i := 0; i < N; i++ { c.Read(buf[:]) // ignore possible timeout errors }
メインのゴルーチンで
c.Read
を繰り返し呼び出すループも、固定回数1024
からN
回に変更されました。これも同様に、short
モードでのテスト実行時間を短縮する効果があります。c.Read
はタイムアウトエラーを無視するようにコメントされており、これは競合状態のテストにおいて、タイムアウトが発生してもテストが中断しないようにするためです。
これらの変更により、TestDeadlineRace
は、通常のテスト実行では十分なイテレーションを確保しつつ、go test -short
モードでは迅速に完了するようになり、テストスイート全体の効率が向上しました。
関連リンク
- Go言語の
net
パッケージドキュメント: https://pkg.go.dev/net - Go言語の
testing
パッケージドキュメント: https://pkg.go.dev/testing - Go言語の
time
パッケージドキュメント: https://pkg.go.dev/time
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(
src/pkg/net/timeout_test.go
) - Go言語のテストに関する一般的なプラクティスと
testing.Short()
の利用例に関する情報(一般的なGoプログラミングの知識に基づく) - Go言語の競合状態とテストに関する情報(一般的なGoプログラミングの知識に基づく)
- https://golang.org/cl/12937043 (Gerrit Code Review)