Golang Fuzz Testing
前言
Fuzz Testing(模糊測試)是一種軟體測試技術,將自動生成的無效資料或是亂數輸入程式,並監控發生的 exceptions,可能是 crash,assertion 錯誤或是 memory leak。
最近研究 golang 的 fuzz testing,這篇文章紀錄使用方式以及該注意的地方。
Fuzz Testing
開始 Fuzz Testing 前,需要先注意電腦環境支援
- Go 版本大於 1.18
- 系統架構必須是 AMD64 和 ARM 64(https://github.com/golang/go/blob/master/src/internal/platform/supported.go#L71)
接下來介紹 go test 支援的 fuzz 參數
- -fuzz
fuzz target 正則表達式,程式只會執行符合正則表達式的一個 Fuzz 函數,有多個會發生錯誤 - -fuzztime
fuzzing 執行時間,如果設為 5s 表示 5 秒後測試就會結束,預設是會一直執行直到程式 exception 發生 - -fuzzminimizetime
Run enough iterations of the fuzz target during each minimization
attempt to take t, as specified as a time.Duration(-fuzzminimizetime 30s)
The special syntax Nx means to run the fuzz target N times(-fuzzminimizetime 100x)
預設是 60s - -parallel
限制 fuzz process 數量,預設是 $GOMAXPROCS - -run
指定執行某個 Fuzz 或測試,如果檔案裡有其他測試的話,預設是會等測試完再 Fuzz,這邊可以透過 run 指定
開始寫 fuzz testing
基礎環境跟 command line option 了解,來開始寫 fuzz testing,文章會用以前實作的開源專案 didyoumean 為例,該套件的功能主要是從 string list 裡找到其中最接近的文字。
寫 fuzz testing 其實跟一般的 testing 很像,需要注意用 *testing.F。
FuzzEditDistance 範例
type EditDistanceTest struct {
A string
B string
Distance int
}
var (
editDistanceTests = []EditDistanceTest{
{
A: "kitten",
B: "sitting",
Distance: 3,
}, {
A: "Saturday",
B: "Sunday",
Distance: 3,
}, {
A: "台灣好棒棒",
B: "台大",
Distance: 1,
},
}
)
// FuzzFindEditDistance
func FuzzFindEditDistance(f *testing.F) {
for _, test := range editDistanceTests {
f.Add(test.A, test.B)
}
f.Fuzz(func(t *testing.T, a string, b string) {
d := findEditDistance(a, b)
})
}
在 FuzzFindEditDistance 函數裡,這邊先用了 f.Add(test.A, test.B) 加入 seed corpus,接著在呼叫 f.Fuzz func(t *testing.T, a string, b string) {} ,這裡注意 Fuzz 這個函數代的參數要跟前面加入的 seed corpus 對應.而 seed corpus 除了會被用來 Fuzz,也會用來產生 generated corpus 並帶入測試。
fuzz testing 執行結果
$ go test -fuzz=Fuzz -fuzztime=10s -test.v ./...
=== RUN TestFindEditDistance
--- PASS: TestFindEditDistance (0.00s)
=== RUN TestFirstMatch
--- PASS: TestFirstMatch (0.00s)
=== RUN TestMatch
--- PASS: TestMatch (0.00s)
=== RUN FuzzFindEditDistance
fuzz: elapsed: 0s, gathering baseline coverage: 0/50 completed
fuzz: elapsed: 0s, gathering baseline coverage: 50/50 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 133773 (44576/sec), new interesting: 6 (total: 56)
fuzz: elapsed: 6s, execs: 133773 (0/sec), new interesting: 6 (total: 56)
fuzz: elapsed: 9s, execs: 133773 (0/sec), new interesting: 6 (total: 56)
fuzz: elapsed: 11s, execs: 265630 (65062/sec), new interesting: 7 (total: 57)
--- PASS: FuzzFindEditDistance (11.03s)
=== NAME
PASS
ok github.com/sc0vu/didyoumean 11.156s
一開始 fuzz 會先使用 seed / generated corpus 確保程式不會出錯並測試出 baseline coverage。
其他的 log
- elapsed 經過多久時間
- execs 執行 fuzz target 多少次
- new interesting 執行 fuzz 食被加到 generated corpus 的總數,要增加 code coverage 的 corpus 才會被加入
沒有用 -run 限制只執行哪個 Test,fuzz testing 會在其他測試結束後開始。
corpus 目前支援這幾種型態:
string, []byte
int, int8, int16, int32/rune, int64
uint, uint8/byte, uint16, uint32, uint64
float32, float64
bool
結論
Fuzz testing 會自動產生異常的測資,這讓工程師可以再測試時就可以發現 exception,對於確保程式正常運作蠻有幫助。
Golang testing 目前也支援 fuzz testing,推薦各位同學使用。