[インデックス 13472] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/gob
パッケージ内のファズテスト(fuzz tests)の実行方法を変更するものです。具体的には、これらのテストがデフォルトで無効になるようにし、コマンドラインフラグ --gob.fuzz
を設定した場合にのみ実行されるように修正しています。これにより、ファズテストが大量のメモリを消費したり、実行に非常に時間がかかったりすることによる、小規模なマシンでのテスト失敗や開発効率の低下を防ぎます。
コミット
- コミットハッシュ:
bbe601789cf7526925192abe0a6bc7e7d9265588
- Author: Rob Pike r@golang.org
- Date: Fri Jul 13 14:23:51 2012 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bbe601789cf7526925192abe0a6bc7e7d9265588
元コミット内容
encoding/gob: disable fuzz tests unless command-line flag is set
They can generate huge amounts of memory, causing failure on
small machines. Also they can be very slow. So slow that one test
was commented out! We uncomment it and use a flag.
Fixes #3742.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6373044
変更の背景
この変更の背景には、encoding/gob
パッケージのファズテストが持つリソース消費の問題があります。
- 大量のメモリ消費: ファズテストは、ランダムな入力データを用いてプログラムの堅牢性を検証するため、意図的に異常なデータや非常に大きなデータを生成することがあります。
encoding/gob
の場合、これによりエンコード/デコード処理中に大量のメモリが割り当てられ、特にメモリが限られた環境(例: CI/CD環境の小規模なビルドエージェントや開発者のローカルマシン)でテストが失敗する原因となっていました。 - 実行時間の長さ: ファズテストは網羅的なテストを行うため、実行に非常に長い時間がかかることがあります。元のコミットメッセージにも「So slow that one test was commented out!」とあるように、一部のテストはあまりにも遅いためにコメントアウトされていました。これは開発サイクルを遅らせ、継続的インテグレーションの効率を低下させる要因となります。
これらの問題に対処するため、ファズテストをデフォルトで無効にし、必要に応じて明示的に有効化するメカニズムを導入することが決定されました。これにより、開発者は通常のテスト実行時には高速なフィードバックを得つつ、必要に応じてより徹底的な(しかし時間とリソースを消費する)ファズテストを実行できるようになります。また、コメントアウトされていたテストも再度有効化され、テストカバレッジが向上しました。
前提知識の解説
encoding/gob
パッケージ
encoding/gob
はGo言語の標準ライブラリの一つで、Goのデータ構造をエンコード(シリアライズ)およびデコード(デシリアライズ)するためのパッケージです。Goプログラム間でGoの値を効率的に転送したり、永続化したりするのに使われます。gob
は自己記述型であり、エンコードされたデータにはそのデータの型情報も含まれるため、受信側は事前に型を知らなくてもデコードできます。これは、RPC(Remote Procedure Call)やネットワーク通信、ファイルへのデータ保存など、Goプログラム間の通信でよく利用されます。
ファズテスト (Fuzz Testing)
ファズテスト(またはファジング)は、ソフトウェアの脆弱性やバグを発見するための自動テスト手法です。プログラムにランダムまたは半ランダムな、無効な、予期しないデータを大量に投入し、その応答(クラッシュ、アサーション失敗、メモリリークなど)を監視します。目的は、通常のテストでは見過ごされがちなエッジケースやコーナーケース、入力のパース処理におけるバグなどを発見することです。
Go言語では、Go 1.18から標準でファジングがサポートされていますが、このコミットが行われた2012年時点では、ファジングは通常、testing
パッケージと組み合わせて手動で実装されるか、外部ツールを使用して行われていました。このコミットにおけるファズテストは、rand
パッケージを使用してランダムなバイトシーケンスを生成し、それを gob
デコーダに投入することで実装されています。
Goの testing
パッケージ
Goの testing
パッケージは、ユニットテスト、ベンチマークテスト、例(Example)テストを記述するためのフレームワークを提供します。テスト関数は TestXxx
という命名規則に従い、go test
コマンドで実行されます。
t.Logf()
: テスト中にログメッセージを出力するために使用されます。testing.Short()
:go test -short
フラグが指定された場合にtrue
を返します。これにより、時間のかかるテストをスキップして、より高速なテスト実行を可能にします。このコミットでは、testing.Short()
の代わりにカスタムフラグを使用するように変更されています。
Goの flag
パッケージ
Goの flag
パッケージは、コマンドライン引数をパースするための機能を提供します。これにより、プログラムの実行時にユーザーがオプションを指定できるようになります。
flag.Bool(name, value, usage string) *bool
: ブール型のコマンドラインフラグを定義します。name
はフラグ名(例: "gob.fuzz")、value
はデフォルト値、usage
はフラグの説明です。この関数は、フラグの値へのポインタを返します。flag.Parse()
: コマンドライン引数をパースし、定義されたフラグに値を割り当てます。通常、main
関数の冒頭で呼び出されます。
技術的詳細
このコミットの技術的な核心は、Goの flag
パッケージを利用して、特定のテストの実行をコマンドライン引数によって制御するメカニズムを導入した点にあります。
-
doFuzzTests
フラグの定義:var doFuzzTests = flag.Bool("gob.fuzz", false, "run the fuzz tests, which are large and very slow")
この行は、gob.fuzz
という名前のブール型コマンドラインフラグを定義しています。デフォルト値はfalse
であり、このフラグが明示的に設定されない限り、ファズテストは実行されません。flag.Bool
は*bool
型のポインタを返すため、フラグの値にアクセスするには*doFuzzTests
のようにデリファレンスする必要があります。 -
テスト関数の条件付き実行:
TestFuzz
およびTestFuzzRegressions
関数内で、テストの冒頭に以下の条件が追加されました。if !*doFuzzTests { ... return }
これは、gob.fuzz
フラグがtrue
でない場合(つまり、コマンドラインで-gob.fuzz
が指定されていない場合)、テスト関数はすぐにreturn
して終了することを意味します。これにより、ファズテストのロジックが実行されるのを防ぎます。 -
ログメッセージの追加:
t.Logf("disabled; run with -gob.fuzz to enable")
テストがスキップされた場合、ユーザーにその旨を伝えるログメッセージが出力されます。これにより、なぜファズテストが実行されなかったのかが明確になります。 -
コメントアウトされたテストの再有効化:
TestFuzzRegressions
内で以前コメントアウトされていたtestFuzz(t, 1330522872628565000, 100, new(int))
の呼び出しが再有効化されました。このテストは非常に時間がかかるためコメントアウトされていましたが、フラグによる制御が導入されたことで、必要な場合にのみ実行できるようになり、テストカバレッジが改善されました。
このアプローチにより、開発者は通常の go test
実行時には高速なテストスイートを利用でき、必要に応じて -gob.fuzz
フラグを追加することで、リソースを大量に消費するファズテストを実行できるようになります。これは、開発ワークフローの柔軟性と効率性を向上させるための一般的なパターンです。
コアとなるコードの変更箇所
--- a/src/pkg/encoding/gob/codec_test.go
+++ b/src/pkg/encoding/gob/codec_test.go
@@ -7,6 +7,7 @@ package gob
import (
"bytes"
"errors"
+ "flag"
"math"
"math/rand"
"reflect"
@@ -16,6 +17,8 @@ import (
"unsafe"
)
+var doFuzzTests = flag.Bool("gob.fuzz", false, "run the fuzz tests, which are large and very slow")
+
// Guarantee encoding format by comparing some encodings to hand-written values
type EncodeT struct {
x uint64
@@ -1434,7 +1437,8 @@ func encFuzzDec(rng *rand.Rand, in interface{}) error {
// This does some "fuzz testing" by attempting to decode a sequence of random bytes.
func TestFuzz(t *testing.T) {
-\tif testing.Short() {
+\tif !*doFuzzTests {
+\t\tt.Logf("disabled; run with -gob.fuzz to enable")
\t\treturn
\t}\
@@ -1453,11 +1457,16 @@ func TestFuzz(t *testing.T) {
}
func TestFuzzRegressions(t *testing.T) {
+\tif !*doFuzzTests {
+\t\tt.Logf("disabled; run with -gob.fuzz to enable")
+\t\treturn
+\t}\
+\n // An instance triggering a type name of length ~102 GB.
\ttestFuzz(t, 1328492090837718000, 100, new(float32))\n \t// An instance triggering a type name of 1.6 GB.\n-\t// Commented out because it takes 5m to run.\n-\t//testFuzz(t, 1330522872628565000, 100, new(int))\n+\t// Note: can take several minutes to run.\n+\ttestFuzz(t, 1330522872628565000, 100, new(int))\n }\n \n func testFuzz(t *testing.T, seed int64, n int, input ...interface{}) {
コアとなるコードの解説
上記の差分は、src/pkg/encoding/gob/codec_test.go
ファイルに対する変更を示しています。
-
flag
パッケージのインポート:import ("flag")
flag
パッケージを使用するために、インポートリストに追加されています。 -
doFuzzTests
変数の定義:var doFuzzTests = flag.Bool("gob.fuzz", false, "run the fuzz tests, which are large and very slow")
これはグローバル変数として定義されており、gob.fuzz
というコマンドラインフラグを登録しています。デフォルト値はfalse
で、説明として「run the fuzz tests, which are large and very slow」が設定されています。この変数は*bool
型のポインタであり、フラグの値にアクセスするには*doFuzzTests
とデリファレンスします。 -
TestFuzz
関数の変更: 元のコードではif testing.Short() { ... }
という条件で、go test -short
が指定された場合にファズテストをスキップしていました。この変更により、その条件がif !*doFuzzTests { ... }
に置き換えられました。!*doFuzzTests
:gob.fuzz
フラグがtrue
でない場合(つまり、-gob.fuzz
が指定されていない場合)にtrue
となります。t.Logf("disabled; run with -gob.fuzz to enable")
: テストがスキップされたことを示すログメッセージが出力されます。return
: テスト関数の残りの部分が実行されずに終了します。
-
TestFuzzRegressions
関数の変更:TestFuzz
と同様に、if !*doFuzzTests { ... return }
の条件が追加され、gob.fuzz
フラグが設定されていない場合はテストがスキップされるようになりました。 さらに重要な変更点として、以前コメントアウトされていた以下の行が再有効化されました。testFuzz(t, 1330522872628565000, 100, new(int))
このテストは実行に数分かかることが注記されており、以前はパフォーマンス上の理由で無効化されていました。フラグによる制御が導入されたことで、この重要な回帰テストが再びテストスイートの一部として含まれるようになりました。
これらの変更により、encoding/gob
のファズテストはデフォルトでは実行されず、開発者が明示的に -gob.fuzz
フラグを指定した場合にのみ実行されるようになり、開発者の利便性とテストの網羅性のバランスが取られています。
関連リンク
- Go CL (Change List): https://golang.org/cl/6373044
- Go Issue: https://golang.org/issue/3742
参考にした情報源リンク
- Go
encoding/gob
documentation: https://pkg.go.dev/encoding/gob - Go
testing
package documentation: https://pkg.go.dev/testing - Go
flag
package documentation: https://pkg.go.dev/flag - Fuzzing (Wikipedia): https://en.wikipedia.org/wiki/Fuzzing
- Go Fuzzing (Go 1.18 onwards, for general context): https://go.dev/doc/fuzz/ (Note: This commit predates native Go fuzzing, but provides general context on the concept.)