最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

特別說明

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

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

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

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

本文我們就來詳細看看 go fuzzing:

  • 是什么?
  • 怎么用?
  • 有何最佳實踐?

首先,你需要升級到 Go 1.18

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

go fuzzing 是什么

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

go fuzzing 怎么用

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

  • 函數(shù)必須是 Fuzz開頭,唯一的參數(shù)是 *testing.F,沒有返回值
  • Fuzz tests 必須在 *_test.go 的文件里
  • 上圖中的 fuzz target 是個方法調(diào)用 (*testing.F).Fuzz,第一個參數(shù)是 *testing.T,然后就是稱之為 fuzzing arguments 的參數(shù),沒有返回值
  • 每個 fuzz test 里只能有一個 fuzz target
  • 調(diào)用 f.Add(…) 的時候需要參數(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),會并行跑。

運行 fuzzing tests

如果我寫了一個 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

我們會得到類似如下結(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 終止了測試,詳細解釋參考官方文檔。

go-zero 的最佳實踐

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

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

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

這是一個注入了 bug 的求和的代碼實現(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

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

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

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

2. 怎么寫 fuzzing target

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

對我們這個 Sum 函數(shù)來說,其實還是比較簡單的,就是隨機生成 n 個元素的 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 求和做比較而已,就不詳細解釋了。但復(fù)雜場景你就需要仔細想想怎么寫驗證代碼了,不過這不會太難,太難的話,可能是對測試函數(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é)果不對,但是我們很難去分析為啥不對,你仔細品品,上面這段輸出,你怎么分析?

3. 失敗 case 如何打印輸入

對于上面失敗的測試,我們?nèi)绻艽蛴〕鲚斎?,然后形成一個簡單的測試用例,那我們就可以直接調(diào)試了。打印出來的輸入最好能夠直接 copy/paste 到新的測試用例里,如果格式不對,對于那么多行的輸入,你需要一行一行調(diào)格式就太累了,而且這未必就只有一個失敗的 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 生成如下代碼,當然框架是自己寫的,輸入?yún)?shù)可以直接拷貝進去。

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)試了,并且能夠增加一個有效 unit test,確保這個 bug 再也不會出現(xiàn)了。

go fuzzing 更多經(jīng)驗

Go 版本問題

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

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

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

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

這樣我們在線上不管用哪個版本就不會報錯了,而我們跑 fuzz testing 一般都是本機跑的,不受影響。

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

上面講的步驟是針對簡單情況的,但有時根據(jù)失敗 case 得到的輸入形成新的 unit test 并不能復(fù)現(xiàn)問題時(特別是有 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 是不會復(fù)現(xiàn)的。為什么只是簡單的返回錯誤碼2呢?我仔細去看了 go fuzzing 的源碼,每個 fuzzing test 都是一個單獨的進程跑的,然后 go fuzzing 把模糊測試的進程輸出扔掉了,只是顯示了狀態(tài)碼。那么我們?nèi)绾谓鉀Q這個問題呢?

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

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,但是任何錯誤我們可以得到清晰的輸出。這里或許我沒研究透 go fuzzing,或者還有其它方法可以控制,如果你知道,感謝告訴我一聲。

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

// go:build fuzz
// +build fuzz

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

go test -tags fuzz ./...

復(fù)雜用法示例

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

MapReduce - github.com/zeromicro/g…

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

stringx - github.com/zeromicro/g…

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

項目地址 github.com/zeromicro/g…

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

相關(guān)文章

  • 詳解Go語言如何使用xorm實現(xiàn)讀取mysql

    詳解Go語言如何使用xorm實現(xiàn)讀取mysql

    xorm是go語言的常用orm之一,可以用來操作數(shù)據(jù)庫。本文就來和大家聊聊Go語言如何使用xorm實現(xiàn)讀取mysql功能,感興趣的小伙伴可以跟隨小編一起學習一下
    2022-11-11
  • go語言通過反射創(chuàng)建結(jié)構(gòu)體、賦值、并調(diào)用對應(yīng)的操作

    go語言通過反射創(chuàng)建結(jié)構(gòu)體、賦值、并調(diào)用對應(yīng)的操作

    這篇文章主要介紹了go語言通過反射創(chuàng)建結(jié)構(gòu)體、賦值、并調(diào)用對應(yīng)的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • Golang使用singleflight解決并發(fā)重復(fù)請求

    Golang使用singleflight解決并發(fā)重復(fù)請求

    高并發(fā)的場景下,經(jīng)常會出現(xiàn)并發(fā)重復(fù)請求資源的情況,singleflight是golang內(nèi)置的一個包,這個包提供了對重復(fù)函數(shù)調(diào)用的抑制功能,所以下面我們就來看看如何使用它解決并發(fā)重復(fù)請求吧
    2023-08-08
  • 源碼分析Go語言中g(shù)ofmt實現(xiàn)原理

    源碼分析Go語言中g(shù)ofmt實現(xiàn)原理

    gofmt?是?Go?語言官方提供的一個工具,用于自動格式化?Go?源代碼,使其符合?Go?語言的官方編碼風格,本文給大家源碼詳細分析了Go語言中g(shù)ofmt實現(xiàn)原理,并通過圖文和代碼講解的非常詳細,需要的朋友可以參考下
    2024-03-03
  • 基于Go語言實現(xiàn)一個并發(fā)下載器

    基于Go語言實現(xiàn)一個并發(fā)下載器

    這篇文章主要為大家詳細介紹了如何利用GO語言實現(xiàn)一個并發(fā)的文件下載器,可以在不重新啟動整個下載的情況下處理錯誤,感興趣的小伙伴可以了解一下
    2023-10-10
  • 淺析Go語言中的棧和先進先出原則

    淺析Go語言中的棧和先進先出原則

    這篇文章主要來和大家探討一樣如何在Go語言中實現(xiàn)和使用堆棧,以及堆棧如何遵循先進先出 (FIFO) 原則,文中的示例代碼簡潔易懂,需要的可以參考一下
    2023-07-07
  • golang字符編碼的實現(xiàn)

    golang字符編碼的實現(xiàn)

    本文主要介紹了golang字符編碼的實現(xiàn),,有三種編碼形式:UTF-8,UTF-16,UTF-32,下面就來介紹一下如何使用,感興趣的可以了解一下
    2024-08-08
  • 詳解go語言中并發(fā)安全和鎖問題

    詳解go語言中并發(fā)安全和鎖問題

    這篇文章主要介紹了go語言中并發(fā)安全和鎖問題,包含互斥鎖解鎖過程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-10-10
  • go語言編程之美自定義二進制文件實用指南

    go語言編程之美自定義二進制文件實用指南

    這篇文章主要介紹了go語言編程之美自定義二進制文件實用指南
    2023-12-12
  • golang?gorm更新日志執(zhí)行SQL示例詳解

    golang?gorm更新日志執(zhí)行SQL示例詳解

    這篇文章主要為大家介紹了golang?gorm更新日志執(zhí)行SQL示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2022-04-04

最新評論

琼结县| 三原县| 云阳县| 上虞市| 金乡县| 彰化市| 哈密市| 永昌县| 织金县| 涪陵区| 和平县| 林西县| 岳池县| 临泉县| 麦盖提县| 昂仁县| 习水县| 和田县| 内江市| 望城县| 政和县| 手游| 瓮安县| 金川县| 尉犁县| 宿州市| 桃江县| 康乐县| 开化县| 大埔县| 呼图壁县| 涡阳县| 连南| 城口县| 观塘区| 青铜峡市| 当涂县| 石柱| 阜宁县| 安陆市| 弥渡县|