aboutsummaryrefslogtreecommitdiff
path: root/src/testing
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
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')
-rw-r--r--src/testing/fuzz.go129
-rw-r--r--src/testing/internal/testdeps/deps.go9
-rw-r--r--src/testing/testing.go6
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")
}