教程:模糊测试入门

本教程介绍了 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`,逐 `byte` 循环,并在末尾返回反转的字符串。

    注意:此代码基于 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)  // Use f.Add to provide a seed corpus
    }
    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
    

    模糊测试时发生故障,导致问题的输入将写入种子语料库文件,该文件将在下次调用 `go test` 时运行,即使没有 `-fuzz` 标志。要查看导致故障的输入,请在文本编辑器中打开写入 testdata/fuzz/FuzzReverse 目录的语料库文件。您的种子语料库文件可能包含不同的字符串,但格式将相同。

    go test fuzz v1
    string("泃")
    

    语料库文件的第一行表示编码版本。每个后续行表示构成语料库条目的每种类型的值。由于模糊目标只接受 1 个输入,因此版本之后只有 1 个值。

  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 reports whether s consists entirely of valid UTF-8-encoded runes.

当前的 `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) 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` 函数。

    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 的字符,此更改将返回错误。

  2. 由于 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。

  3. 您需要导入 errors 和 unicode/utf8 包。main.go 中的导入语句应如下所示。

    import (
        "errors"
        "fmt"
        "unicode/utf8"
    )
    
  4. 修改 reverse_test.go 文件以检查错误,如果生成错误则通过返回跳过测试。

    func FuzzReverse(f *testing.F) {
        testcases := []string {"Hello, world", " ", "!12345"}
        for _, tc := range testcases {
            f.Add(tc)  // Use f.Add to provide a seed corpus
        }
        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 语言模糊测试的旅程。

下一步是选择您代码中想要进行模糊测试的函数,并尝试一下!如果模糊测试在您的代码中发现了错误,请考虑将其添加到荣誉案例中。

如果您遇到任何问题或有功能想法,请提交问题

有关该功能的讨论和一般反馈,您还可以参与 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) // Use f.Add to provide a seed corpus
    }
    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)
        }
    })
}

返回顶部