[インデックス 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/gobdocumentation: https://pkg.go.dev/encoding/gob - Go
testingpackage documentation: https://pkg.go.dev/testing - Go
flagpackage 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.)