Skip to content

Commit 36a265a

Browse files
committed
testing: fix -run behavior with fuzz tests
This change fixes some issues with -run, and the subsequent command line output when running in verbose mode. It replaces CorpusEntry.Name with CorpusEntry.Path, and refactors the code accordingly. This change also adds a lot of additional tests which check explicit command line output when fuzz targets are run without fuzzing. This will be important to avoid regressions. Updates #48149 Change-Id: If34b1f51db646317b7b51c3c38ae53231d01f568 Reviewed-on: https://go-review.googlesource.com/c/go/+/354632 Trust: Katie Hockman <[email protected]> Run-TryBot: Katie Hockman <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent 4679670 commit 36a265a

File tree

6 files changed

+212
-58
lines changed

6 files changed

+212
-58
lines changed

src/cmd/go/testdata/script/test_fuzz_mutate_crash.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ go run check_testdata.go FuzzWithBug
2121
# Now, the failing bytes should have been added to the seed corpus for
2222
# the target, and should fail when run without fuzzing.
2323
! go test
24-
stdout 'testdata[/\\]fuzz[/\\]FuzzWithBug[/\\][a-f0-9]{64}'
24+
stdout 'FuzzWithBug/[a-f0-9]{64}'
2525
stdout 'this input caused a crash!'
2626

2727
! go test -run=FuzzWithNilPanic -fuzz=FuzzWithNilPanic -fuzztime=100x -fuzzminimizetime=1000x
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# TODO(jayconrod): support shared memory on more platforms.
2+
[!darwin] [!linux] [!windows] skip
3+
4+
[short] skip
5+
env GOCACHE=$WORK/cache
6+
7+
# Tests which verify the behavior and command line output when
8+
# running a fuzz target as a unit test.
9+
10+
# Tests without -run.
11+
12+
! go test
13+
stdout FAIL
14+
stdout 'error here'
15+
16+
! go test -v
17+
stdout FAIL
18+
stdout 'error here'
19+
stdout '=== RUN FuzzFoo/thisfails'
20+
stdout '--- FAIL: FuzzFoo/thisfails'
21+
stdout '=== RUN FuzzFoo/thispasses'
22+
stdout '--- PASS: FuzzFoo/thispasses'
23+
24+
# Tests where -run matches all seed corpora.
25+
26+
! go test -run FuzzFoo/this
27+
stdout FAIL
28+
stdout 'error here'
29+
! stdout 'no tests to run'
30+
31+
! go test -run /this
32+
stdout FAIL
33+
stdout 'error here'
34+
! stdout 'no tests to run'
35+
36+
! go test -v -run FuzzFoo/this
37+
stdout FAIL
38+
stdout 'error here'
39+
stdout '=== RUN FuzzFoo/thisfails'
40+
stdout '--- FAIL: FuzzFoo/thisfails'
41+
stdout '=== RUN FuzzFoo/thispasses'
42+
stdout '--- PASS: FuzzFoo/thispasses'
43+
! stdout 'no tests to run'
44+
45+
! go test -v -run /this
46+
stdout FAIL
47+
stdout 'error here'
48+
stdout '=== RUN FuzzFoo/thisfails'
49+
stdout '--- FAIL: FuzzFoo/thisfails'
50+
stdout '=== RUN FuzzFoo/thispasses'
51+
stdout '--- PASS: FuzzFoo/thispasses'
52+
! stdout 'no tests to run'
53+
54+
# Tests where -run only matches one seed corpus which passes.
55+
56+
go test -run FuzzFoo/thispasses
57+
stdout ok
58+
! stdout 'no tests to run'
59+
60+
go test -run /thispasses
61+
stdout ok
62+
! stdout 'no tests to run'
63+
64+
# Same tests in verbose mode
65+
go test -v -run FuzzFoo/thispasses
66+
stdout '=== RUN FuzzFoo/thispasses'
67+
stdout '--- PASS: FuzzFoo/thispasses'
68+
! stdout '=== RUN FuzzFoo/thisfails'
69+
! stdout 'no tests to run'
70+
71+
go test -v -run /thispasses
72+
stdout '=== RUN FuzzFoo/thispasses'
73+
stdout '--- PASS: FuzzFoo/thispasses'
74+
! stdout '=== RUN FuzzFoo/thisfails'
75+
! stdout 'no tests to run'
76+
77+
# Tests where -run only matches one seed corpus which fails.
78+
79+
! go test -run FuzzFoo/thisfails
80+
stdout FAIL
81+
stdout 'error here'
82+
! stdout 'no tests to run'
83+
84+
! go test -run /thisfails
85+
stdout FAIL
86+
stdout 'error here'
87+
! stdout 'no tests to run'
88+
89+
! go test -v -run FuzzFoo/thisfails
90+
stdout 'error here'
91+
stdout '=== RUN FuzzFoo/thisfails'
92+
stdout '--- FAIL: FuzzFoo/thisfails'
93+
! stdout '=== RUN FuzzFoo/thispasses'
94+
! stdout 'no tests to run'
95+
96+
! go test -v -run /thisfails
97+
stdout 'error here'
98+
stdout '=== RUN FuzzFoo/thisfails'
99+
stdout '--- FAIL: FuzzFoo/thisfails'
100+
! stdout '=== RUN FuzzFoo/thispasses'
101+
! stdout 'no tests to run'
102+
103+
# Tests where -run doesn't match any seed corpora.
104+
105+
go test -run FuzzFoo/nomatch
106+
stdout ok
107+
108+
go test -run /nomatch
109+
stdout ok
110+
111+
go test -v -run FuzzFoo/nomatch
112+
stdout '=== RUN FuzzFoo'
113+
stdout '--- PASS: FuzzFoo'
114+
stdout ok
115+
! stdout 'no tests to run'
116+
117+
go test -v -run /nomatch
118+
stdout '=== RUN FuzzFoo'
119+
stdout '--- PASS: FuzzFoo'
120+
stdout ok
121+
! stdout 'no tests to run'
122+
123+
-- go.mod --
124+
module example.com/x
125+
126+
go 1.16
127+
-- x_test.go --
128+
package x
129+
130+
import "testing"
131+
132+
func FuzzFoo(f *testing.F) {
133+
f.Add("this is fine")
134+
f.Fuzz(func(t *testing.T, s string) {
135+
if s == "fails" {
136+
t.Error("error here")
137+
}
138+
})
139+
}
140+
-- testdata/fuzz/FuzzFoo/thisfails --
141+
go test fuzz v1
142+
string("fails")
143+
-- testdata/fuzz/FuzzFoo/thispasses --
144+
go test fuzz v1
145+
string("passes")

