Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18155] ファイルの概要

このコミットは、Go言語のランタイムパッケージ内のmap_test.goファイルに対する変更です。具体的には、マップのイテレーション順序の非決定性をテストする際の試行回数を増加させています。

コミット

  • コミットハッシュ: 8778760a7edb8bb1b756b6d3dbf20edb1308159e
  • Author: David Symonds dsymonds@golang.org
  • Date: Fri Jan 3 10:41:56 2014 +1100
  • コミットメッセージ:
    runtime: increase attempt count for map iteration order test.
    
    Some builders broke on this test; I'm guessing that was because
    this test didn't try hard enough to find a different iteration order.
    
    Update #6719
    
    R=dave
    CC=golang-codereviews
    https://golang.org/cl/47300043
    

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/8778760a7edb8bb1b756b6d3dbf20edb1308159e

元コミット内容

runtime: increase attempt count for map iteration order test.

Some builders broke on this test; I'm guessing that was because
this test didn't try hard enough to find a different iteration order.

Update #6719

R=dave
CC=golang-codereviews
https://golang.org/cl/47300043

変更の背景

この変更の背景には、Go言語のマップ(map)のイテレーション順序が意図的に非決定論的であるという特性があります。Goのマップはハッシュテーブルとして実装されており、そのイテレーション順序は、プログラムの実行ごとに、あるいは同じプログラムの実行中でも、マップが変更された場合に異なる可能性があります。これは、特定のサービス拒否攻撃を防ぐためのセキュリティ上の理由と、ハッシュテーブルの平均的なパフォーマンスを保証するための実装上の理由によるものです。

コミットメッセージによると、「一部のビルダがこのテストで壊れた」とあります。これは、TestMapIterOrderというテストが、マップのイテレーション順序が非決定論的であることを検証しようとしていたにもかかわらず、十分な試行回数を行わなかったために、たまたま同じ順序でイテレーションされてしまい、テストが失敗したことを示唆しています。つまり、テストが非決定性を検出するのに十分な確率を持っていなかったため、本来は合格すべきテストが失敗するという「フレイキーテスト(flaky test)」の状態になっていたと考えられます。

この問題を解決するために、テストが異なるイテレーション順序を見つけるための試行回数を増やす必要がありました。これは、Goのマップの非決定的な性質をより確実に検証し、テストの信頼性を向上させるための変更です。

前提知識の解説

