• English
  • 模糊測試入門指南

    本教學介紹Go語言中模糊測試的基礎知識。模糊測試透過向測試程式輸入隨機資料,試圖發現漏洞或導致崩潰的輸入。模糊測試可發現的漏洞類型包括SQL注入、緩衝區溢出、拒絕服務攻擊和跨站腳本攻擊等。

    在本教學中,您將為一個簡單函數撰寫模糊測試,執行 go 命令,並除錯修復程式碼中的問題。

    若需了解本教學中的術語,請參閱Go模糊測試詞彙表

    您將依次完成以下步驟:

    1. 建立程式碼資料夾
    2. 加入待測程式碼
    3. 加入單元測試
    4. 加入模糊測試
    5. 修復兩個漏洞
    6. 探索更多資源

    注意:Go模糊測試目前僅支援部分內建類型,具體列表詳見 Go模糊測試文件,未來將逐步增加更多內建類型的支援。

    先決條件

    • Go 1.18 或更高版本的安裝。 有關安裝說明,請參閱 安裝 Go
    • 一個用於編輯程式碼的工具。 任何你擁有的文字編輯器都可以正常工作。
    • 一個命令終端。 Go 在 Linux 和 Mac 上的任何終端上都能很好地工作,在 Windows 上的 PowerShell 或 cmd 上也是如此。
    • 支援模糊測試的環境。 目前,Go 模糊測試與覆蓋檢測僅在 AMD64 和 ARM64 架構上可用。

    為程式碼建立一個資料夾

    首先,為要撰寫的程式碼建立一個資料夾。

    1. 開啟命令提示符並切換到你的主目錄。

      在 Linux 或 Mac 上:

      $ cd

      在 Windows 上:

      C:\> cd %HOMEPATH%

      本教學的其餘部分將顯示 $ 作為提示符。你使用的命令在 Windows 上也能工作。

    2. 從命令提示符,建立一個名為 fuzz 的程式碼目錄。

      $ mkdir fuzz
      $ cd fuzz
    3. 建立一個模組來儲存你的程式碼。

      執行 go mod init 命令,給它你新程式碼的模組路徑。

      $ go mod init example/fuzz
      go: creating new go.mod: module example/fuzz

      注意: 對於生產程式碼,你將指定一個更具體符合你自己需求的模組路徑。有關更多資訊,請參閱 管理依賴項

    接下來,你將加入一些簡單的程式碼來反轉字串,稍後我們將對其進行模糊測試。

    加入測試程式碼

    在此步驟中,你將加入一個函數來反轉字串。

    撰寫程式碼

    1. 使用你的文字編輯器,在 fuzz 目錄中建立一個名為 main.go 的檔案。

    2. 將以下套件宣告貼上到 main.go 的頂部。

      package main

      一個獨立的程式(與函式庫相對)始終在 main 套件中。

    3. 在套件宣告下方,貼上以下函數宣告。

      func Reverse(s string) string {
          b := []byte(s)
          for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
              b[i], b[j] = b[j], b[i]
          }
          return string(b)
      }

      此函數將接受一個 string,逐個位元組地循環遍歷它,並在最後返回反轉後的字串。

      注意: 此程式碼基於 golang.org/x/example 中的 stringutil.Reverse 函數。

    4. main.go 的頂部,套件宣告下方,貼上以下 main 函數以初始化一個字串,反轉它,列印輸出,並重複。

      func main() {
          input := "The quick brown fox jumped over the lazy dog"
          rev := Reverse(input)
          doubleRev := Reverse(rev)
          fmt.Printf("original: %q\n", input)
          fmt.Printf("reversed: %q\n", rev)
          fmt.Printf("reversed again: %q\n", doubleRev)
      }

      此函數將執行一些 Reverse 操作,然後將輸出列印到命令列。這對於查看程式碼的實際執行情況和潛在的除錯很有幫助。

    5. main 函數使用了 fmt 套件,因此你需要匯入它。

      程式碼的前幾行應如下所示:

      package main
      
      import "fmt"

    執行程式碼

    在包含 main.go 的目錄的命令列中,執行程式碼。

    $ go run .
    original: "The quick brown fox jumped over the lazy dog"
    reversed: "god yzal eht revo depmuj xof nworb kciuq ehT"
    reversed again: "The quick brown fox jumped over the lazy dog"

    你可以看到原始字串,反轉後的結果,然後再反轉一次的結果,這與原始字串相同。

    現在程式碼正在執行,是時候測試它了。

    加入單元測試

    在此步驟中,你將為 Reverse 函數撰寫一個基本的單元測試。

    撰寫程式碼

    1. 使用你的文字編輯器,在 fuzz 目錄中建立一個名為 reverse_test.go 的檔案。

    2. 將以下程式碼貼上到 reverse_test.go

      package main
      
      import (
          "testing"
      )
      
      func TestReverse(t *testing.T) {
          testcases := []struct {
              in, want string
          }{
              {"Hello, world", "dlrow ,olleH"},
              {" ", " "},
              {"!12345", "54321!"},
          }
          for _, tc := range testcases {
              rev := Reverse(tc.in)
              if rev != tc.want {
                      t.Errorf("Reverse: %q, want %q", rev, tc.want)
              }
          }
      }

      這個簡單的測試將斷言列出的輸入字串將被正確反轉。

    執行程式碼

    使用 go test 執行單元測試

    $ go test
    PASS
    ok      example/fuzz  0.013s

    接下來,你將把單元測試改為模糊測試。

    加入模糊測試

    單元測試有一些限制,即每個輸入都必須由開發者加入到測試中。模糊測試的一個好處是它能為你的程式碼產生輸入,並可能識別出你設想的測試用例未觸及的邊緣情況。

    在本節中,你將把單元測試轉換為模糊測試,這樣你就可以用較少的工作產生更多輸入!

    請注意,你可以在同一個 *_test.go 檔案中保留單元測試、基準測試和模糊測試,但在這個例子中,你將把單元測試轉換為模糊測試。

    撰寫程式碼

    在你的文字編輯器中,將 reverse_test.go 中的單元測試替換為以下模糊測試。

    func FuzzReverse(f *testing.F) {
        testcases := []string{"Hello, world", " ", "!12345"}
        for _, tc := range testcases {
            f.Add(tc)  // 使用 f.Add 提供種子語料庫
        }
        f.Fuzz(func(t *testing.T, orig string) {
            rev := Reverse(orig)
            doubleRev := Reverse(rev)
            if orig != doubleRev {
                t.Errorf("Before: %q, after: %q", orig, doubleRev)
            }
            if utf8.ValidString(orig) && !utf8.ValidString(rev) {
                t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
            }
        })
    }

    模糊測試也有一些限制。在你的單元測試中,你可以預測 Reverse 函數的預期輸出,並驗證實際輸出是否符合這些期望。

    例如,在測試用例 Reverse("Hello, world") 中,單元測試指定返回值為 "dlrow ,olleH"

    在進行模糊測試時,你無法預測預期輸出,因為你無法控制輸入。

    然而,你可以驗證 Reverse 函數的某些屬性。本模糊測試中正在檢查的兩個屬性是:

    1. 反轉字串兩次會保留原始值
    2. 反轉後的字串保持其有效 UTF-8 狀態。

    注意單元測試和模糊測試之間的語法差異:

    • 函數以 FuzzXxx 開頭,而不是 TestXxx,並取 *testing.F 而不是 *testing.T
    • 你期望看到 t.Run 執行的地方,你會看到 f.Fuzz,它取一個模糊目標函數,其參數是 *testing.T 和要模糊的類型。你單元測試中的輸入將使用 f.Add 作為種子語料庫輸入提供。

    確保已匯入新套件 unicode/utf8

    package main
    
    import (
        "testing"
        "unicode/utf8"
    )

    將單元測試轉換為模糊測試後,是時候再次執行測試了。

    執行程式碼

    1. 執行模糊測試但不進行模糊,以確保種子輸入通過。

      $ go test
      PASS
      ok      example/fuzz  0.013s

      如果你在該檔案中有其他測試,並且只想執行模糊測試,你也可以執行 go test -run=FuzzReverse

    2. 使用模糊執行 FuzzReverse,看看是否有任何隨機產生的字串輸入會導致失敗。這是使用 go test 和一個新標誌 -fuzz 來執行的,設定為參數 Fuzz。複製以下命令。

      $ go test -fuzz=Fuzz

      另一個有用的標誌是 -fuzztime,它限制模糊測試所花費的時間。例如,在下面的測試中指定 -fuzztime 10s 意味著,只要沒有更早發生失敗,測試將在 10 秒後預設退出。有關其他測試標誌,請參閱 cmd/go 文件中的 此部分

      現在,執行你剛剛複製的命令。

      $ go test -fuzz=Fuzz
      fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
      fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers
      fuzz: minimizing 38-byte failing input file...
      --- FAIL: FuzzReverse (0.01s)
          --- FAIL: FuzzReverse (0.00s)
              reverse_test.go:20: Reverse produced invalid UTF-8 string "\x9c\xdd"
      
          Failing input written to testdata/fuzz/FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
          To re-run:
          go test -run=FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
      FAIL
      exit status 1
      FAIL    example/fuzz  0.030s

      模糊測試期間發生了失敗,導致問題的輸入被寫入種子語料庫檔案,即使沒有 -fuzz 標誌,下次執行 go test 時也會執行該檔案。要查看導致失敗的輸入,請在文字編輯器中開啟寫入 testdata/fuzz/FuzzReverse 目錄的語料庫檔案。你的種子語料庫檔案可能包含不同的字串,但格式將相同。

      go test fuzz v1
      string("泃")

      語料庫檔案的第一行表示編碼版本。接下來的每一行代表構成語料庫條目的每個類型的值。由於模糊目標只接受一個輸入,因此版本後只有一個值。

    3. 再次執行 go test,不使用 -fuzz 標誌;將使用新的失敗種子語料庫條目:

      $ go test
      --- FAIL: FuzzReverse (0.00s)
          --- FAIL: FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a (0.00s)
              reverse_test.go:20: Reverse produced invalid string
      FAIL
      exit status 1
      FAIL    example/fuzz  0.016s

      由於我們的測試失敗,是時候除錯了。

    修復無效字串錯誤

    在本節中,你將除錯失敗,並修復錯誤。

    在繼續之前,請花點時間思考並嘗試自己解決問題。

    診斷錯誤

    有幾種不同的方法可以除錯此錯誤。如果你使用 VS Code 作為文字編輯器,你可以 設定除錯器 進行調查。

    在本教學中,我們將有用的除錯資訊記錄到終端。

    首先,考慮 utf8.ValidString 的文件。

    ValidString 報告 s 是否完全由有效的 UTF-8 編碼的符文組成。

    目前的 Reverse 函數逐位元組反轉字串,這就是我們的問題所在。為了保留原始字串的 UTF-8 編碼符文,我們必須改為逐個符文地反轉字串。

    為了檢查為什麼輸入(在這種情況下是中文字元 )導致 Reverse 在反轉時產生無效字串,你可以檢查反轉字串中的符文數量。

    撰寫程式碼

    在你的文字編輯器中,將 FuzzReverse 中的模糊目標替換為以下內容。

    f.Fuzz(func(t *testing.T, orig string) {
        rev := Reverse(orig)
        doubleRev := Reverse(rev)
        t.Logf("Number of runes: orig=%d, rev=%d, doubleRev=%d", utf8.RuneCountInString(orig), utf8.RuneCountInString(rev), utf8.RuneCountInString(doubleRev))
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        })

    如果發生錯誤,或帶 -v 執行測試,此 t.Logf 行將列印到命令列,這有助於你除錯此特定問題。

    執行程式碼

    使用 go test 執行測試

    $ go test
    --- FAIL: FuzzReverse (0.00s)
        --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)
            reverse_test.go:16: Number of runes: orig=1, rev=3, doubleRev=1
            reverse_test.go:21: Reverse produced invalid UTF-8 string "\x83\xb3\xe6"
    FAIL
    exit status 1
    FAIL    example/fuzz    0.598s

    整個種子語料庫使用的字串中每個字元都是一個位元組。然而,像 泃 這樣的字元可能需要多個位元組。因此,逐位元組反轉字串會使多位元組字元無效。

    注意: 如果你對 Go 如何處理字串感到好奇,請閱讀部落格文章 Go 中的字串、位元組、符文和字元 以獲得更深入的理解。

    對錯誤有了更好的理解後,糾正 Reverse 函數中的錯誤。

    修復錯誤

    為了糾正 Reverse 函數,讓我們逐個符文而不是逐位元組遍歷字串。

    撰寫程式碼

    在你的文字編輯器中,將現有的 Reverse() 函數替換為以下內容。

    func Reverse(s string) {
        r := []rune(s)
        for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
            r[i], r[j] = r[j], r[i]
        }
        return string(r)
    }

    關鍵區別在於 Reverse 現在迭代字串中的每個 rune,而不是每個 byte。請注意,這只是一個範例,並不能正確處理 組合字元

    執行程式碼

    1. 使用 go test 執行測試

      $ go test
      PASS
      ok      example/fuzz  0.016s

      測試現在通過!

    2. 再次使用 go test -fuzz 進行模糊測試,看看是否有任何新錯誤。

      $ go test -fuzz=Fuzz
      fuzz: elapsed: 0s, gathering baseline coverage: 0/37 completed
      fuzz: minimizing 506-byte failing input file...
      fuzz: elapsed: 0s, gathering baseline coverage: 5/37 completed
      --- FAIL: FuzzReverse (0.02s)
          --- FAIL: FuzzReverse (0.00s)
              reverse_test.go:33: Before: "\x91", after: "�"
      
          Failing input written to testdata/fuzz/FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c
          To re-run:
          go test -run=FuzzReverse/1ffc28f7538e29d79fce69fef20ce5ea72648529a9ca10bea392bcff28cd015c
      FAIL
      exit status 1
      FAIL    example/fuzz  0.032s

      我們可以看到,字串在反轉兩次後不同於原始字串。這次輸入本身是無效的 unicode。如果我們使用字串進行模糊測試,這怎麼可能?

      讓我們再次除錯。

    修復雙反轉錯誤

    在本節中,你將除錯雙反轉失敗並修復錯誤。

    在繼續之前,請花點時間思考並嘗試自己解決問題。

    診斷錯誤

    和以前一樣,有幾種方法可以除錯此失敗。在這種情況下,使用 除錯器 將是一個很好的方法。

    在本教學中,我們將在 Reverse 函數中記錄有用的除錯資訊。

    仔細查看反轉後的字串以發現錯誤。在 Go 中,字串是唯讀的位元組切片,可以包含無效 UTF-8 的位元組。原始字串是包含一個位元組 '\x91' 的位元組切片。當輸入字串設定為 []rune 時,Go 將位元組切片編碼為 UTF-8,並用 UTF-8 字元 � 替換該位元組。當我們比較替換的 UTF-8 字元和輸入位元組切片時,它們顯然不相等。

    撰寫程式碼

    1. 在你的文字編輯器中,將 Reverse 函數替換為以下內容。

      func Reverse(s string) string {
          fmt.Printf("input: %q\n", s)
          r := []rune(s)
          fmt.Printf("runes: %q\n", r)
          for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
              r[i], r[j] = r[j], r[i]
          }
          return string(r)
      }

      這將幫助我們理解將字串轉換為符文切片時出錯的原因。

    執行程式碼

    這次,我們只想執行失敗的測試以檢查日誌。為此,我們將使用 go test -run

    要執行 FuzzXxx/testdata 中的特定語料庫條目,你可以向 -run 提供 {FuzzTestName}/{filename}。這在除錯時很有幫助。在這種情況下,將 -run 標誌設定為失敗測試的確切雜湊值。從你的終端複製並貼上唯一雜湊值;它將不同於下面的值。

    $ go test -run=FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0
    input: "\x91"
    runes: ['�']
    input: "�"
    runes: ['�']
    --- FAIL: FuzzReverse (0.00s)
        --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)
            reverse_test.go:16: Number of runes: orig=1, rev=1, doubleRev=1
            reverse_test.go:18: Before: "\x91", after: "�"
    FAIL
    exit status 1
    FAIL    example/fuzz    0.145s```
    
    既然知道輸入是無效的 Unicode,讓我們修復 `Reverse` 函數中的錯誤。
    
    ### 修復錯誤
    
    為了解決這個問題,如果 `Reverse` 的輸入不是有效的 UTF-8,我們返回一個錯誤。
    
    #### 撰寫程式碼
    
    1. 在你的文字編輯器中,將現有的 `Reverse` 函數替換為以下內容。
    
       ```go
       func Reverse(s string) (string, error) {
           if !utf8.ValidString(s) {
               return s, errors.New("input is not valid UTF-8")
           }
           r := []rune(s)
           for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
               r[i], r[j] = r[j], r[i]
           }
           return string(r), nil
       }

    如果輸入字串包含無效的 UTF-8 字元,此更改將返回錯誤。

    1. 由於 Reverse 函數現在返回錯誤,修改 main 函數以忽略額外的錯誤值。將現有的 main 函數替換為以下內容。

      func main() {
          input := "The quick brown fox jumped over the lazy dog"
          rev, revErr := Reverse(input)
          doubleRev, doubleRevErr := Reverse(rev)
          fmt.Printf("original: %q\n", input)
          fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
          fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
      }

      這些對 Reverse 的呼叫應返回 nil 錯誤,因為輸入字串是有效的 UTF-8。

    2. 你需要匯入 errorsunicode/utf8 套件。 main.go 中的匯入語句應如下所示。

      import (
          "errors"
          "fmt"
          "unicode/utf8"
      )
    3. 修改 reverse_test.go 檔案以檢查錯誤,如果產生錯誤則跳過測試。

      func FuzzReverse(f *testing.F) {
          testcases := []string {"Hello, world", " ", "!12345"}
          for _, tc := range testcases {
              f.Add(tc)  // 使用 f.Add 提供種子語料庫
          }
          f.Fuzz(func(t *testing.T, orig string) {
              rev, err1 := Reverse(orig)
              if err1 != nil {
                  return
              }
              doubleRev, err2 := Reverse(rev)
              if err2 != nil {
                   return
              }
              if orig != doubleRev {
                  t.Errorf("Before: %q, after: %q", orig, doubleRev)
              }
              if utf8.ValidString(orig) && !utf8.ValidString(rev) {
                  t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
              }
          })
      }

      你也可以呼叫 t.Skip() 來停止該模糊輸入的執行,而不是返回。

    執行程式碼

    1. 使用 go test 執行測試

      $ go test
      PASS
      ok      example/fuzz  0.019s
    2. 使用 go test -fuzz=Fuzz 進行模糊測試,幾秒後使用 ctrl-C 停止模糊測試。模糊測試將一直執行,直到遇到失敗輸入,除非你傳遞 -fuzztime 標誌。預設情況下,如果沒有發生失敗,它將永遠執行,並且可以用 ctrl-C 中斷。

    $ go test -fuzz=Fuzz
    fuzz: elapsed: 0s, gathering baseline coverage: 0/38 completed
    fuzz: elapsed: 0s, gathering baseline coverage: 38/38 completed, now fuzzing with 4 workers
    fuzz: elapsed: 3s, execs: 86342 (28778/sec), new interesting: 2 (total: 35)
    fuzz: elapsed: 6s, execs: 193490 (35714/sec), new interesting: 4 (total: 37)
    fuzz: elapsed: 9s, execs: 304390 (36961/sec), new interesting: 4 (total: 37)
    ...
    fuzz: elapsed: 3m45s, execs: 7246222 (32357/sec), new interesting: 8 (total: 41)
    ^Cfuzz: elapsed: 3m48s, execs: 7335316 (31648/sec), new interesting: 8 (total: 41)
    PASS
    ok      example/fuzz  228.000s
    1. 使用 go test -fuzz=Fuzz -fuzztime 30s 進行模糊測試,如果沒有發現失敗,將在 30 秒後退出。

      $ go test -fuzz=Fuzz -fuzztime 30s
      fuzz: elapsed: 0s, gathering baseline coverage: 0/5 completed
      fuzz: elapsed: 0s, gathering baseline coverage: 5/5 completed, now fuzzing with 4 workers
      fuzz: elapsed: 3s, execs: 80290 (26763/sec), new interesting: 12 (total: 12)
      fuzz: elapsed: 6s, execs: 210803 (43501/sec), new interesting: 14 (total: 14)
      fuzz: elapsed: 9s, execs: 292882 (27360/sec), new interesting: 14 (total: 14)
      fuzz: elapsed: 12s, execs: 371872 (26329/sec), new interesting: 14 (total: 14)
      fuzz: elapsed: 15s, execs: 517169 (48433/sec), new interesting: 15 (total: 15)
      fuzz: elapsed: 18s, execs: 663276 (48699/sec), new interesting: 15 (total: 15)
      fuzz: elapsed: 21s, execs: 771698 (36143/sec), new interesting: 15 (total: 15)
      fuzz: elapsed: 24s, execs: 924768 (50990/sec), new interesting: 16 (total: 16)
      fuzz: elapsed: 27s, execs: 1082025 (52427/sec), new interesting: 17 (total: 17)
      fuzz: elapsed: 30s, execs: 1172817 (30281/sec), new interesting: 17 (total: 17)
      fuzz: elapsed: 31s, execs: 1172817 (0/sec), new interesting: 17 (total: 17)
      PASS
      ok      example/fuzz  31.025s

      模糊測試通過!

      除了 -fuzz 標誌外,go test 還添加了幾種新標誌,可以在 文件 中查看。

      有關模糊測試輸出中使用的術語的更多資訊,請參閱 Go 模糊測試。例如,「new interesting」 指的是擴展現有模糊測試語料庫程式碼覆蓋率的輸入。隨著模糊測試開始,「new interesting」 輸入的數量可以預期會急劇增加,隨著發現新的程式碼路徑而多次激增,然後隨著時間推移而減少。

    結論

    幹得好!你剛剛介紹了 Go 中的模糊測試。

    下一步是選擇你程式碼中的一個函數進行模糊測試,並嘗試一下!如果模糊測試在你的程式碼中發現了一個 bug,考慮將其加入到 獎盃案例

    如果你遇到任何問題或有功能建議,提交問題

    有關該功能的討論和一般反饋,你也可以參與 Gophers Slack 中的 #fuzzing 頻道

    有關進一步閱讀,請查看 go.dev/security/fuzz 上的文件。

    完整程式碼

    --- main.go ---

    package main
    
    import (
        "errors"
        "fmt"
        "unicode/utf8"
    )
    
    func main() {
        input := "The quick brown fox jumped over the lazy dog"
        rev, revErr := Reverse(input)
        doubleRev, doubleRevErr := Reverse(rev)
        fmt.Printf("original: %q\n", input)
        fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
        fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
    }
    
    func Reverse(s string) (string, error) {
        if !utf8.ValidString(s) {
            return s, errors.New("input is not valid UTF-8")
        }
        r := []rune(s)
        for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
            r[i], r[j] = r[j], r[i]
        }
        return string(r), nil
    }

    --- reverse_test.go ---

    package main
    
    import (
        "testing"
        "unicode/utf8"
    )
    
    func FuzzReverse(f *testing.F) {
        testcases := []string{"Hello, world", " ", "!12345"}
        for _, tc := range testcases {
            f.Add(tc) // 使用 f.Add 提供種子語料庫
        }
        f.Fuzz(func(t *testing.T, orig string) {
            rev, err1 := Reverse(orig)
            if err1 != nil {
                return
            }
            doubleRev, err2 := Reverse(rev)
            if err2 != nil {
                return
            }
            if orig != doubleRev {
                t.Errorf("Before: %q, after: %q", orig, doubleRev)
            }
            if utf8.ValidString(orig) && !utf8.ValidString(rev) {
                t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
            }
        })
    }

    返回頂部