go/src/testing/fuzz.go
Katie Hockman 33c89be3b7 [dev.fuzz] testing: add basic go command support for fuzzing
This change adds support for a -fuzz flag in the go command, and sets up
the groundwork for native fuzzing support. These functions are no-ops
for now, but will be built out and tested in future PRs.

Change-Id: I58e78eceada5799bcb73acc4ae5a20372badbf40
Reviewed-on: https://go-review.googlesource.com/c/go/+/251441
Run-TryBot: Katie Hockman <katie@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
2020-12-04 19:17:29 +01:00

197 lines
5.7 KiB
Go

// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testing
import (
"flag"
"fmt"
"os"
"time"
)
func initFuzzFlags() {
matchFuzz = flag.String("test.fuzz", "", "run the fuzz target matching `regexp`")
}
var matchFuzz *string
// InternalFuzzTarget is an internal type but exported because it is cross-package;
// it is part of the implementation of the "go test" command.
type InternalFuzzTarget struct {
Name string
Fn func(f *F)
}
// F is a type passed to fuzz targets for fuzz testing.
type F struct {
common
context *fuzzContext
corpus []corpusEntry // corpus is the in-memory corpus
result FuzzResult // result is the result of running the fuzz target
fuzzFunc func(f *F) // fuzzFunc is the function which makes up the fuzz target
fuzz bool // fuzz indicates whether or not the fuzzing engine should run
}
// corpus corpusEntry
type corpusEntry struct {
b []byte
}
// Add will add the arguments to the seed corpus for the fuzz target. This
// cannot be invoked after or within the Fuzz function. The args must match
// those in the Fuzz function.
func (f *F) Add(args ...interface{}) {
return
}
// Fuzz runs the fuzz function, ff, for fuzz testing. It runs ff in a separate
// goroutine. Only one call to Fuzz is allowed per fuzz target, and any
// subsequent calls will panic. If ff fails for a set of arguments, those
// arguments will be added to the seed corpus.
func (f *F) Fuzz(ff interface{}) {
return
}
// FuzzResult contains the results of a fuzz run.
type FuzzResult struct {
N int // The number of iterations.
T time.Duration // The total time taken.
Crasher corpusEntry // Crasher is the corpus entry that caused the crash
Error error // Error is the error from the crash
}
func (r FuzzResult) String() string {
s := ""
if len(r.Error.Error()) != 0 {
s = fmt.Sprintf("error: %s\ncrasher: %b", r.Error.Error(), r.Crasher)
}
return s
}
// fuzzContext holds all fields that are common to all fuzz targets.
type fuzzContext struct {
runMatch *matcher
fuzzMatch *matcher
}
// RunFuzzTargets is an internal function but exported because it is cross-package;
// it is part of the implementation of the "go test" command.
func RunFuzzTargets(matchString func(pat, str string) (bool, error), fuzzTargets []InternalFuzzTarget) (ok bool) {
_, ok = runFuzzTargets(matchString, fuzzTargets)
return ok
}
// runFuzzTargets runs the fuzz targets matching the pattern for -run. This will
// only run the f.Fuzz function for each seed corpus without using the fuzzing
// engine to generate or mutate inputs. If -fuzz matches a given fuzz target,
// then such test will be skipped and run later during fuzzing.
func runFuzzTargets(matchString func(pat, str string) (bool, error), fuzzTargets []InternalFuzzTarget) (ran, ok bool) {
ran, ok = true, true
if len(fuzzTargets) == 0 {
return false, ok
}
for _, ft := range fuzzTargets {
ctx := &fuzzContext{runMatch: newMatcher(matchString, *match, "-test.run")}
f := &F{
common: common{
signal: make(chan bool),
barrier: make(chan bool),
w: os.Stdout,
name: ft.Name,
},
context: ctx,
}
testName, matched, _ := ctx.runMatch.fullName(&f.common, f.name)
if !matched {
continue
}
if *matchFuzz != "" {
ctx.fuzzMatch = newMatcher(matchString, *matchFuzz, "-test.fuzz")
if _, doFuzz, partial := ctx.fuzzMatch.fullName(&f.common, f.name); doFuzz && !partial {
continue // this will be run later when fuzzed
}
}
if Verbose() {
f.chatty = newChattyPrinter(f.w)
}
if f.chatty != nil {
f.chatty.Updatef(f.name, "=== RUN %s\n", testName)
}
}
return ran, ok
}
// RunFuzzing is an internal function but exported because it is cross-package;
// it is part of the implementation of the "go test" command.
func RunFuzzing(matchString func(pat, str string) (bool, error), fuzzTargets []InternalFuzzTarget) (ok bool) {
_, ok = runFuzzing(matchString, fuzzTargets)
return ok
}
// runFuzzing runs the fuzz target matching the pattern for -fuzz. Only one such
// fuzz target must match. This will run the f.Fuzz function for each seed
// corpus and will run the fuzzing engine to generate and mutate new inputs
// against f.Fuzz.
func runFuzzing(matchString func(pat, str string) (bool, error), fuzzTargets []InternalFuzzTarget) (ran, ok bool) {
ran, ok = true, true
if len(fuzzTargets) == 0 {
return false, ok
}
ctx := &fuzzContext{
fuzzMatch: newMatcher(matchString, *matchFuzz, "-test.fuzz"),
}
if *matchFuzz == "" {
return false, true
}
f := &F{
common: common{
signal: make(chan bool),
barrier: make(chan bool),
w: os.Stdout,
},
context: ctx,
fuzz: true,
}
var (
ft InternalFuzzTarget
found int
)
for _, ft = range fuzzTargets {
testName, matched, _ := ctx.fuzzMatch.fullName(&f.common, ft.Name)
if matched {
found++
if found > 1 {
fmt.Fprintf(f.w, "testing: warning: -fuzz matched more than one target, won't run\n")
return false, ok
}
f.name = testName
}
}
if Verbose() {
f.chatty = newChattyPrinter(f.w)
}
if f.chatty != nil {
f.chatty.Updatef(f.name, "--- FUZZ %s\n", f.name)
}
return ran, ok
}
// Fuzz runs a single fuzz target. It is useful for creating
// custom fuzz targets that do not use the "go test" command.
//
// If fn depends on testing flags, then Init must be used to register
// those flags before calling Fuzz and before calling flag.Parse.
func Fuzz(fn func(f *F)) FuzzResult {
f := &F{
common: common{
signal: make(chan bool),
w: discard{},
},
fuzzFunc: fn,
}
// TODO(katiehockman): run the test
return f.result
}