Go言語のマップ(map

Go言語のmapは、キーと値のペアを格納するための組み込みのデータ構造です。他の言語におけるハッシュマップ、ハッシュテーブル、ディクショナリに相当します。Goのマップは、高速なキーによる値の検索、追加、削除を提供します。

マップのイテレーション順序の非決定性

Goのマップをfor rangeループでイテレーションする際、要素が返される順序は保証されません。これは意図的な設計であり、以下の理由に基づいています。

  1. セキュリティ: イテレーション順序をランダム化することで、攻撃者が特定の入力を作成してハッシュテーブル操作の最悪のパフォーマンスを引き起こす可能性のあるサービス拒否攻撃を防ぐのに役立ちます。
  2. 実装の詳細: Goのマップの実装(ハッシュテーブル)は、キーを均等に分散させ、良好な平均ケースパフォーマンスを確保するためにランダム化を使用します。要素が追加または削除されると、マップの内部構造が変化する可能性があり、これが非決定的なイテレーション順序にさらに寄与します。

この非決定的な動作はGoの基本的な設計選択であり、変更される可能性は低いとされています。したがって、プログラムのロジックが特定の要素の順序に依存する場合は、mapをそのまま使用するべきではありません。決定的な順序が必要な場合は、キー(または値)をスライスに抽出し、そのスライスをソートしてから、ソートされたスライスをイテレーションしてマップ要素にアクセスする必要があります。

reflect.DeepEqual

reflect.DeepEqualは、Go言語のreflectパッケージに含まれる関数で、2つの値が「深く等しい」かどうかを判定します。これは、プリミティブ型だけでなく、構造体、配列、スライス、マップなどの複合型についても、その内容が再帰的に等しいかどうかを比較します。

TestMapIterOrderテストでは、マップのイテレーションによって得られた順序が、以前のイテレーション順序と異なるかどうかを比較するためにreflect.DeepEqualが使用されています。もしDeepEqualtrueを返した場合、それは順序が同じであることを意味し、テストの目的(非決定性を確認する)からすると望ましくない結果となります。

技術的詳細

TestMapIterOrder関数は、Goのマップのイテレーション順序が非決定論的であることを検証するためのテストです。このテストの基本的なロジックは以下の通りです。

  1. マップの初期化とデータ投入: テスト対象のマップを作成し、キーと値のペアをいくつか挿入します。
  2. 最初のイテレーション順序の取得: ord()というヘルパー関数(おそらくマップをイテレーションしてその順序をスライスとして返す関数)を呼び出し、最初のイテレーション順序を記録します。
  3. 異なる順序の検出試行: forループを使用して、マップを複数回イテレーションし、その都度ord()を呼び出して現在のイテレーション順序を取得します。
  4. 順序の比較: 取得した現在の順序と、最初に記録した順序をreflect.DeepEqualで比較します。
  5. テストの成功条件: もしreflect.DeepEqualfalseを返した場合(つまり、異なる順序が検出された場合)、okフラグをtrueに設定し、ループを抜けます。
  6. テストの失敗条件: ループが終了してもokfalseのままだった場合(つまり、指定された試行回数内に異なる順序が一度も検出されなかった場合)、テストは失敗します。これは、マップのイテレーションが非決定論的であるという前提が満たされなかったことを意味します。

このコミットの変更点は、ステップ3の「異なる順序の検出試行」におけるforループの試行回数を増やしたことです。元のコードではtry < 5、つまり最大5回しか試行していませんでした。しかし、マップのイテレーション順序のランダム性は、試行回数が少ないと、たまたま同じ順序が連続して出現する可能性があり、その結果、テストが誤って失敗することがありました。

試行回数をtry < 100に増やすことで、異なるイテレーション順序が検出される確率が大幅に向上します。これにより、テストがより堅牢になり、Goのマップの非決定的な性質をより確実に検証できるようになります。これは、フレイキーテストの問題を解決し、CI/CDパイプラインにおけるテストの信頼性を高める上で重要な変更です。

コアとなるコードの変更箇所

--- a/src/pkg/runtime/map_test.go
+++ b/src/pkg/runtime/map_test.go
@@ -428,7 +428,7 @@ func TestMapIterOrder(t *testing.T) {
 		}
 		first := ord()
 		ok := false
-		for try := 0; try < 5; try++ {
+		for try := 0; try < 100; try++ {
 			if !reflect.DeepEqual(first, ord()) {
 				ok = true
 				break

コアとなるコードの解説

変更された行はsrc/pkg/runtime/map_test.goファイルの以下の部分です。

-		for try := 0; try < 5; try++ {
+		for try := 0; try < 100; try++ {

この変更は、TestMapIterOrder関数内のforループの条件を変更しています。

  • 変更前: try < 5
    • これは、マップのイテレーション順序がfirstと異なるかどうかを最大5回試行することを意味します。もし5回の試行で一度も異なる順序が見つからなかった場合、テストは失敗します。
  • 変更後: try < 100
    • これは、試行回数を最大100回に増やしています。これにより、異なるイテレーション順序が検出される可能性が大幅に高まります。

この変更の目的は、Goマップのイテレーション順序の非決定性をテストする際の信頼性を向上させることです。ランダムな性質を持つものをテストする場合、試行回数が少ないと、たまたま期待するランダムな結果(この場合は異なる順序)が得られないことがあります。試行回数を増やすことで、統計的に異なる順序が出現する確率が高まり、テストがより正確にマップの非決定性を検証できるようになります。これにより、ビルドシステムでのテストの不安定性(フレイキーテスト)が解消され、テストの信頼性が向上します。

関連リンク

参考にした情報源リンク