src/cmd/go/testdata/script/test_fuzz_seed_corpus.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ stdout ok
2626
! go test -fuzz=FuzzWithTestdata -run=FuzzWithTestdata -fuzztime=1x
2727
! stdout ^ok
2828
! stdout 'Crash written to testdata[/\\]fuzz[/\\]FuzzWithTestdata[/\\]'
29+
stdout 'found a crash while testing seed corpus entry: FuzzWithTestdata/1'
2930
stdout FAIL
3031

3132
# Test that fuzzing a target with no seed corpus or cache finds a crash, prints

src/internal/fuzz/fuzz.go

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,14 @@ func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err err
141141
if c.crashMinimizing == nil || crashWritten {
142142
return
143143
}
144-
fileName, werr := writeToCorpus(c.crashMinimizing.entry.Data, opts.CorpusDir)
144+
werr := writeToCorpus(&c.crashMinimizing.entry, opts.CorpusDir)
145145
if werr != nil {
146146
err = fmt.Errorf("%w\n%v", err, werr)
147147
return
148148
}
149149
if err == nil {
150150
err = &crashError{
151-
name: filepath.Base(fileName),
151+
path: c.crashMinimizing.entry.Path,
152152
err: errors.New(c.crashMinimizing.crasherMsg),
153153
}
154154
}
@@ -230,7 +230,8 @@ func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err err
230230

