前提条件

  • Go 1.18 以降のインストール。 インストール手順については、Goのインストールを参照してください。
  • コードを編集するためのツール。 お使いのテキストエディタで問題ありません。
  • コマンドターミナル。 GoはLinuxやMacの任意のターミナル、WindowsのPowerShellやcmdでうまく動作します。
  • ファジングをサポートする環境。 現在、GoのファジングはAMD64およびARM64アーキテクチャでのみ利用可能です。

コード用のフォルダを作成する

まず、作成するコード用のフォルダを作成します。

  • 1. コマンドプロンプトを開き、ホームディレクトリに移動します。
    LinuxまたはMacの場合:
    1. $ cd
    Windowsの場合:
    1. C:\> cd %HOMEPATH%
    チュートリアルの残りの部分では、プロンプトとして$が表示されます。使用するコマンドはWindowsでも動作します。
  • 2. コマンドプロンプトから、fuzzという名前のコード用のディレクトリを作成します。
    1. $ mkdir fuzz
    2. $ cd fuzz
  • 3. コードを保持するためのモジュールを作成します。
    go mod initコマンドを実行し、新しいコードのモジュールパスを指定します。
    1. $ go mod init example/fuzz
    2. go: creating new go.mod: module example/fuzz
    注意: 本番コードの場合、より具体的なニーズに応じたモジュールパスを指定します。詳細については、依存関係の管理を参照してください。

次に、後でファジングするために文字列を反転させる簡単なコードを追加します。

テスト用のコードを追加する

このステップでは、文字列を反転させる関数を追加します。

コードを書く

  • 1. テキストエディタを使用して、fuzzディレクトリにmain.goという名前のファイルを作成します。
  • 2. main.goのファイルの先頭に、次のパッケージ宣言を貼り付けます。
    1. package main
    スタンドアロンプログラム(ライブラリとは対照的に)は常にパッケージmainにあります。
  • 3. パッケージ宣言の下に、次の関数宣言を貼り付けます。
    1. func Reverse(s string) string {
    2. b := []byte(s)
    3. for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
    4. b[i], b[j] = b[j], b[i]
    5. }
    6. return string(b)
    7. }
    この関数はstringを受け取り、byteずつループして、最後に反転した文字列を返します。
    注意: このコードはgolang.org/x/example内のstringutil.Reverse関数に基づいています。
  • 4. main.goの先頭で、パッケージ宣言の下に、文字列を初期化し、反転させ、出力を印刷し、繰り返すための次のmain関数を貼り付けます。
    1. func main() {
    2. input := "The quick brown fox jumped over the lazy dog"
    3. rev := Reverse(input)
    4. doubleRev := Reverse(rev)
    5. fmt.Printf("original: %q\n", input)
    6. fmt.Printf("reversed: %q\n", rev)
    7. fmt.Printf("reversed again: %q\n", doubleRev)
    8. }
    この関数は、いくつかのReverse操作を実行し、その後出力をコマンドラインに印刷します。これは、コードの動作を確認したり、デバッグに役立つ場合があります。
  • 5. main関数はfmtパッケージを使用するため、インポートする必要があります。
    最初のコード行は次のようになります:

    1. package main
    2. import "fmt"

コードを実行する

main.goを含むディレクトリのコマンドラインから、コードを実行します。

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

元の文字列、反転した結果、再度反転した結果(元の文字列と同じ)を確認できます。

コードが実行されているので、テストを行う時間です。

ユニットテストを追加する

このステップでは、Reverse関数の基本的なユニットテストを作成します。

コードを書く

  • 1. テキストエディタを使用して、fuzzディレクトリにreverse_test.goという名前のファイルを作成します。
  • 2. reverse_test.goに次のコードを貼り付けます。

    1. package main
    2. import (
    3. "testing"
    4. )
    5. func TestReverse(t *testing.T) {
    6. testcases := []struct {
    7. in, want string
    8. }{
    9. {"Hello, world", "dlrow ,olleH"},
    10. {" ", " "},
    11. {"!12345", "54321!"},
    12. }
    13. for _, tc := range testcases {
    14. rev := Reverse(tc.in)
    15. if rev != tc.want {
    16. t.Errorf("Reverse: %q, want %q", rev, tc.want)
    17. }
    18. }
    19. }

    このシンプルなテストは、リストされた入力文字列が正しく反転されることを確認します。

コードを実行する

go testを使用してユニットテストを実行します。

  1. $ go test
  2. PASS
  3. ok example/fuzz 0.013s

次に、ユニットテストをファジングテストに変更します。

