欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言開發(fā)代碼自測絕佳go?fuzzing用法詳解

 更新時(shí)間:2022年06月21日 11:12:36   作者:萬俊峰Kevin  
這篇文章主要為大家介紹了Go語言開發(fā)代碼自測絕佳go?fuzzing用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

特別說明

這個(gè)真的不是標(biāo)題黨,我寫代碼20+年,真心認(rèn)為 go fuzzing 是我見過的最牛逼的代碼自測方法。我在用 AC自動(dòng)機(jī) 算法改進(jìn)關(guān)鍵字過濾效率(提升~50%),改進(jìn) mapreduce 對(duì) panic 的處理機(jī)制的時(shí)候,都通過 go fuzzing 發(fā)現(xiàn)了邊緣情況的 bug。所以深深的認(rèn)為,這是我見過最牛逼的代碼自測方法,沒有之一!

go fuzzing 至今已經(jīng)發(fā)現(xiàn)了代碼質(zhì)量極高的 Go 標(biāo)準(zhǔn)庫超過200個(gè)bug,見:github.com/dvyukov/go-…

春節(jié)程序員之間的祝福經(jīng)常是,祝你代碼永無 bug!雖然調(diào)侃,但對(duì)我們每個(gè)程序員來說,每天都在寫 bug,這是事實(shí)。代碼沒 bug 這事,只能證偽,不能證明。即將發(fā)布的 Go 1.18 官方提供了一個(gè)幫助我們證偽的絕佳工具 - go fuzzing。

Go 1.18 大家最關(guān)注的是泛型,然而我真的覺得 go fuzzing 真的是 Go 1.18 最有用的功能,沒有之一!

本文我們就來詳細(xì)看看 go fuzzing:

  • 是什么?
  • 怎么用?
  • 有何最佳實(shí)踐?

首先,你需要升級(jí)到 Go 1.18

Go 1.18 雖然還未正式發(fā)布,但你可以下載 RC 版本,而且即使你生產(chǎn)用 Go 更早版本,你也可以開發(fā)環(huán)境使用 go fuzzing 尋找 bug

go fuzzing 是什么

根據(jù) 官方文檔 介紹,go fuzzing 是通過持續(xù)給一個(gè)程序不同的輸入來自動(dòng)化測試,并通過分析代碼覆蓋率來智能的尋找失敗的 case。這種方法可以盡可能的尋找到一些邊緣 case,親測確實(shí)發(fā)現(xiàn)的都是些平時(shí)很難發(fā)現(xiàn)的問題。

go fuzzing 怎么用

官方介紹寫 fuzz tests 的一些規(guī)則:

  • 函數(shù)必須是 Fuzz開頭,唯一的參數(shù)是 *testing.F,沒有返回值
  • Fuzz tests 必須在 *_test.go 的文件里
  • 上圖中的 fuzz target 是個(gè)方法調(diào)用 (*testing.F).Fuzz,第一個(gè)參數(shù)是 *testing.T,然后就是稱之為 fuzzing arguments 的參數(shù),沒有返回值
  • 每個(gè) fuzz test 里只能有一個(gè) fuzz target
  • 調(diào)用 f.Add(…) 的時(shí)候需要參數(shù)類型跟 fuzzing arguments 順序和類型都一致

fuzzing arguments 只支持以下類型:

string, []byte

int, int8, int16, int32/rune, int64

uint, uint8/byte, uint16, uint32, uint64

float32, float64

bool

fuzz target 不要依賴全局狀態(tài),會(huì)并行跑。

運(yùn)行 fuzzing tests

如果我寫了一個(gè) fuzzing test,比如:

// 具體代碼見 https://github.com/zeromicro/go-zero/blob/master/core/mr/mapreduce_fuzz_test.go
func FuzzMapReduce(f *testing.F) {
  ...
}

那么我們可以這樣執(zhí)行:

go test -fuzz=MapReduce

我們會(huì)得到類似如下結(jié)果:

fuzz: elapsed: 0s, gathering baseline coverage: 0/2 completed
fuzz: elapsed: 0s, gathering baseline coverage: 2/2 completed, now fuzzing with 10 workers
fuzz: elapsed: 3s, execs: 3338 (1112/sec), new interesting: 56 (total: 57)
fuzz: elapsed: 6s, execs: 6770 (1144/sec), new interesting: 62 (total: 63)
fuzz: elapsed: 9s, execs: 10157 (1129/sec), new interesting: 69 (total: 70)
fuzz: elapsed: 12s, execs: 13586 (1143/sec), new interesting: 72 (total: 73)
^Cfuzz: elapsed: 13s, execs: 14031 (1084/sec), new interesting: 72 (total: 73)
PASS
ok    github.com/zeromicro/go-zero/core/mr  13.169s