231231
if result.crasherMsg != "" {
232232
if c.warmupRun() && result.entry.IsSeed {
233-
fmt.Fprintf(c.opts.Log, "found a crash while testing seed corpus entry: %q\n", result.entry.Parent)
233+
target := filepath.Base(c.opts.CorpusDir)
234+
fmt.Fprintf(c.opts.Log, "found a crash while testing seed corpus entry: %s/%s\n", target, testName(result.entry.Parent))
234235
stop(errors.New(result.crasherMsg))
235236
break
236237
}
@@ -249,11 +250,11 @@ func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err err
249250
} else if !crashWritten {
250251
// Found a crasher that's either minimized or not minimizable.
251252
// Write to corpus and stop.
252-
fileName, err := writeToCorpus(result.entry.Data, opts.CorpusDir)
253+
err := writeToCorpus(&result.entry, opts.CorpusDir)
253254
if err == nil {
254255
crashWritten = true
255256
err = &crashError{
256-
name: filepath.Base(fileName),
257+
path: result.entry.Path,
257258
err: errors.New(result.crasherMsg),
258259
}
259260
}
@@ -262,7 +263,7 @@ func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err err
262263
c.opts.Log,
263264
"DEBUG new crasher, elapsed: %s, id: %s, parent: %s, gen: %d, size: %d, exec time: %s\n",
264265
c.elapsed(),
265-
fileName,
266+
result.entry.Path,
266267
result.entry.Parent,
267268
result.entry.Generation,
268269
len(result.entry.Data),
@@ -315,12 +316,11 @@ func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err err
315316
// Update the coordinator's coverage mask and save the value.
316317
inputSize := len(result.entry.Data)
317318
if opts.CacheDir != "" {
318-
filename, err := writeToCorpus(result.entry.Data, opts.CacheDir)
319+
err := writeToCorpus(&result.entry, opts.CacheDir)
319320
if err != nil {
320321
stop(err)
321322
}
322323
result.entry.Data = nil
323-
result.entry.Name = filename
324324
}
325325
c.updateCoverage(keepCoverage)
326326
c.corpus.entries = append(c.corpus.entries, result.entry)
@@ -331,7 +331,7 @@ func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err err
331331
c.opts.Log,
332332
"DEBUG new interesting input, elapsed: %s, id: %s, parent: %s, gen: %d, new bits: %d, total bits: %d, size: %d, exec time: %s\n",
333333
c.elapsed(),
334-
result.entry.Name,
334+
result.entry.Path,
335335
result.entry.Parent,
336336
result.entry.Generation,
337337
countBits(keepCoverage),
@@ -347,7 +347,7 @@ func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err err
347347
c.opts.Log,
348348
"DEBUG worker reported interesting input that doesn't expand coverage, elapsed: %s, id: %s, parent: %s, canMinimize: %t\n",
349349
c.elapsed(),
350-
result.entry.Name,
350+
result.entry.Path,
351351
result.entry.Parent,
352352
result.canMinimize,
353353
)
@@ -397,7 +397,7 @@ func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err err
397397
// of the file where the input causing the crasher was saved. The testing
398398
// framework uses this to report a command to re-run that specific input.
399399
type crashError struct {
400-
name string
400+
path string
401401
err error
402402
}
403403

@@ -409,8 +409,8 @@ func (e *crashError) Unwrap() error {
409409
return e.err
410410
}
411411