ファジングテストを追加する

ユニットテストには制限があります。すなわち、各入力は開発者によってテストに追加されなければなりません。ファジングの利点の一つは、コードの入力を生成し、テストケースが到達しなかったエッジケースを特定する可能性があることです。

このセクションでは、ユニットテストをファジングテストに変換し、より少ない作業でより多くの入力を生成できるようにします!

ユニットテスト、ベンチマーク、ファジングテストを同じ*_test.goファイルに保持できますが、この例ではユニットテストをファジングテストに変換します。

コードを書く

テキストエディタで、reverse_test.go内のユニットテストを次のファジングテストに置き換えます。

  1. func FuzzReverse(f *testing.F) {
  2. testcases := []string{"Hello, world", " ", "!12345"}
  3. for _, tc := range testcases {
  4. f.Add(tc) // Use f.Add to provide a seed corpus
  5. }
  6. f.Fuzz(func(t *testing.T, orig string) {
  7. rev := Reverse(orig)
  8. doubleRev := Reverse(rev)
  9. if orig != doubleRev {
  10. t.Errorf("Before: %q, after: %q", orig, doubleRev)
  11. }
  12. if utf8.ValidString(orig) && !utf8.ValidString(rev) {
  13. t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
  14. }
  15. })
  16. }

ファジングにもいくつかの制限があります。ユニットテストでは、Reverse関数の期待される出力を予測し、実際の出力がその期待に合致するかを確認できました。

例えば、テストケースReverse("Hello, world")では、ユニットテストは返り値を"dlrow ,olleH"として指定しています。

ファジングでは、入力を制御できないため、期待される出力を予測することはできません。

ただし、Reverse関数のいくつかの特性をファジングテストで確認できます。このファジングテストで確認される2つの特性は次のとおりです:

  • 1. 文字列を2回反転させると、元の値が保持される
  • 2. 反転した文字列は、有効なUTF-8としてその状態を保持する。

ユニットテストとファジングテストの構文の違いに注意してください:

  • 関数はTestXxxの代わりにFuzzXxxで始まり、*testing.Fの代わりに*testing.Tを取ります。
  • t.Runの実行が期待される場所では、f.Fuzzが表示され、ファジングターゲット関数のパラメータが*testing.Tであり、ファジングされる型です。ユニットテストからの入力は、f.Addを使用してシードコーパス入力として提供されます。

新しいパッケージunicode/utf8がインポートされていることを確認してください。

  1. package main
  2. import (
  3. "testing"
  4. "unicode/utf8"
  5. )

ユニットテストがファジングテストに変換されたので、再度テストを実行する時間です。

コードを実行する

  • 1. ファジングせずにファジングテストを実行して、シード入力が通過することを確認します。
    1. $ go test
    2. PASS
    3. ok example/fuzz 0.013s
    他のテストがそのファイルにある場合は、go test -run=FuzzReverseを実行してファジングテストのみを実行することもできます。
  • 2. ファジングを使用してFuzzReverseを実行し、ランダムに生成された文字列入力が失敗を引き起こすかどうかを確認します。これは、go testを使用して新しいフラグ-fuzzをパラメータFuzzに設定して実行されます。以下のコマンドをコピーしてください。

    1. $ go test -fuzz=Fuzz

    もう一つの便利なフラグは-fuzztimeで、ファジングにかかる時間を制限します。例えば、以下のテストで-fuzztime 10sを指定すると、以前に失敗が発生しなかった場合、デフォルトで10秒経過後にテストが終了します。詳細なテストフラグについては、cmd/goのドキュメントのthis sectionを参照してください。
    今、コピーしたコマンドを実行します。

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

    ファジング中に失敗が発生し、その問題を引き起こした入力は、次回go testが呼び出されるときに実行されるシードコーパスファイルに書き込まれます。-fuzzフラグなしでも。失敗を引き起こした入力を表示するには、テキストエディタでtestdata/fuzz/FuzzReverseディレクトリに書き込まれたコーパスファイルを開きます。シードコーパスファイルには異なる文字列が含まれている可能性がありますが、形式は同じです。

    1. go test fuzz v1
    2. string("泃")

    コーパスファイルの最初の行はエンコーディングバージョンを示します。次の各行は、コーパスエントリを構成する各型の値を表します。ファジングターゲットは1つの入力しか受け取らないため、バージョンの後には1つの値しかありません。

  • 3. go test-fuzzフラグなしで再度実行します。新しい失敗したシードコーパスエントリが使用されます:
    1. $ go test
    2. --- FAIL: FuzzReverse (0.00s)
    3. --- FAIL: FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a (0.00s)
    4. reverse_test.go:20: Reverse produced invalid string
    5. FAIL
    6. exit status 1
    7. FAIL example/fuzz 0.016s
    テストが失敗したので、デバッグの時間です。

