# Test that fuzzing a fuzz target that returns without failing or calling
# f.Fuzz fails and causes a non-zero exit status.
-! go test -fuzz=Fuzz -fuzztime=5s -parallel=1 noop_fuzz_test.go
+! go test -fuzz=Fuzz -fuzztime=5s noop_fuzz_test.go
! stdout ^ok
stdout FAIL
# Test that calling f.Error in a fuzz target causes a non-zero exit status.
-! go test -fuzz=Fuzz -fuzztime=5s -parallel=1 error_fuzz_test.go
+! go test -fuzz=Fuzz -fuzztime=5s error_fuzz_test.go
! stdout ^ok
stdout FAIL
! stdout FAIL
# Test that successful fuzzing exits cleanly.
-go test -fuzz=Fuzz -fuzztime=5s -parallel=1 success_fuzz_test.go
+go test -fuzz=Fuzz -fuzztime=5s success_fuzz_test.go
stdout ok
! stdout FAIL
# Test that calling f.Fatal while fuzzing causes a non-zero exit status.
-! go test -fuzz=Fuzz -fuzztime=5s -parallel=1 fatal_fuzz_test.go
+! go test -fuzz=Fuzz -fuzztime=5s fatal_fuzz_test.go
! stdout ^ok
stdout FAIL
! exists $GOCACHE/fuzz
# Fuzzing should write interesting values to the cache.
-go test -fuzz=FuzzY -fuzztime=5s -parallel=1 .
+go test -fuzz=FuzzY -fuzztime=5s .
go run ./contains_files $GOCACHE/fuzz/example.com/y/FuzzY
# 'go clean -cache' should not delete the fuzz cache.
go test
# Fuzzing should exit 0 when after fuzztime, even if timeout is short.
-go test -timeout=10ms -fuzz=FuzzFast -fuzztime=5s -parallel=1
+go test -timeout=10ms -fuzz=FuzzFast -fuzztime=5s
# We should see the same behavior when invoking the test binary directly.
go test -c
stdout '^ok'
# Matches only for fuzzing.
-go test -fuzz Fuzz -fuzztime 5s -parallel 1 standalone_fuzz_test.go
+go test -fuzz Fuzz -fuzztime 2s -parallel 4 standalone_fuzz_test.go
! stdout '^ok.*\[no tests to run\]'
stdout '^ok'
# Matches none for fuzzing but will run the fuzz target as a test.
-go test -fuzz ThisWillNotMatch -fuzztime 5s -parallel 1 standalone_fuzz_test.go
+go test -fuzz ThisWillNotMatch -fuzztime 2s -parallel 4 standalone_fuzz_test.go
! stdout '^ok.*\[no tests to run\]'
stdout '^ok'
stdout '\[no targets to fuzz\]'
! stdout '\[no targets to fuzz\]'
# Matches more than one fuzz target for fuzzing.
-go test -fuzz Fuzz -fuzztime 5s -parallel 1 multiple_fuzz_test.go
+go test -fuzz Fuzz -fuzztime 2s -parallel 4 multiple_fuzz_test.go
# The tests should run, but not be fuzzed
! stdout '\[no tests to run\]'
! stdout '\[no targets to fuzz\]'
[short] skip
-# TODO: remove -parallel=1 once the races are fixed.
-
# Running the seed corpus for all of the targets should pass the first
# time, since nothing in the seed corpus will cause a crash.
-go test -parallel=1
+go test
# Running the fuzzer should find a crashing input quickly.
-! go test -fuzz=FuzzWithBug -fuzztime=5s -parallel=1
+! go test -fuzz=FuzzWithBug -fuzztime=5s
stdout 'testdata[/\\]corpus[/\\]FuzzWithBug[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzWithBug
# Now, the failing bytes should have been added to the seed corpus for
# the target, and should fail when run without fuzzing.
-! go test -parallel=1
+! go test
# Running the fuzzer should find a crashing input quickly for fuzzing two types.
-! go test -run=FuzzWithTwoTypes -fuzz=FuzzWithTwoTypes -fuzztime=5s -parallel=1
+! go test -run=FuzzWithTwoTypes -fuzz=FuzzWithTwoTypes -fuzztime=5s
stdout 'testdata[/\\]corpus[/\\]FuzzWithTwoTypes[/\\]'
stdout 'these inputs caused a crash!'
go run check_testdata.go FuzzWithTwoTypes
-! go test -run=FuzzWithNilPanic -fuzz=FuzzWithNilPanic -fuzztime=5s -parallel=1
+! go test -run=FuzzWithNilPanic -fuzz=FuzzWithNilPanic -fuzztime=5s
stdout 'testdata[/\\]corpus[/\\]FuzzWithNilPanic[/\\]'
stdout 'runtime.Goexit'
go run check_testdata.go FuzzWithNilPanic
-! go test -run=FuzzWithFail -fuzz=FuzzWithFail -fuzztime=5s -parallel=1
+! go test -run=FuzzWithFail -fuzz=FuzzWithFail -fuzztime=5s
stdout 'testdata[/\\]corpus[/\\]FuzzWithFail[/\\]'
go run check_testdata.go FuzzWithFail
-! go test -run=FuzzWithErrorf -fuzz=FuzzWithErrorf -fuzztime=5s -parallel=1
+! go test -run=FuzzWithErrorf -fuzz=FuzzWithErrorf -fuzztime=5s
stdout 'testdata[/\\]corpus[/\\]FuzzWithErrorf[/\\]'
# TODO: Uncomment this part of the test once it's fixed
# stdout 'errorf was called here'
go run check_testdata.go FuzzWithErrorf
-! go test -run=FuzzWithFatalf -fuzz=FuzzWithFatalf -fuzztime=5s -parallel=1
+! go test -run=FuzzWithFatalf -fuzz=FuzzWithFatalf -fuzztime=5s
stdout 'testdata[/\\]corpus[/\\]FuzzWithFatalf[/\\]'
# TODO: Uncomment this part of the test once it's fixed
# stdout 'fatalf was called here'
go run check_testdata.go FuzzWithFatalf
-! go test -run=FuzzWithBadExit -fuzz=FuzzWithBadExit -fuzztime=5s -parallel=1
+! go test -run=FuzzWithBadExit -fuzz=FuzzWithBadExit -fuzztime=5s
stdout 'testdata[/\\]corpus[/\\]FuzzWithBadExit[/\\]'
stdout 'unexpectedly'
go run check_testdata.go FuzzWithBadExit
os.Exit(1)
}
- if len(files) != 1 {
- fmt.Fprintln(os.Stderr, fmt.Errorf("expect only one new mutation to be written to testdata", len(files)))
+ if len(files) == 0 {
+ fmt.Fprintf(os.Stderr, "expect at least one new mutation to be written to testdata\n")
os.Exit(1)
}
os.Exit(1)
}
if bytes.Equal(contents, []byte("aa")) {
- fmt.Fprintln(os.Stderr, fmt.Errorf("newly written testdata entry was not mutated"))
+ fmt.Fprintf(os.Stderr, "newly written testdata entry was not mutated\n")
os.Exit(1)
}
// The hash of the bytes in the file should match the filename.
h := []byte(fmt.Sprintf("%x", sha256.Sum256(contents)))
if !bytes.Equal([]byte(fname), h) {
- fmt.Fprintln(os.Stderr, fmt.Errorf("hash of bytes %q does not match filename %q", h, fname))
+ fmt.Fprintf(os.Stderr, "hash of bytes %q does not match filename %q\n", h, fname)
os.Exit(1)
}
-}
\ No newline at end of file
+}
"reflect"
"runtime"
"strings"
- "sync"
)
// CoordinateFuzzing creates several worker processes and communicates with
inputC: make(chan CorpusEntry),
interestingC: make(chan CorpusEntry),
crasherC: make(chan crasherEntry),
- errC: make(chan error),
}
+ errC := make(chan error)
newWorker := func() (*worker, error) {
mem, err := sharedMemTempFile(sharedMemSize)
}, nil
}
+ var fuzzErr error
+ stopping := false
+ stop := func(err error) {
+ if fuzzErr == nil || fuzzErr == ctx.Err() {
+ fuzzErr = err
+ }
+ if stopping {
+ return
+ }
+ stopping = true
+ close(c.doneC)
+ }
+
// Start workers.
workers := make([]*worker, parallel)
for i := range workers {
return err
}
}
-
- workerErrs := make([]error, len(workers))
- var wg sync.WaitGroup
- wg.Add(len(workers))
for i := range workers {
- go func(i int) {
- defer wg.Done()
- workerErrs[i] = workers[i].runFuzzing()
- if cleanErr := workers[i].cleanup(); workerErrs[i] == nil {
- workerErrs[i] = cleanErr
+ w := workers[i]
+ go func() {
+ err := w.runFuzzing()
+ cleanErr := w.cleanup()
+ if err == nil {
+ err = cleanErr
}
- }(i)
+ errC <- err
+ }()
}
- // Before returning, signal workers to stop, wait for them to actually stop,
- // and gather any errors they encountered.
- defer func() {
- close(c.doneC)
- wg.Wait()
- if err == nil || err == ctx.Err() {
- for _, werr := range workerErrs {
- if werr != nil {
- // Return the first error found, replacing ctx.Err() if a more
- // interesting error is found.
- err = werr
- break
- }
- }
- }
- }()
-
// Main event loop.
+ // Do not return until all workers have terminated. We avoid a deadlock by
+ // receiving messages from workers even after closing c.doneC.
+ activeWorkers := len(workers)
i := 0
for {
select {
// not the coordinator or worker processes. 'go test' will stop running
// actions, but it won't interrupt its child processes. This makes it
// difficult to stop fuzzing on Windows without a timeout.
- return ctx.Err()
+ stop(ctx.Err())
case crasher := <-c.crasherC:
// A worker found a crasher. Write it to testdata and return it.
}
// TODO(jayconrod,katiehockman): if -keepfuzzing, report the error to
// the user and restart the crashed worker.
- return err
+ stop(err)
case entry := <-c.interestingC:
// Some interesting input arrived from a worker.
corpus.entries = append(corpus.entries, entry)
if cacheDir != "" {
if _, err := writeToCorpus(entry.Data, cacheDir); err != nil {
- return err
+ stop(err)
}
}
- case err := <-c.errC:
- // A worker encountered a fatal error.
- return err
+ case err := <-errC:
+ // A worker terminated, possibly after encountering a fatal error.
+ stop(err)
+ activeWorkers--
+ if activeWorkers == 0 {
+ return fuzzErr
+ }
case c.inputC <- corpus.entries[i]:
// Send the next input to any worker.
// should be saved in the corpus, and we may want to stop fuzzing after
// receiving one.
crasherC chan crasherEntry
-
- // errC is sent internal errors encountered by workers. When the coordinator
- // receives an error, it closes doneC and returns.
- errC chan error
}
// readCache creates a combined corpus from seed values and values in the cache
return false
}
status := exitErr.Sys().(syscall.WaitStatus)
- return status.Signal() == syscall.SIGINT
+ return status.Signal() == syscall.SIGINT || status.Signal() == syscall.SIGKILL
}
// Start the process.
if err := w.start(); err != nil {
// We couldn't start the worker process. We can't do anything, and it's
- // likely that other workers can't either, so give up.
- w.coordinator.errC <- err
+ // likely that other workers can't either, so don't try to restart.
return err
}