+# Test that calling f.Error in a fuzz target causes a non-zero exit status.
+! go test -fuzz Fuzz error_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+
# Test that calling f.Fatal in a fuzz target causes a non-zero exit status.
-! go test fail_fuzz_test.go
+! go test fatal_fuzz_test.go
! stdout ^ok
stdout FAIL
! stdout FAIL
# Test that calling f.Fatal while fuzzing causes a non-zero exit status.
-! go test -fuzz Fuzz fail_fuzz_test.go
+! go test -fuzz Fuzz fatal_fuzz_test.go
! stdout ^ok
stdout FAIL
! stdout ^ok
stdout FAIL
--- fail_fuzz_test.go --
-package fail_fuzz
+# Test fatal with testdata seed corpus
+! go test -run FuzzFail corpustesting/fuzz_testdata_corpus_test.go
+! stdout ^ok
+stdout FAIL
+stdout 'fatal here'
+
+# Test pass with testdata seed corpus
+go test -run FuzzPass corpustesting/fuzz_testdata_corpus_test.go
+stdout ok
+! stdout FAIL
+! stdout 'fatal here'
+
+# Test pass with file in other nested testdata directory
+go test -run FuzzInNestedDir corpustesting/fuzz_testdata_corpus_test.go
+stdout ok
+! stdout FAIL
+! stdout 'fatal here'
+
+-- error_fuzz_test.go --
+package error_fuzz
import "testing"
-func FuzzFail(f *testing.F) {
+func Fuzz(f *testing.F) {
+ f.Error("error in target")
+}
+
+-- fatal_fuzz_test.go --
+package fatal_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
f.Fatal("fatal in target")
}
panic(nil)
}
})
-}
\ No newline at end of file
+}
+
+-- corpustesting/fuzz_testdata_corpus_test.go --
+package fuzz_testdata_corpus
+
+import "testing"
+
+func fuzzFn(f *testing.F) {
+ f.Helper()
+ f.Fuzz(func(t *testing.T, b []byte) {
+ if string(b) == "12345\n" {
+ t.Fatal("fatal here")
+ }
+ })
+}
+
+func FuzzFail(f *testing.F) {
+ fuzzFn(f)
+}
+
+func FuzzPass(f *testing.F) {
+ fuzzFn(f)
+}
+
+func FuzzInNestedDir(f *testing.F) {
+ fuzzFn(f)
+}
+
+-- corpustesting/testdata/corpus/FuzzFail/1 --
+12345
+-- corpustesting/testdata/corpus/FuzzPass/1 --
+00000
+-- corpustesting/testdata/corpus/FuzzInNestedDir/anotherdir/1 --
+12345
\ No newline at end of file
[short] skip
-# Run chatty fuzz targets with an error and fatal.
-! go test -v chatty_fail_fuzz_test.go
+# Run chatty fuzz targets with an error.
+! go test -v chatty_error_fuzz_test.go
! stdout '^ok'
stdout 'FAIL'
stdout 'error in target'
+
+# Run chatty fuzz targets with a fatal.
+! go test -v chatty_fatal_fuzz_test.go
+! stdout '^ok'
+stdout 'FAIL'
stdout 'fatal in target'
# Run chatty fuzz target with a panic
stdout 'all good here'
! stdout FAIL
--- chatty_fail_fuzz_test.go --
-package chatty_fail_fuzz
+-- chatty_error_fuzz_test.go --
+package chatty_error_fuzz
import "testing"
func Fuzz(f *testing.F) {
f.Error("error in target")
+}
+
+-- chatty_fatal_fuzz_test.go --
+package chatty_fatal_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
f.Fatal("fatal in target")
}
package fuzz
import (
+ "fmt"
+ "io/ioutil"
"os"
+ "path/filepath"
"runtime"
"sync"
"time"
// parallel is the number of worker processes to run in parallel. If parallel
// is 0, CoordinateFuzzing will run GOMAXPROCS workers.
//
-// seed is a list of seed values added by the fuzz target with testing.F.Add.
-// Seed values from testdata and GOFUZZCACHE should not be included in this
-// list; this function loads them separately.
+// seed is a list of seed values added by the fuzz target with testing.F.Add and
+// in testdata.
+// Seed values from GOFUZZCACHE should not be included in this list; this
+// function loads them separately.
func CoordinateFuzzing(parallel int, seed [][]byte) error {
if parallel == 0 {
parallel = runtime.GOMAXPROCS(0)
corpus.entries = append(corpus.entries, corpusEntry{b: []byte{0}})
}
- // TODO(jayconrod,katiehockman): read corpus from testdata.
// TODO(jayconrod,katiehockman): read corpus from GOFUZZCACHE.
// Start workers.
// values from this channel.
inputC chan corpusEntry
}
+
+// ReadCorpus reads the corpus from the testdata directory in this target's
+// package.
+func ReadCorpus(name string) ([][]byte, error) {
+ testdataDir := filepath.Join("testdata/corpus", name)
+ files, err := ioutil.ReadDir(testdataDir)
+ if os.IsNotExist(err) {
+ return nil, nil // No corpus to read
+ } else if err != nil {
+ return nil, fmt.Errorf("testing: reading seed corpus from testdata: %v", err)
+ }
+ var corpus [][]byte
+ for _, file := range files {
+ if file.IsDir() {
+ continue
+ }
+ bytes, err := ioutil.ReadFile(filepath.Join(testdataDir, file.Name()))
+ if err != nil {
+ return nil, fmt.Errorf("testing: failed to read corpus file: %v", err)
+ }
+ corpus = append(corpus, bytes)
+ }
+ return corpus, nil
+}
b []byte
}
+func bytesToCorpus(bytes [][]byte) []corpusEntry {
+ c := make([]corpusEntry, len(bytes))
+ for i, b := range bytes {
+ c[i].b = b
+ }
+ return c
+}
+
// Add will add the arguments to the seed corpus for the fuzz target. This will
// be a no-op if called after or within the Fuzz function. The args must match
// those in the Fuzz function.
panic("testing: Fuzz function must have type func(*testing.T, []byte)")
}
+ // Load seed corpus
+ c, err := f.context.readCorpus(f.name)
+ if err != nil {
+ f.Fatal(err)
+ }
+ f.corpus = append(f.corpus, bytesToCorpus(c)...)
+ // TODO(jayconrod,katiehockman): dedupe testdata corpus with entries from f.Add
+
defer runtime.Goexit() // exit after this function
var errStr string
}
// run runs each fuzz target in its own goroutine with its own *F.
-func (f *F) run(name string, fn func(f *F)) (ran, ok bool) {
- innerF := &F{
+func (f *F) run(ft InternalFuzzTarget) (ran, ok bool) {
+ f = &F{
common: common{
signal: make(chan bool),
- name: name,
+ name: ft.Name,
chatty: f.chatty,
w: f.w,
},
context: f.context,
}
- if innerF.chatty != nil {
- innerF.chatty.Updatef(name, "=== RUN %s\n", name)
+ if f.chatty != nil {
+ f.chatty.Updatef(ft.Name, "=== RUN %s\n", ft.Name)
}
- go innerF.runTarget(fn)
- <-innerF.signal
- return innerF.ran, !innerF.failed
+ go f.runTarget(ft.Fn)
+ <-f.signal
+ return f.ran, !f.failed
}
// runTarget runs the given target, handling panics and exits
fuzzMatch *matcher
coordinateFuzzing func(int, [][]byte) error
runFuzzWorker func(func([]byte) error) error
+ readCorpus func(string) ([][]byte, error)
}
// runFuzzTargets runs the fuzz targets matching the pattern for -run. This will
// only run the f.Fuzz function for each seed corpus without using the fuzzing
// engine to generate or mutate inputs.
-func runFuzzTargets(matchString func(pat, str string) (bool, error), fuzzTargets []InternalFuzzTarget) (ran, ok bool) {
+func runFuzzTargets(deps testDeps, fuzzTargets []InternalFuzzTarget) (ran, ok bool) {
ok = true
if len(fuzzTargets) == 0 || *isFuzzWorker {
return ran, ok
}
- ctx := &fuzzContext{runMatch: newMatcher(matchString, *match, "-test.run")}
+ ctx := &fuzzContext{
+ runMatch: newMatcher(deps.MatchString, *match, "-test.run"),
+ readCorpus: deps.ReadCorpus,
+ }
var fts []InternalFuzzTarget
for _, ft := range fuzzTargets {
if _, matched, _ := ctx.runMatch.fullName(nil, ft.Name); matched {
fuzzFunc: func(f *F) {
for _, ft := range fts {
// Run each fuzz target in it's own goroutine.
- ftRan, ftOk := f.run(ft.Name, ft.Fn)
+ ftRan, ftOk := f.run(ft)
ran = ran || ftRan
ok = ok && ftOk
}
if len(fuzzTargets) == 0 || *matchFuzz == "" {
return false, true
}
- ctx := &fuzzContext{fuzzMatch: newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz")}
+ ctx := &fuzzContext{
+ fuzzMatch: newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz"),
+ readCorpus: deps.ReadCorpus,
+ }
if *isFuzzWorker {
ctx.runFuzzWorker = deps.RunFuzzWorker
} else {
func (TestDeps) RunFuzzWorker(fn func([]byte) error) error {
return fuzz.RunFuzzWorker(fn)
}
+
+func (TestDeps) ReadCorpus(name string) ([][]byte, error) {
+ return fuzz.ReadCorpus(name)
+}
func (f matchStringOnly) SetPanicOnExit0(bool) {}
func (f matchStringOnly) CoordinateFuzzing(int, [][]byte) error { return errMain }
func (f matchStringOnly) RunFuzzWorker(func([]byte) error) error { return errMain }
+func (f matchStringOnly) ReadCorpus(name string) ([][]byte, error) { return nil, 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.
WriteProfileTo(string, io.Writer, int) error
CoordinateFuzzing(int, [][]byte) error
RunFuzzWorker(func([]byte) error) error
+ ReadCorpus(name string) ([][]byte, error)
}
// MainStart is meant for use by tests generated by 'go test'.
deadline := m.startAlarm()
haveExamples = len(m.examples) > 0
testRan, testOk := runTests(m.deps.MatchString, m.tests, deadline)
- fuzzTargetsRan, fuzzTargetsOk := runFuzzTargets(m.deps.MatchString, m.fuzzTargets)
+ fuzzTargetsRan, fuzzTargetsOk := runFuzzTargets(m.deps, m.fuzzTargets)
exampleRan, exampleOk := runExamples(m.deps.MatchString, m.examples)
m.stopAlarm()
if !testRan && !exampleRan && !fuzzTargetsRan && *matchBenchmarks == "" && *matchFuzz == "" {