[インデックス 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
関数の挙動にありました。
-
initBig
関数の問題: 元のinitBig
関数は、jsonBig
の長さがn
と異なる場合にのみjson.Marshal(genValue(n))
を呼び出してjsonBig
を再生成していました。これは、テストが複数回実行される際に、以前のテスト実行で生成されたjsonBig
が再利用される可能性があることを意味します。もし、以前の実行でgenValue(n)
が空の配列を生成してしまい、その結果jsonBig
が[]
となっていた場合、その後のテスト実行でもjsonBig
が[]
のまま再利用され、TestIndentBig
が失敗する原因となっていました。 -
genArray
関数の問題:genArray
関数は、再帰的にJSON配列を生成する際に、内部で計算される要素数f
が0
になる可能性がありました。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つの関数 initBig
と genArray
に変更が加えられています。
-
initBig
関数の変更:- 元のコードでは、
big
とsmall
という定数が定義され、n
の初期値として使用されていました。これらの定数は削除され、n
の値は直接10000
と100
にハードコードされました。これはコードの簡素化であり、直接的なバグ修正とは関係ありません。 - 最も重要な変更は、
if len(jsonBig) != n { ... }
という条件ブロックが削除されたことです。元のコードでは、jsonBig
の長さがn
と異なる場合にのみ、json.Marshal(genValue(n))
を呼び出してjsonBig
を再生成していました。この条件が削除されたことで、initBig
が呼び出されるたびに、常にjson.Marshal(genValue(n))
が実行され、jsonBig
が新しいデータで確実に初期化されるようになりました。これにより、以前のテスト実行でjsonBig
が不正な状態(空の配列など)になっていたとしても、それが次のテスト実行に引き継がれることがなくなりました。
- 元のコードでは、
-
genArray
関数の変更:genArray
関数は、テスト用のJSON配列を生成するヘルパー関数です。この関数内で、配列の要素数f
が計算されます。- 追加された行
if f < 1 { f = 1 }
がこのコミットの主要なバグ修正箇所です。このガード句は、計算されたf
の値が1
未満(つまり0
以下)になった場合に、強制的にf
を1
に設定します。これにより、genArray
が常に少なくとも1つの要素を持つ配列を生成することが保証されます。 - この修正により、
jsonBig
が空の配列[]
になることがなくなり、TestIndentBig
が期待通りに「大きくなる」JSONデータを処理できるようになりました。
これらの変更により、TestIndentBig
の不安定性が解消され、テストの信頼性が向上しました。
関連リンク
- Go issue tracker: https://golang.org/cl/49930051 (Gerrit Code Review へのリンク)
参考にした情報源リンク
- Go言語の公式ドキュメント:
encoding/json
パッケージ - Go言語の公式ドキュメント:
testing
パッケージ - Go言語のテストに関する一般的な情報源 (例: Goのブログ、GoDoc)