無効な文字列エラーを修正する

このセクションでは、失敗をデバッグし、バグを修正します。

進む前に、これについて考えたり、自分で問題を修正しようとしたりする時間を自由に使ってください。

エラーを診断する

このエラーをデバッグする方法はいくつかあります。VS Codeをテキストエディタとして使用している場合は、デバッガを設定することで調査できます。

このチュートリアルでは、便利なデバッグ情報をターミナルにログ出力します。

まず、utf8.ValidStringのドキュメントを考慮してください。

  1. ValidString reports whether s consists entirely of valid UTF-8-encoded runes.

現在のReverse関数は文字列をバイト単位で反転させており、これが問題です。元の文字列のUTF-8エンコードされたルーンを保持するためには、文字列をルーン単位で反転させる必要があります。

入力(この場合は中国語の文字)が反転されたときにReverseが無効な文字列を生成する理由を調べるために、反転された文字列のルーン数を確認できます。

コードを書く

テキストエディタで、FuzzReverse内のファジングターゲットを次のように置き換えます。

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

このt.Logf行は、エラーが発生した場合や、-vでテストを実行する場合にコマンドラインに出力します。これにより、この特定の問題をデバッグするのに役立ちます。

コードを実行する

go testを使用してテストを実行します。

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

シードコーパス全体は、すべての文字が単一バイトである文字列を使用していました。しかし、泃のような文字は複数のバイトを必要とする場合があります。したがって、文字列をバイト単位で反転させると、マルチバイト文字が無効になります。

注意: Goが文字列をどのように扱うかに興味がある場合は、Goにおける文字列、バイト、ルーン、文字のブログ記事を読んで、より深く理解してください。

バグをよりよく理解した上で、Reverse関数のエラーを修正します。