其中的 ^C 是我按了 ctrl-C 終止了測試,詳細(xì)解釋參考官方文檔。

go-zero 的最佳實(shí)踐

按照我使用下來的經(jīng)驗(yàn)總結(jié),我把最佳實(shí)踐初步總結(jié)為以下四步:

  • 定義 fuzzing arguments,首先要想明白怎么定義 fuzzing arguments,并通過給定的 fuzzing arguments 寫 fuzzing target
  • 思考 fuzzing target 怎么寫,這里的重點(diǎn)是怎么驗(yàn)證結(jié)果的正確性,因?yàn)?fuzzing arguments 是“隨機(jī)”給的,所以要有個(gè)通用的結(jié)果驗(yàn)證方法
  • 思考遇到失敗的 case 如何打印結(jié)果,便于生成新的 unit test
  • 根據(jù)失敗的 fuzzing test 打印結(jié)果編寫新的 unit test,這個(gè)新的 unit test會(huì)被用來調(diào)試解決fuzzing test發(fā)現(xiàn)的問題,并固化下來留給CI 用

接下來我們以一個(gè)最簡單的數(shù)組求和函數(shù)來展示一下上述步驟,go-zero 的實(shí)際案例略顯復(fù)雜,文末我會(huì)給出 go-zero 內(nèi)部落地案例,供大家參考復(fù)雜場景寫法。

這是一個(gè)注入了 bug 的求和的代碼實(shí)現(xiàn):

func Sum(vals []int64) int64 {
  var total int64
  for _, val := range vals {
    if val%1e5 != 0 {
      total += val
    }
  }
  return total
}

1. 定義 fuzzing arguments

你至少需要給出一個(gè) fuzzing argument,不然 go fuzzing 沒法生成測試代碼,所以即使我們沒有很好的輸入,我們也需要定義一個(gè)對(duì)結(jié)果產(chǎn)生影響的 fuzzing argument,這里我們就用 slice 元素個(gè)數(shù)作為 fuzzing arguments,然后 Go fuzzing 會(huì)根據(jù)跑出來的 code coverage 自動(dòng)生成不同的參數(shù)來模擬測試。

func FuzzSum(f *testing.F) {
  f.Add(10)
  f.Fuzz(func(t *testing.T, n int) {
    n %= 20
    ...
  })
}

這里的 n 就是讓 go fuzzing 來模擬 slice 元素個(gè)數(shù),為了保證元素個(gè)數(shù)不會(huì)太多,我們限制在20以內(nèi)(0個(gè)也沒問題),并且我們添加了一個(gè)值為10的語料(go fuzzing 里面稱之為 corpus),這個(gè)值就是讓 go fuzzing 冷啟動(dòng)的一個(gè)值,具體為多少不重要。

2. 怎么寫 fuzzing target

這一步的重點(diǎn)是如何編寫可驗(yàn)證的 fuzzing target,根據(jù)給定的 fuzzing arguments 寫出測試代碼的同時(shí),還需要生成驗(yàn)證結(jié)果正確性用的數(shù)據(jù)。

對(duì)我們這個(gè) Sum 函數(shù)來說,其實(shí)還是比較簡單的,就是隨機(jī)生成 n 個(gè)元素的 slice,然后求和算出期望的結(jié)果。如下:

func FuzzSum(f *testing.F) {
  rand.Seed(time.Now().UnixNano())
  f.Add(10)
  f.Fuzz(func(t *testing.T, n int) {
    n %= 20
    var vals []int64
    var expect int64
    for i := 0; i < n; i++ {
      val := rand.Int63() % 1e6
      vals = append(vals, val)
      expect += val
    }
    assert.Equal(t, expect, Sum(vals))
  })
}

這段代碼還是很容易理解的,自己求和和 Sum 求和做比較而已,就不詳細(xì)解釋了。但復(fù)雜場景你就需要仔細(xì)想想怎么寫驗(yàn)證代碼了,不過這不會(huì)太難,太難的話,可能是對(duì)測試函數(shù)沒有足夠理解或者簡化。

此時(shí)就可以用如下命令跑 fuzzing tests 了,結(jié)果類似如下:

$ go test -fuzz=Sum
fuzz: elapsed: 0s, gathering baseline coverage: 0/2 completed
fuzz: elapsed: 0s, gathering baseline coverage: 2/2 completed, now fuzzing with 10 workers
fuzz: elapsed: 0s, execs: 6672 (33646/sec), new interesting: 7 (total: 6)
--- FAIL: FuzzSum (0.21s)
    --- FAIL: FuzzSum (0.00s)
        sum_fuzz_test.go:34:
              Error Trace:  sum_fuzz_test.go:34
                                  value.go:556
                                  value.go:339
                                  fuzz.go:334
              Error:        Not equal:
                            expected: 8736932
                            actual  : 8636932
              Test:         FuzzSum
    Failing input written to testdata/fuzz/FuzzSum/739002313aceff0ff5ef993030bbde9115541cabee2554e6c9f3faaf581f2004
    To re-run:
    go test -run=FuzzSum/739002313aceff0ff5ef993030bbde9115541cabee2554e6c9f3faaf581f2004
FAIL
exit status 1
FAIL  github.com/kevwan/fuzzing  0.614s

那么問題來了!我們看到了結(jié)果不對(duì),但是我們很難去分析為啥不對(duì),你仔細(xì)品品,上面這段輸出,你怎么分析?

3. 失敗 case 如何打印輸入

對(duì)于上面失敗的測試,我們?nèi)绻艽蛴〕鲚斎?,然后形成一個(gè)簡單的測試用例,那我們就可以直接調(diào)試了。打印出來的輸入最好能夠直接 copy/paste 到新的測試用例里,如果格式不對(duì),對(duì)于那么多行的輸入,你需要一行一行調(diào)格式就太累了,而且這未必就只有一個(gè)失敗的 case。

所以我們把代碼改成了下面這樣:

func FuzzSum(f *testing.F) {
  rand.Seed(time.Now().UnixNano())
  f.Add(10)
  f.Fuzz(func(t *testing.T, n int) {
    n %= 20
    var vals []int64
    var expect int64
    var buf strings.Builder
    buf.WriteString("\n")
    for i := 0; i < n; i++ {
      val := rand.Int63() % 1e6
      vals = append(vals, val)
      expect += val
      buf.WriteString(fmt.Sprintf("%d,\n", val))
    }
    assert.Equal(t, expect, Sum(vals), buf.String())
  })
}

再跑命令,得到如下結(jié)果:

$ go test -fuzz=Sum
fuzz: elapsed: 0s, gathering baseline coverage: 0/2 completed
fuzz: elapsed: 0s, gathering baseline coverage: 2/2 completed, now fuzzing with 10 workers
fuzz: elapsed: 0s, execs: 1402 (10028/sec), new interesting: 10 (total: 8)
--- FAIL: FuzzSum (0.16s)
    --- FAIL: FuzzSum (0.00s)
        sum_fuzz_test.go:34:
              Error Trace:  sum_fuzz_test.go:34
                                  value.go:556
                                  value.go:339
                                  fuzz.go:334
              Error:        Not equal:
                            expected: 5823336
                            actual  : 5623336
              Test:         FuzzSum
              Messages:
                            799023,
                            110387,
                            811082,
                            115543,
                            859422,
                            997646,
                            200000,
                            399008,
                            7905,
                            931332,
                            591988,
    Failing input written to testdata/fuzz/FuzzSum/26d024acf85aae88f3291bf7e1c6f473eab8b051f2adb1bf05d4491bc49f5767
    To re-run:
    go test -run=FuzzSum/26d024acf85aae88f3291bf7e1c6f473eab8b051f2adb1bf05d4491bc49f5767
FAIL
exit status 1
FAIL  github.com/kevwan/fuzzing  0.602s

4. 編寫新的測試用例

根據(jù)上面的失敗 case 的輸出,我們可以 copy/paste 生成如下代碼,當(dāng)然框架是自己寫的,輸入?yún)?shù)可以直接拷貝進(jìn)去。

func TestSumFuzzCase1(t *testing.T) {
  vals := []int64{
    799023,
    110387,
    811082,
    115543,
    859422,
    997646,
    200000,
    399008,
    7905,
    931332,
    591988,
  }
  assert.Equal(t, int64(5823336), Sum(vals))
}

這樣我們就可以很方便的調(diào)試了,并且能夠增加一個(gè)有效 unit test,確保這個(gè) bug 再也不會(huì)出現(xiàn)了。

