]> Cypherpunks repositories - gostls13.git/commitdiff
testing: write output to buffer when fuzzing
authorKatie Hockman <katie@golang.org>
Thu, 14 Oct 2021 16:32:58 +0000 (12:32 -0400)
committerKatie Hockman <katie@golang.org>
Tue, 19 Oct 2021 15:48:56 +0000 (15:48 +0000)
Fixes #48709

Change-Id: Ia6376a2f792946498d6565a53605b3e6c985ea7c
Reviewed-on: https://go-review.googlesource.com/c/go/+/355909
Trust: Katie Hockman <katie@golang.org>
Run-TryBot: Katie Hockman <katie@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
src/cmd/go/testdata/script/test_fuzz_minimize.txt
src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt
src/testing/fuzz.go

index 8b11621bbda9c182a583fba4d89c058b98c7e8a8..3293e878bb73830901811e48febedac67856d596 100644 (file)
@@ -31,7 +31,7 @@ stdout FAIL
 ! go test -fuzz=FuzzMinimizeZeroLimitSet -run=FuzzMinimizeZeroLimitSet -fuzztime=10000x -fuzzminimizetime=0x .
 ! stdout '^ok'
 ! stdout 'minimizing'
-stdout 'there was an Error'
+stdout -count=1 'there was an Error'
 stdout FAIL
 
 # Test that minimization is working for recoverable errors.
@@ -49,11 +49,27 @@ go run ./check_testdata FuzzMinimizerRecoverable 50
 ! go test -run=FuzzMinimizerRecoverable .
 rm testdata
 
+# Test that minimization is working for recoverable errors. Run it with -v this
+# time to ensure the command line output still looks right.
+! go test -v -fuzz=FuzzMinimizerRecoverable -run=FuzzMinimizerRecoverable -fuzztime=10000x .
+! stdout '^ok'
+stdout 'got the minimum size!'
+# The error message that was printed should be for the one written to testdata.
+stdout 'contains a non-zero byte of length 50'
+stdout FAIL
+
+# Check that the bytes written to testdata are of length 50 (the minimum size)
+go run ./check_testdata FuzzMinimizerRecoverable 50
+
+# Test that re-running the minimized value causes a crash.
+! go test -run=FuzzMinimizerRecoverable .
+rm testdata
+
 # Test that minimization doesn't run for non-recoverable errors.
 ! go test -fuzz=FuzzMinimizerNonrecoverable -run=FuzzMinimizerNonrecoverable -fuzztime=10000x .
 ! stdout '^ok'
 ! stdout 'minimizing'
-stdout 'fuzzing process terminated unexpectedly: exit status 99'
+stdout -count=1 'fuzzing process terminated unexpectedly: exit status 99'
 stdout FAIL
 
 # Check that re-running the value causes a crash.
index fc66201eb3fac361298a365f84dd15dc04cd751e..8ea4cdb8a5a18481ef02270adeede11ed94f8cce 100644 (file)
@@ -29,7 +29,7 @@ env GOCACHE=$WORK/gocache
 ! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerCrashInMinimization -test.fuzztime=10000x -test.parallel=1
 ! stdout '^ok'
 stdout 'got the minimum size!'
-stdout 'flaky failure'
+stdout -count=1 'flaky failure'
 stdout FAIL
 
 # Make sure the crash that was written will fail when run with go test
index 0429f8243d2ff72d8495b8cddb2a88dcafb49f03..d5cb5e853f5ec7c52a98e0b64f891af8d40a6e0b 100644 (file)
@@ -5,6 +5,7 @@
 package testing
 
 import (
+       "bytes"
        "errors"
        "flag"
        "fmt"
@@ -367,14 +368,14 @@ func (f *F) Fuzz(ff interface{}) {
        // run calls fn on a given input, as a subtest with its own T.
        // run is analogous to T.Run. The test filtering and cleanup works similarly.
        // fn is called in its own goroutine.
-       run := func(e corpusEntry) error {
+       run := func(captureOut io.Writer, e corpusEntry) (ok bool) {
                if e.Values == nil {
                        // The corpusEntry must have non-nil Values in order to run the
                        // test. If Values is nil, it is a bug in our code.
                        panic(fmt.Sprintf("corpus file %q was not unmarshaled", e.Path))
                }
                if shouldFailFast() {
-                       return nil
+                       return true
                }
                testName := f.name
                if e.Path != "" {
@@ -405,6 +406,10 @@ func (f *F) Fuzz(ff interface{}) {
                        },
                        context: f.testContext,
                }
+               if captureOut != nil {
+                       // t.parent aliases f.common.
+                       t.parent.w = captureOut
+               }
                t.w = indenter{&t.common}
                if t.chatty != nil {
                        // TODO(#48132): adjust this to work with test2json.
@@ -426,10 +431,7 @@ func (f *F) Fuzz(ff interface{}) {
                })
                <-t.signal
                f.inFuzzFn = false
-               if t.Failed() {
-                       return errors.New(string(f.output))
-               }
-               return nil
+               return !t.Failed()
        }
 
        switch f.fuzzContext.mode {
@@ -466,7 +468,17 @@ func (f *F) Fuzz(ff interface{}) {
        case fuzzWorker:
                // Fuzzing is enabled, and this is a worker process. Follow instructions
                // from the coordinator.
-               if err := f.fuzzContext.deps.RunFuzzWorker(run); err != nil {
+               if err := f.fuzzContext.deps.RunFuzzWorker(func(e corpusEntry) error {
+                       // Don't write to f.w (which points to Stdout) if running from a
+                       // fuzz worker. This would become very verbose, particularly during
+                       // minimization. Return the error instead, and let the caller deal
+                       // with the output.
+                       var buf bytes.Buffer
+                       if ok := run(&buf, e); !ok {
+                               return errors.New(buf.String())
+                       }
+                       return nil
+               }); err != nil {
                        // Internal errors are marked with f.Fail; user code may call this too, before F.Fuzz.
                        // The worker will exit with fuzzWorkerExitCode, indicating this is a failure
                        // (and 'go test' should exit non-zero) but a crasher should not be recorded.
@@ -479,7 +491,7 @@ func (f *F) Fuzz(ff interface{}) {
                for _, e := range f.corpus {
                        name := fmt.Sprintf("%s/%s", f.name, filepath.Base(e.Path))
                        if _, ok, _ := f.testContext.match.fullName(nil, name); ok {
-                               run(e)
+                               run(f.w, e)
                        }
                }
        }