エラーを修正する

  1. <a name="write-the-code-4"></a>
  2. #### コードを書く
  3. テキストエディタで、既存のReverse()関数を次のように置き換えます。
  4. ``````bash
  5. func Reverse(s string) string {
  6. r := []rune(s)
  7. for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
  8. r[i], r[j] = r[j], r[i]
  9. }
  10. return string(r)
  11. }
  12. `

重要な違いは、Reverseが文字列内の各runeを反復処理しているのではなく、各byteを反復処理していることです。

コードを実行する

  • 1. go testを使用してテストを実行します。
    1. $ go test
    2. PASS
    3. ok example/fuzz 0.016s
    テストは現在通過します!
  • 2. go test -fuzzで再度ファジングして、新しいバグがないか確認します。

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

    文字列が2回反転した後、元の文字列とは異なることがわかります。今回は入力自体が無効なunicodeです。文字列でファジングしているのに、これはどういうことでしょうか?
    再度デバッグしましょう。

ダブルリバースエラーを修正する

このセクションでは、ダブルリバースの失敗をデバッグし、バグを修正します。

進む前に、これについて考えたり、自分で問題を修正しようとしたりする時間を自由に使ってください。

エラーを診断する

以前と同様に、この失敗をデバッグする方法はいくつかあります。この場合、デバッガを使用するのが良いアプローチです。

このチュートリアルでは、Reverse関数内に便利なデバッグ情報をログ出力します。

反転された文字列を注意深く見て、エラーを見つけてください。Goでは、文字列は読み取り専用のバイトスライスであり、有効なUTF-8でないバイトを含むことがあります。元の文字列は1バイトのバイトスライス'\x91'です。入力文字列が[]runeに設定されると、GoはバイトスライスをUTF-8にエンコードし、バイトをUTF-8文字�に置き換えます。置き換えられたUTF-8文字と入力バイトスライスを比較すると、明らかに等しくありません。

コードを書く

  • 1. テキストエディタで、Reverse関数を次のように置き換えます。
    1. func Reverse(s string) string {
    2. fmt.Printf("input: %q\n", s)
    3. r := []rune(s)
    4. fmt.Printf("runes: %q\n", r)
    5. for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
    6. r[i], r[j] = r[j], r[i]
    7. }
    8. return string(r)
    9. }
    これにより、文字列をルーンのスライスに変換する際に何が問題なのかを理解するのに役立ちます。

コードを実行する

今回は、ログを確認するために失敗したテストのみを実行したいと思います。これを行うために、go test -runを使用します。

FuzzXxx/testdata内の特定のコーパスエントリを実行するには、{FuzzTestName}/{filename}を-runに提供できます。これはデバッグ時に役立ちます。この場合、-runフラグを失敗したテストの正確なハッシュに設定します。ターミナルからユニークなハッシュをコピーして貼り付けてください。以下のものとは異なります。

  1. $ go test -run=FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0
  2. input: "\x91"
  3. runes: ['�']
  4. input: "�"
  5. runes: ['�']
  6. --- FAIL: FuzzReverse (0.00s)
  7. --- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)
  8. reverse_test.go:16: Number of runes: orig=1, rev=1, doubleRev=1
  9. reverse_test.go:18: Before: "\x91", after: "�"
  10. FAIL
  11. exit status 1
  12. FAIL example/fuzz 0.145s

入力が無効なunicodeであることがわかったので、Reverse関数のエラーを修正しましょう。

エラーを修正する

この問題を修正するために、Reverseへの入力が有効なUTF-8でない場合はエラーを返すようにします。

コードを書く

  • 1. テキストエディタで、既存のReverse関数を次のように置き換えます。
    1. func Reverse(s string) (string, error) {
    2. if !utf8.ValidString(s) {
    3. return s, errors.New("input is not valid UTF-8")
    4. }
    5. r := []rune(s)
    6. for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
    7. r[i], r[j] = r[j], r[i]
    8. }
    9. return string(r), nil
    10. }
    この変更により、入力文字列に有効なUTF-8でない文字が含まれている場合にエラーが返されます。
  • 2. Reverse関数がエラーを返すようになったので、main関数を修正して、余分なエラー値を破棄します。既存のmain関数を次のように置き換えます。
    1. func main() {
    2. input := "The quick brown fox jumped over the lazy dog"
    3. rev, revErr := Reverse(input)
    4. doubleRev, doubleRevErr := Reverse(rev)
    5. fmt.Printf("original: %q\n", input)
    6. fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
    7. fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
    8. }
    これらのReverseへの呼び出しは、入力文字列が有効なUTF-8であるため、nilエラーを返す必要があります。
  • 3. errorsおよびunicode/utf8パッケージをインポートする必要があります。main.goのインポート文は次のようになります。
    1. import (
    2. "errors"
    3. "fmt"
    4. "unicode/utf8"
    5. )
  • 4. reverse_test.goファイルを修正して、エラーをチェックし、エラーが生成された場合はテストをスキップします。
    1. func FuzzReverse(f *testing.F) {
    2. testcases := []string {"Hello, world", " ", "!12345"}
    3. for _, tc := range testcases {
    4. f.Add(tc) // Use f.Add to provide a seed corpus
    5. }
    6. f.Fuzz(func(t *testing.T, orig string) {
    7. rev, err1 := Reverse(orig)
    8. if err1 != nil {
    9. return
    10. }
    11. doubleRev, err2 := Reverse(rev)
    12. if err2 != nil {
    13. return
    14. }
    15. if orig != doubleRev {
    16. t.Errorf("Before: %q, after: %q", orig, doubleRev)
    17. }
    18. if utf8.ValidString(orig) && !utf8.ValidString(rev) {
    19. t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
    20. }
    21. })
    22. }
    返す代わりに、t.Skip()を呼び出して、そのファジング入力の実行を停止することもできます。

コードを実行する

  • 1. go testを使用してテストを実行します。
    1. $ go test
    2. PASS
    3. ok example/fuzz 0.019s
  • 2. go test -fuzz=Fuzzでファジングし、数秒後にctrl-Cでファジングを停止します。ファジングテストは、失敗する入力に遭遇するまで実行されます。-fuzztimeフラグを渡さない限り、デフォルトでは失敗が発生しない限り永遠に実行されます。このプロセスはctrl-Cで中断できます。
  1. $ go test -fuzz=Fuzz
  2. fuzz: elapsed: 0s, gathering baseline coverage: 0/38 completed
  3. fuzz: elapsed: 0s, gathering baseline coverage: 38/38 completed, now fuzzing with 4 workers
  4. fuzz: elapsed: 3s, execs: 86342 (28778/sec), new interesting: 2 (total: 35)
  5. fuzz: elapsed: 6s, execs: 193490 (35714/sec), new interesting: 4 (total: 37)
  6. fuzz: elapsed: 9s, execs: 304390 (36961/sec), new interesting: 4 (total: 37)
  7. ...
  8. fuzz: elapsed: 3m45s, execs: 7246222 (32357/sec), new interesting: 8 (total: 41)
  9. ^Cfuzz: elapsed: 3m48s, execs: 7335316 (31648/sec), new interesting: 8 (total: 41)
  10. PASS
  11. ok example/fuzz 228.000s
  • 1. go test -fuzz=Fuzz -fuzztime 30sでファジングし、失敗が見つからなかった場合は30秒間ファジングします。
    1. $ go test -fuzz=Fuzz -fuzztime 30s
    2. fuzz: elapsed: 0s, gathering baseline coverage: 0/5 completed
    3. fuzz: elapsed: 0s, gathering baseline coverage: 5/5 completed, now fuzzing with 4 workers
    4. fuzz: elapsed: 3s, execs: 80290 (26763/sec), new interesting: 12 (total: 12)
    5. fuzz: elapsed: 6s, execs: 210803 (43501/sec), new interesting: 14 (total: 14)
    6. fuzz: elapsed: 9s, execs: 292882 (27360/sec), new interesting: 14 (total: 14)
    7. fuzz: elapsed: 12s, execs: 371872 (26329/sec), new interesting: 14 (total: 14)
    8. fuzz: elapsed: 15s, execs: 517169 (48433/sec), new interesting: 15 (total: 15)
    9. fuzz: elapsed: 18s, execs: 663276 (48699/sec), new interesting: 15 (total: 15)
    10. fuzz: elapsed: 21s, execs: 771698 (36143/sec), new interesting: 15 (total: 15)
    11. fuzz: elapsed: 24s, execs: 924768 (50990/sec), new interesting: 16 (total: 16)
    12. fuzz: elapsed: 27s, execs: 1082025 (52427/sec), new interesting: 17 (total: 17)
    13. fuzz: elapsed: 30s, execs: 1172817 (30281/sec), new interesting: 17 (total: 17)
    14. fuzz: elapsed: 31s, execs: 1172817 (0/sec), new interesting: 17 (total: 17)
    15. PASS
    16. ok example/fuzz 31.025s
    ファジングが成功しました!
    -fuzzフラグに加えて、go testにいくつかの新しいフラグが追加され、ドキュメントで確認できます。
    ファジング出力で使用される用語についての詳細は、Go Fuzzingを参照してください。例えば、「新しい興味深い」は、既存のファジングテストコーパスのコードカバレッジを拡張する入力を指します。「新しい興味深い」入力の数は、ファジングが始まると急激に増加し、新しいコードパスが発見されると数回スパイクし、その後時間とともに減少します。

結論

素晴らしい! Goにおけるファジングを紹介しました。

次のステップは、ファジングしたいコード内の関数を選択し、試してみることです!ファジングがコード内のバグを見つけた場合は、それをtrophy caseに追加することを検討してください。

問題が発生した場合や機能のアイデアがある場合は、issueを提出してください

機能に関する議論や一般的なフィードバックについては、Gophers Slackの#fuzzing channelにも参加できます。

さらなる読み物については、go.dev/security/fuzzのドキュメントをチェックしてください。

完成したコード

— main.go —

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "unicode/utf8"
  6. )
  7. func main() {
  8. input := "The quick brown fox jumped over the lazy dog"
  9. rev, revErr := Reverse(input)
  10. doubleRev, doubleRevErr := Reverse(rev)
  11. fmt.Printf("original: %q\n", input)
  12. fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
  13. fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
  14. }
  15. func Reverse(s string) (string, error) {
  16. if !utf8.ValidString(s) {
  17. return s, errors.New("input is not valid UTF-8")
  18. }
  19. r := []rune(s)
  20. for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
  21. r[i], r[j] = r[j], r[i]
  22. }
  23. return string(r), nil
  24. }

— reverse_test.go —

  1. package main
  2. import (
  3. "testing"
  4. "unicode/utf8"
  5. )
  6. func FuzzReverse(f *testing.F) {
  7. testcases := []string{"Hello, world", " ", "!12345"}
  8. for _, tc := range testcases {
  9. f.Add(tc) // Use f.Add to provide a seed corpus
  10. }
  11. f.Fuzz(func(t *testing.T, orig string) {
  12. rev, err1 := Reverse(orig)
  13. if err1 != nil {
  14. return
  15. }
  16. doubleRev, err2 := Reverse(rev)
  17. if err2 != nil {
  18. return
  19. }
  20. if orig != doubleRev {
  21. t.Errorf("Before: %q, after: %q", orig, doubleRev)
  22. }
  23. if utf8.ValidString(orig) && !utf8.ValidString(rev) {
  24. t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
  25. }
  26. })
  27. }

トップに戻る