go fuzzing 更多經(jīng)驗(yàn)

Go 版本問題

我相信,Go 1.18 發(fā)布了,大多數(shù)項(xiàng)目線上代碼不會(huì)立馬升級(jí)到 1.18 的,那么 go fuzzing 引入的 testing.F 不能使用怎么辦?

線上(go.mod)不升級(jí)到 Go 1.18,但是我們本機(jī)是完全推薦升級(jí)的,那么這時(shí)我們只需要把上面的 FuzzSum 放到一個(gè)文件名類似 sum_fuzz_test.go 的文件里,然后在文件頭加上如下指令即可:

// go:build go1.18
// +build go1.18

注意:第三行必須是一個(gè)空行,否則就會(huì)變成 package 的注釋了。

這樣我們?cè)诰€上不管用哪個(gè)版本就不會(huì)報(bào)錯(cuò)了,而我們跑 fuzz testing 一般都是本機(jī)跑的,不受影響。

go fuzzing 不能復(fù)現(xiàn)的失敗

上面講的步驟是針對(duì)簡單情況的,但有時(shí)根據(jù)失敗 case 得到的輸入形成新的 unit test 并不能復(fù)現(xiàn)問題時(shí)(特別是有 goroutine 死鎖問題),問題就變得復(fù)雜起來了,如下輸出你感受一下:

go test -fuzz=MapReduce
fuzz: elapsed: 0s, gathering baseline coverage: 0/2 completed
fuzz: elapsed: 0s, gathering baseline coverage: 2/2 completed, now fuzzing with 10 workers
fuzz: elapsed: 3s, execs: 3681 (1227/sec), new interesting: 54 (total: 55)
...
fuzz: elapsed: 1m21s, execs: 92705 (1101/sec), new interesting: 85 (total: 86)
--- FAIL: FuzzMapReduce (80.96s)
    fuzzing process hung or terminated unexpectedly: exit status 2
    Failing input written to testdata/fuzz/FuzzMapReduce/ee6a61e8c968adad2e629fba11984532cac5d177c4899d3e0b7c2949a0a3d840
    To re-run:
    go test -run=FuzzMapReduce/ee6a61e8c968adad2e629fba11984532cac5d177c4899d3e0b7c2949a0a3d840
FAIL
exit status 1
FAIL  github.com/zeromicro/go-zero/core/mr  81.471s

這種情況下,只是告訴我們 fuzzing process 卡住了或者不正常結(jié)束了,狀態(tài)碼是2。這種情況下,一般 re-run 是不會(huì)復(fù)現(xiàn)的。為什么只是簡單的返回錯(cuò)誤碼2呢?我仔細(xì)去看了 go fuzzing 的源碼,每個(gè) fuzzing test 都是一個(gè)單獨(dú)的進(jìn)程跑的,然后 go fuzzing 把模糊測試的進(jìn)程輸出扔掉了,只是顯示了狀態(tài)碼。那么我們?nèi)绾谓鉀Q這個(gè)問題呢?

我仔細(xì)分析了之后,決定自己來寫一個(gè)類似 fuzzing test 的常規(guī)單元測試代碼,這樣就可以保證失敗是在同一個(gè)進(jìn)程內(nèi),并且會(huì)把錯(cuò)誤信息打印到標(biāo)準(zhǔn)輸出,代碼大致如下:

func TestSumFuzzRandom(t *testing.T) {
  const times = 100000
  rand.Seed(time.Now().UnixNano())
  for i := 0; i < times; i++ {
    n := rand.Intn(20)
    var vals []int64
    var expect int64
    var buf strings.Builder
    buf.WriteString("\n")
    for i := 0; i < n; i++ {
      val := rand.Int63() % 1e6
      vals = append(vals, val)
      expect += val
      buf.WriteString(fmt.Sprintf("%d,\n", val))
    }
    assert.Equal(t, expect, Sum(vals), buf.String())
  }
}

這樣我們就可以自己來簡單模擬一下 go fuzzing,但是任何錯(cuò)誤我們可以得到清晰的輸出。這里或許我沒研究透 go fuzzing,或者還有其它方法可以控制,如果你知道,感謝告訴我一聲。

但這種需要跑很長時(shí)間的模擬 case,我們不會(huì)希望它在 CI 時(shí)每次都被執(zhí)行,所以我把它放在一個(gè)單獨(dú)的文件里,文件名類似 sum_fuzzcase_test.go,并在文件頭加上了如下指令:

// go:build fuzz
// +build fuzz

