diff options
| author | Jay Conrod <jayconrod@google.com> | 2020-10-02 16:05:33 -0400 |
|---|---|---|
| committer | Filippo Valsorda <filippo@golang.org> | 2020-12-04 19:17:29 +0100 |
| commit | 8fabdcee8ff0537097ae68619ff515563bb2f986 (patch) | |
| tree | 6e5ea2e5b8392f5c2d592a94440b6fa1e7489b1a /src/testing | |
| parent | 0a6f004cb1ed99bc225f4fe3cba5c2c5b901b442 (diff) | |
| download | go-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')
| -rw-r--r-- | src/testing/fuzz.go | 129 | ||||
| -rw-r--r-- | src/testing/internal/testdeps/deps.go | 9 | ||||
| -rw-r--r-- | src/testing/testing.go | 6 |
3 files changed, 101 insertions, 43 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 diff --git a/src/testing/internal/testdeps/deps.go b/src/testing/internal/testdeps/deps.go index 3608d33294..9665092f4c 100644 --- a/src/testing/internal/testdeps/deps.go +++ b/src/testing/internal/testdeps/deps.go @@ -12,6 +12,7 @@ package testdeps import ( "bufio" + "internal/fuzz" "internal/testlog" "io" "regexp" @@ -126,3 +127,11 @@ func (TestDeps) StopTestLog() error { func (TestDeps) SetPanicOnExit0(v bool) { testlog.SetPanicOnExit0(v) } + +func (TestDeps) CoordinateFuzzing(parallel int, seed [][]byte) error { + return fuzz.CoordinateFuzzing(parallel, seed) +} + +func (TestDeps) RunFuzzWorker(fn func([]byte) error) error { + return fuzz.RunFuzzWorker(fn) +} diff --git a/src/testing/testing.go b/src/testing/testing.go index 7cf3323d51..2c2e77dc4b 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -1324,6 +1324,8 @@ func (f matchStringOnly) ImportPath() string { return " func (f matchStringOnly) StartTestLog(io.Writer) {} func (f matchStringOnly) StopTestLog() error { return errMain } func (f matchStringOnly) SetPanicOnExit0(bool) {} +func (f matchStringOnly) CoordinateFuzzing(int, [][]byte) error { return errMain } +func (f matchStringOnly) RunFuzzWorker(func([]byte) error) error { return errMain } // Main is an internal function, part of the implementation of the "go test" command. // It was exported because it is cross-package and predates "internal" packages. @@ -1366,6 +1368,8 @@ type testDeps interface { StartTestLog(io.Writer) StopTestLog() error WriteProfileTo(string, io.Writer, int) error + CoordinateFuzzing(int, [][]byte) error + RunFuzzWorker(func([]byte) error) error } // MainStart is meant for use by tests generated by 'go test'. @@ -1431,7 +1435,7 @@ func (m *M) Run() (code int) { return } - fuzzingRan, fuzzingOk := runFuzzing(m.deps.MatchString, m.fuzzTargets) + fuzzingRan, fuzzingOk := runFuzzing(m.deps, m.fuzzTargets) if *matchFuzz != "" && !fuzzingRan { fmt.Fprintln(os.Stderr, "testing: warning: no targets to fuzz") } |
