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

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

このコミットは、Go言語の標準ライブラリ encoding/json パッケージ内のテストの失敗を修正するものです。具体的には、scanner_test.go ファイル内の TestIndentBig というテストが、特定の条件下で失敗する問題を解決しています。

コミット

commit 3b85f9b7e184be17c411152f6b010aa279a85dcf
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Wed Feb 12 21:50:58 2014 +0400

    encoding/json: fix test failure
    $ go test -cpu=1,1,1,1,1,1,1,1,1 encoding/json
    --- FAIL: TestIndentBig (0.00 seconds)
            scanner_test.go:131: Indent(jsonBig) did not get bigger
    On 4-th run initBig generates an empty array.
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/49930051

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

https://github.com/golang/go/commit/3b85f9b7e184be17c411152f6b010aa279a85dcf

元コミット内容

encoding/json: fix test failure

このコミットは、encoding/json パッケージのテストが失敗する問題を修正します。 具体的には、go test -cpu=1,1,1,1,1,1,1,1,1 encoding/json を実行した際に、TestIndentBig テストが失敗し、scanner_test.go:131: Indent(jsonBig) did not get bigger というエラーメッセージが表示される問題です。 この問題は、「4回目の実行時に initBig が空の配列を生成する」ことが原因であると特定されています。

変更の背景

このコミットの背景には、Go言語の encoding/json パッケージの Indent 関数のテストの不安定性がありました。Indent 関数はJSONデータを整形(インデント)する役割を持ちます。TestIndentBig テストは、大きなJSONデータに対して Indent 関数が正しく動作し、結果として整形されたJSONデータが元のデータよりも大きくなることを検証していました。

しかし、テストが実行されるたびに、特に複数回連続で実行されると、jsonBig というテスト用の大きなJSONデータを生成する initBig 関数が、誤って空のJSON配列 [] を生成してしまうことがありました。空の配列に対して Indent 関数を適用しても、結果は [] または [\n] となり、元のデータよりも「大きく」なるというテストの期待を満たせません。これにより、テストが失敗するという現象が発生していました。

この問題は、テストの再現性が低い「flaky test」(不安定なテスト)として認識され、開発プロセスにおいて信頼性の低下を招くため、早急な修正が必要とされました。特に、go test -cpu=... のようにCPU数を指定してテストを実行する際に顕在化していたことから、並行処理やテストの実行順序に依存する可能性も示唆されていました。

前提知識の解説

  • encoding/json パッケージ: Go言語の標準ライブラリで、JSONデータのエンコード(Goのデータ構造からJSONへ)とデコード(JSONからGoのデータ構造へ)を提供します。
    • json.Marshal: Goの値をJSON形式のバイトスライスに変換(エンコード)する関数です。
    • json.Indent: JSON形式のバイトスライスを読み込み、指定されたプレフィックスとインデント文字列を使用して整形されたJSON形式のバイトスライスを生成する関数です。
  • Goのテスト (testing パッケージ): Go言語には、組み込みのテストフレームワークが用意されています。
    • go test: テストを実行するためのコマンドです。
    • testing.Short(): テストが「ショートモード」で実行されているかどうかを示すブール値を返します。go test -short フラグを付けて実行すると true になります。通常、時間のかかるテストをスキップするために使用されます。
  • Flaky Test (不安定なテスト): 同じコードに対して、同じテストが成功したり失敗したりするテストのことです。環境、実行順序、タイミング、乱数など、外部要因に依存して結果が変わることがあります。開発の生産性を低下させ、テストの信頼性を損なうため、修正が推奨されます。
  • genValue および genArray 関数: このテストファイル内で定義されているヘルパー関数で、テスト用の複雑なJSON構造を動的に生成するために使用されます。genValue は任意のGoの値を生成し、genArray は配列を生成します。

技術的詳細

このコミットの技術的な核心は、encoding/json パッケージのテストスイートにおける TestIndentBig の不安定性を解消することにあります。このテストは、json.Indent 関数が大きなJSONデータに対して正しく動作し、その出力が元のデータよりも大きくなることを検証します。