這樣我們需要跑這個(gè)模擬 case 的時(shí)候加上 -tags fuzz 即可,比如:

go test -tags fuzz ./...

復(fù)雜用法示例

上面介紹的是一個(gè)示例,還是比較簡單的,如果遇到復(fù)雜場景不知道怎么寫,可以先看看 go-zero 是如何落地 go fuzzing 的,如下所示:

MapReduce - github.com/zeromicro/g…

模糊測試了 死鎖goroutine leak,特別是 chan + goroutine 的復(fù)雜場景可以借鑒

stringx - github.com/zeromicro/g…

模糊測試了常規(guī)的算法實(shí)現(xiàn),對(duì)于算法類場景可以借鑒

項(xiàng)目地址 github.com/zeromicro/g…

以上就是Go語言開發(fā)代碼自測絕佳go fuzzing用法詳解的詳細(xì)內(nèi)容,更多關(guān)于Go開發(fā)go fuzzing代碼自測的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言開發(fā)k8s之Service操作解析

    Go語言開發(fā)k8s之Service操作解析

    這篇文章主要為大家介紹了Go語言開發(fā)k8s之Service操作解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • Golang JSON的進(jìn)階用法實(shí)例講解

    Golang JSON的進(jìn)階用法實(shí)例講解

    這篇文章主要給大家介紹了關(guān)于Golang JSON進(jìn)階用法的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用golang具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-09-09
  • golang使用泛型結(jié)構(gòu)體實(shí)現(xiàn)封裝切片

    golang使用泛型結(jié)構(gòu)體實(shí)現(xiàn)封裝切片

    這篇文章主要為大家詳細(xì)介紹了golang使用泛型結(jié)構(gòu)體實(shí)現(xiàn)封裝切片,即封裝切片的增、刪、改、查、長度大小、ForEach(遍歷切片),感興趣的小伙伴可以學(xué)習(xí)一下
    2023-10-10
  • Go語言中排序的3種實(shí)現(xiàn)方法

    Go語言中排序的3種實(shí)現(xiàn)方法

    在寫代碼過程中,排序是經(jīng)常會(huì)遇到的需求,這篇文章主要為大家介紹三種常用的方法,文中的示例代碼簡潔易懂,需要的小伙伴可以參考下
    2023-08-08
  • Golang接口的定義與空接口及斷言的使用示例

    Golang接口的定義與空接口及斷言的使用示例

    在?Golang?中,接口是一種類型,它是由一組方法簽名組成的抽象集合。這篇文章主要為大家介紹了Golang接口的具體使用,希望對(duì)大家有所幫助,空接口是特殊形式的接口類型,普通的接口都有方法,而空接口沒有定義任何方法口,也因此,我們可以說所有類型都至少實(shí)現(xiàn)了空接口
    2023-04-04
  • Golang中定時(shí)器的陷阱詳解

    Golang中定時(shí)器的陷阱詳解

    這篇文章主要給大家介紹了關(guān)于Golang中定時(shí)器陷阱的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用golang具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-09-09
  • Go語言展現(xiàn)快速排序算法全過程的思路及代碼示例

    Go語言展現(xiàn)快速排序算法全過程的思路及代碼示例

    這篇文章主要介紹了Go語言展現(xiàn)快速排序算法全過程的思路及代碼示例,文章最后作者還提到了對(duì)Quick Sort算法優(yōu)化的一些想法,需要的朋友可以參考下
    2016-04-04
  • Go語言實(shí)現(xiàn)字符串切片賦值的方法小結(jié)

    Go語言實(shí)現(xiàn)字符串切片賦值的方法小結(jié)

    這篇文章主要給大家介紹了Go語言實(shí)現(xiàn)字符串切片賦值的兩種方法,分別是在for循環(huán)的range中以及在函數(shù)的參數(shù)傳遞中實(shí)現(xiàn),有需要的朋友們可以根據(jù)自己的需要選擇使用。下面來一起看看吧。
    2016-10-10
  • Golang 函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)裝飾器的一個(gè)實(shí)現(xiàn)詳解

    Golang 函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)裝飾器的一個(gè)實(shí)現(xiàn)詳解

    這篇文章主要介紹了Golang 函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)裝飾器的一個(gè)實(shí)現(xiàn)詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-03-03
  • Gin框架限流實(shí)現(xiàn)示例

    Gin框架限流實(shí)現(xiàn)示例

    本文主要介紹了Gin框架限流實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03

最新評(píng)論