412-
func (e *crashError) CrashName() string {
413-
return e.name
412+
func (e *crashError) CrashPath() string {
413+
return e.path
414414
}
415415

416416
type corpus struct {
@@ -426,14 +426,14 @@ type corpus struct {
426426
type CorpusEntry = struct {
427427
Parent string
428428

429-
// Name is the name of the corpus file, if the entry was loaded from the
430-
// seed corpus. It can be used with -run. For entries added with f.Add and
431-
// entries generated by the mutator, Name is empty and Data is populated.
432-
Name string
429+
// Path is the path of the corpus file, if the entry was loaded from disk.
430+
// For other entries, including seed values provided by f.Add, Path is the
431+
// name of the test, e.g. seed#0 or its hash.
432+
Path string
433433

434-
// Data is the raw input data. Data should only be populated for initial
435-
// seed values added with f.Add. For on-disk corpus files, Data will
436-
// be nil.
434+
// Data is the raw input data. Data should only be populated for seed
435+
// values. For on-disk corpus files, Data will be nil, as it will be loaded
436+
// from disk using Path.
437437
Data []byte
438438

439439
// Values is the unmarshaled values from a corpus file.
@@ -452,7 +452,7 @@ func CorpusEntryData(ce CorpusEntry) ([]byte, error) {
452452
return ce.Data, nil
453453
}
454454

455-
return os.ReadFile(ce.Name)
455+
return os.ReadFile(ce.Path)
456456
}
457457

458458
type fuzzInput struct {
@@ -616,7 +616,7 @@ type coordinator struct {
616616
}
617617

618618
func newCoordinator(opts CoordinateFuzzingOpts) (*coordinator, error) {
619-
// Make sure all of the seed corpus given by f.Add has marshalled data.
619+
// Make sure all of the seed corpus has marshalled data.
620620
for i := range opts.Seed {
621621
if opts.Seed[i].Data == nil && opts.Seed[i].Values != nil {
622622
opts.Seed[i].Data = marshalCorpusFile(opts.Seed[i].Values...)
@@ -673,7 +673,7 @@ func newCoordinator(opts CoordinateFuzzingOpts) (*coordinator, error) {
673673
data := marshalCorpusFile(vals...)
674674
h := sha256.Sum256(data)
675675
name := fmt.Sprintf("%x", h[:4])
676-
c.corpus.entries = append(c.corpus.entries, CorpusEntry{Name: name, Data: data})
676+
c.corpus.entries = append(c.corpus.entries, CorpusEntry{Path: name, Data: data})
677677
}
678678

679679
return c, nil
@@ -956,7 +956,7 @@ func ReadCorpus(dir string, types []reflect.Type) ([]CorpusEntry, error) {
956956
errs = append(errs, fmt.Errorf("%q: %v", filename, err))
957957
continue
958958
}
959-
corpus = append(corpus, CorpusEntry{Name: filename, Values: vals})
959+
corpus = append(corpus, CorpusEntry{Path: filename, Values: vals})
960960
}
961961
if len(errs) > 0 {
962962
return corpus, &MalformedCorpusError{errs: errs}
@@ -979,7 +979,7 @@ func readCorpusData(data []byte, types []reflect.Type) ([]interface{}, error) {
979979
// provided.
980980
func CheckCorpus(vals []interface{}, types []reflect.Type) error {
981981
if len(vals) != len(types) {
982-
return fmt.Errorf("wrong number of values in corpus entry: %d, want %d", len(vals), len(types))
982+
return fmt.Errorf("wrong number of values in corpus entry %v: want %v", vals, types)
983983
}
984984
for i := range types {
985985
if reflect.TypeOf(vals[i]) != types[i] {
@@ -989,21 +989,25 @@ func CheckCorpus(vals []interface{}, types []reflect.Type) error {
989989
return nil
990990
}
991991

992-
// writeToCorpus atomically writes the given bytes to a new file in testdata.
993-
// If the directory does not exist, it will create one. If the file already
994-
// exists, writeToCorpus will not rewrite it. writeToCorpus returns the
995-
// file's name, or an error if it failed.
996-
func writeToCorpus(b []byte, dir string) (name string, err error) {
997-
sum := fmt.Sprintf("%x", sha256.Sum256(b))
998-
name = filepath.Join(dir, sum)
992+
// writeToCorpus atomically writes the given bytes to a new file in testdata. If
993+
// the directory does not exist, it will create one. If the file already exists,
994+
// writeToCorpus will not rewrite it. writeToCorpus sets entry.Path to the new
995+
// file that was just written or an error if it failed.
996+
func writeToCorpus(entry *CorpusEntry, dir string) (err error) {
997+
sum := fmt.Sprintf("%x", sha256.Sum256(entry.Data))
998+
entry.Path = filepath.Join(dir, sum)
999999
if err := os.MkdirAll(dir, 0777); err != nil {
1000-
return "", err
1000+
return err
10011001
}
1002-
if err := ioutil.WriteFile(name, b, 0666); err != nil {
1003-
os.Remove(name) // remove partially written file
1004-
return "", err
1002+
if err := ioutil.WriteFile(entry.Path, entry.Data, 0666); err != nil {
1003+
os.Remove(entry.Path) // remove partially written file
1004+
return err
10051005
}
1006-
return name, nil
1006+
return nil
1007+
}
1008+
1009+
func testName(path string) string {
1010+
return filepath.Base(path)
10071011
}
10081012

10091013
func zeroValue(t reflect.Type) interface{} {

src/internal/fuzz/worker.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,7 @@ func (wc *workerClient) minimize(ctx context.Context, entryIn CorpusEntry, args
10291029
entryOut.Values, err = unmarshalCorpusFile(entryOut.Data)
10301030
h := sha256.Sum256(entryOut.Data)
10311031
name := fmt.Sprintf("%x", h[:4])
1032-
entryOut.Name = name
1032+
entryOut.Path = name
10331033
entryOut.Parent = entryIn.Parent
10341034
entryOut.Generation = entryIn.Generation
10351035
if err != nil {
@@ -1092,8 +1092,8 @@ func (wc *workerClient) fuzz(ctx context.Context, entryIn CorpusEntry, args fuzz
10921092
h := sha256.Sum256(dataOut)
10931093
name := fmt.Sprintf("%x", h[:4])
10941094
entryOut = CorpusEntry{
1095-
Name: name,
1096-
Parent: entryIn.Name,
1095+
Parent: entryIn.Path,
1096+
Path: name,
10971097
Data: dataOut,
10981098
Generation: entryIn.Generation + 1,
10991099
}

0 commit comments

Comments
 (0)