問題の根本原因は、テストデータ jsonBig を生成する initBig 関数と、その内部で呼び出される genArray 関数の挙動にありました。

  1. initBig 関数の問題: 元の initBig 関数は、jsonBig の長さが n と異なる場合にのみ json.Marshal(genValue(n)) を呼び出して jsonBig を再生成していました。これは、テストが複数回実行される際に、以前のテスト実行で生成された jsonBig が再利用される可能性があることを意味します。もし、以前の実行で genValue(n) が空の配列を生成してしまい、その結果 jsonBig[] となっていた場合、その後のテスト実行でも jsonBig[] のまま再利用され、TestIndentBig が失敗する原因となっていました。

  2. genArray 関数の問題: genArray 関数は、再帰的にJSON配列を生成する際に、内部で計算される要素数 f0 になる可能性がありました。f((i+1)*n)/f - (i*n)/f のような複雑な整数演算によって決定されます。特定の n の値や、テストの実行環境(特にCPU数や並行性)によって、この計算結果が 0 になることがあり、その結果 genArray が空の配列を返していました。空の配列が jsonBig に格納されると、json.Indent を適用しても出力が大きくならないため、テストが失敗していました。

このコミットでは、これらの問題を解決するために以下の2つの変更が導入されました。

  • initBig の変更: jsonBig の再生成ロジックが変更され、if len(jsonBig) != n の条件が削除されました。これにより、initBig が呼び出されるたびに、常に json.Marshal(genValue(n)) が実行され、jsonBig が新しいデータで確実に初期化されるようになりました。これにより、以前のテスト実行で生成された不正な jsonBig が再利用される可能性が排除されました。
  • genArray の変更: genArray 関数内に if f < 1 { f = 1 } というガード句が追加されました。この変更により、genArray が生成する配列の要素数 f が、いかなる場合でも最低1つは保証されるようになりました。これにより、genArray が空の配列を返すことがなくなり、jsonBig が常に非空のJSONデータを含むことが保証され、TestIndentBig の失敗原因が根本的に解消されました。

これらの変更により、TestIndentBig は常に期待通りの非空のJSONデータを処理するようになり、テストの不安定性が解消されました。

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

--- a/src/pkg/encoding/json/scanner_test.go
+++ b/src/pkg/encoding/json/scanner_test.go
@@ -239,23 +239,16 @@ func trim(b []byte) []byte {
 
 var jsonBig []byte
 
-const (
-	big   = 10000
-	small = 100
-)
-
 func initBig() {
-	n := big
+	n := 10000
 	if testing.Short() {
-		n = small
+		n = 100
 	}
-	if len(jsonBig) != n {
-		b, err := Marshal(genValue(n))
-		if err != nil {
-			panic(err)
-		}
-		jsonBig = b
+	b, err := Marshal(genValue(n))
+	if err != nil {
+		panic(err)
 	}
+	jsonBig = b
 }
 
 func genValue(n int) interface{} {
@@ -296,6 +289,9 @@ func genArray(n int) []interface{} {
 	if f > n {
 		f = n
 	}
+	if f < 1 {
+		f = 1
+	}
 	x := make([]interface{}, f)
 	for i := range x {
 		x[i] = genValue(((i+1)*n)/f - (i*n)/f)

コアとなるコードの解説

このコミットでは、src/pkg/encoding/json/scanner_test.go ファイル内の2つの関数 initBiggenArray に変更が加えられています。

  1. initBig 関数の変更:

    • 元のコードでは、bigsmall という定数が定義され、n の初期値として使用されていました。これらの定数は削除され、n の値は直接 10000100 にハードコードされました。これはコードの簡素化であり、直接的なバグ修正とは関係ありません。
    • 最も重要な変更は、if len(jsonBig) != n { ... } という条件ブロックが削除されたことです。元のコードでは、jsonBig の長さが n と異なる場合にのみ、json.Marshal(genValue(n)) を呼び出して jsonBig を再生成していました。この条件が削除されたことで、initBig が呼び出されるたびに、常に json.Marshal(genValue(n)) が実行され、jsonBig が新しいデータで確実に初期化されるようになりました。これにより、以前のテスト実行で jsonBig が不正な状態(空の配列など)になっていたとしても、それが次のテスト実行に引き継がれることがなくなりました。
  2. genArray 関数の変更:

    • genArray 関数は、テスト用のJSON配列を生成するヘルパー関数です。この関数内で、配列の要素数 f が計算されます。
    • 追加された行 if f < 1 { f = 1 } がこのコミットの主要なバグ修正箇所です。このガード句は、計算された f の値が 1 未満(つまり 0 以下)になった場合に、強制的に f1 に設定します。これにより、genArray が常に少なくとも1つの要素を持つ配列を生成することが保証されます。
    • この修正により、jsonBig が空の配列 [] になることがなくなり、TestIndentBig が期待通りに「大きくなる」JSONデータを処理できるようになりました。

これらの変更により、TestIndentBig の不安定性が解消され、テストの信頼性が向上しました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント: encoding/json パッケージ
  • Go言語の公式ドキュメント: testing パッケージ
  • Go言語のテストに関する一般的な情報源 (例: Goのブログ、GoDoc)