aboutsummaryrefslogtreecommitdiff
path: root/src/testing/fuzz.go
diff options
context:
space:
mode:
authorJay Conrod <jayconrod@google.com>2020-10-02 16:05:33 -0400
committerFilippo Valsorda <filippo@golang.org>2020-12-04 19:17:29 +0100
commit8fabdcee8ff0537097ae68619ff515563bb2f986 (patch)
tree6e5ea2e5b8392f5c2d592a94440b6fa1e7489b1a /src/testing/fuzz.go
parent0a6f004cb1ed99bc225f4fe3cba5c2c5b901b442 (diff)
downloadgo-8fabdcee8ff0537097ae68619ff515563bb2f986.tar.xz
[dev.fuzz] internal/fuzz: coordinate fuzzing across workers
Package fuzz provides common fuzzing functionality for tests built with "go test" and for programs that use fuzzing functionality in the testing package. Change-Id: I3901c6a993a9adb8a93733ae1838b86dd78c7036 Reviewed-on: https://go-review.googlesource.com/c/go/+/259259 Run-TryBot: Jay Conrod <jayconrod@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Katie Hockman <katie@golang.org> Trust: Katie Hockman <katie@golang.org> Trust: Jay Conrod <jayconrod@google.com>
Diffstat (limited to 'src/testing/fuzz.go')
-rw-r--r--src/testing/fuzz.go129
1 files changed, 87 insertions, 42 deletions
diff --git a/src/testing/fuzz.go b/src/testing/fuzz.go
index 11bbd8fb16..6773b7161d 100644
--- a/src/testing/fuzz.go
+++ b/src/testing/fuzz.go
@@ -15,9 +15,13 @@ import (
func initFuzzFlags() {
matchFuzz = flag.String("test.fuzz", "", "run the fuzz target matching `regexp`")
+ isFuzzWorker = flag.Bool("test.fuzzworker", false, "coordinate with the parent process to fuzz random values")
}
-var matchFuzz *string
+var (
+ matchFuzz *string
+ isFuzzWorker *bool
+)
// InternalFuzzTarget is an internal type but exported because it is cross-package;
// it is part of the implementation of the "go test" command.
@@ -33,7 +37,6 @@ type F struct {
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 the fuzzing engine should run
}
// corpus corpusEntry
@@ -88,7 +91,6 @@ func (f *F) Fuzz(ff interface{}) {
t.Fail()
t.output = []byte(fmt.Sprintf(" %s", err))
}
- f.setRan()
f.inFuzzFn = false
t.signal <- true // signal that the test has finished
}()
@@ -100,30 +102,76 @@ func (f *F) Fuzz(ff interface{}) {
t.finished = true
}
- // Run the seed corpus first
- for _, c := range f.corpus {
- t := &T{
- common: common{
- signal: make(chan bool),
- w: f.w,
- chatty: f.chatty,
- },
- context: newTestContext(1, nil),
+ switch {
+ case f.context.coordinateFuzzing != nil:
+ // Fuzzing is enabled, and this is the test process started by 'go test'.
+ // Act as the coordinator process, and coordinate workers to perform the
+ // actual fuzzing.
+ seed := make([][]byte, len(f.corpus))
+ for i, e := range f.corpus {
+ seed[i] = e.b
}
- go run(t, c.b)
- <-t.signal
- if t.Failed() {
- f.Fail()
- errStr += string(t.output)
+ err := f.context.coordinateFuzzing(*parallel, seed)
+ f.setRan()
+ f.finished = true
+ f.result = FuzzResult{Error: err}
+ // TODO(jayconrod,katiehockman): Aggregate statistics across workers
+ // and set FuzzResult properly.
+
+ case f.context.runFuzzWorker != nil:
+ // Fuzzing is enabled, and this is a worker process. Follow instructions
+ // from the coordinator.
+ err := f.context.runFuzzWorker(func(input []byte) error {
+ t := &T{
+ common: common{
+ signal: make(chan bool),
+ w: f.w,
+ chatty: f.chatty,
+ },
+ context: newTestContext(1, nil),
+ }
+ go run(t, input)
+ <-t.signal
+ if t.Failed() {
+ return errors.New(string(t.output))
+ }
+ return nil
+ })
+ if err != nil {
+ // TODO(jayconrod,katiehockman): how should we handle a failure to
+ // communicate with the coordinator? Might be caused by the coordinator
+ // terminating early.
+ fmt.Fprintf(os.Stderr, "testing: communicating with fuzz coordinator: %v\n", err)
+ os.Exit(1)
}
- }
- f.finished = true
- if f.Failed() {
- f.result = FuzzResult{Error: errors.New(errStr)}
- return
- }
+ f.setRan()
+ f.finished = true
- // TODO: if f.fuzz is set, run fuzzing engine
+ default:
+ // Fuzzing is not enabled. Only run the seed corpus.
+ for _, c := range f.corpus {
+ t := &T{
+ common: common{
+ signal: make(chan bool),
+ w: f.w,
+ chatty: f.chatty,
+ },
+ context: newTestContext(1, nil),
+ }
+ go run(t, c.b)
+ <-t.signal
+ if t.Failed() {
+ f.Fail()
+ errStr += string(t.output)
+ }
+ f.setRan()
+ }
+ f.finished = true
+ if f.Failed() {
+ f.result = FuzzResult{Error: errors.New(errStr)}
+ return
+ }
+ }
}
func (f *F) report() {
@@ -202,8 +250,10 @@ func (r FuzzResult) String() string {
// fuzzContext holds all fields that are common to all fuzz targets.
type fuzzContext struct {
- runMatch *matcher
- fuzzMatch *matcher
+ runMatch *matcher
+ fuzzMatch *matcher
+ coordinateFuzzing func(int, [][]byte) error
+ runFuzzWorker func(func([]byte) error) error
}
// RunFuzzTargets is an internal function but exported because it is cross-package;
@@ -218,7 +268,7 @@ func RunFuzzTargets(matchString func(pat, str string) (bool, error), fuzzTargets
// engine to generate or mutate inputs.
func runFuzzTargets(matchString func(pat, str string) (bool, error), fuzzTargets []InternalFuzzTarget) (ran, ok bool) {
ok = true
- if len(fuzzTargets) == 0 {
+ if len(fuzzTargets) == 0 || *isFuzzWorker {
return ran, ok
}
ctx := &fuzzContext{runMatch: newMatcher(matchString, *match, "-test.run")}
@@ -249,25 +299,21 @@ func runFuzzTargets(matchString func(pat, str string) (bool, error), fuzzTargets
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 fuzzing engine to generate and
// mutate new inputs against the f.Fuzz function.
-func runFuzzing(matchString func(pat, str string) (bool, error), fuzzTargets []InternalFuzzTarget) (ran, ok bool) {
- if len(fuzzTargets) == 0 {
+//
+// If fuzzing is disabled (-test.fuzz is not set), runFuzzing
+// returns immediately.
+func runFuzzing(deps testDeps, fuzzTargets []InternalFuzzTarget) (ran, ok bool) {
+ if len(fuzzTargets) == 0 || *matchFuzz == "" {
return false, true
}
- ctx := &fuzzContext{
- fuzzMatch: newMatcher(matchString, *matchFuzz, "-test.fuzz"),
- }
- if *matchFuzz == "" {
- return false, true
+ ctx := &fuzzContext{fuzzMatch: newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz")}
+ if *isFuzzWorker {
+ ctx.runFuzzWorker = deps.RunFuzzWorker
+ } else {
+ ctx.coordinateFuzzing = deps.CoordinateFuzzing
}
f := &F{
common: common{
@@ -275,7 +321,6 @@ func runFuzzing(matchString func(pat, str string) (bool, error), fuzzTargets []I
w: os.Stdout,
},
context: ctx,
- fuzz: true,
}
var (
ft InternalFuzzTarget