From: Katie Hockman Date: Wed, 7 Oct 2020 18:08:53 +0000 (-0400) Subject: [dev.fuzz] testing: exit after f.Fuzz function X-Git-Tag: go1.18beta1~1282^2~124 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=0a6f004cb1ed99bc225f4fe3cba5c2c5b901b442;p=gostls13.git [dev.fuzz] testing: exit after f.Fuzz function This change causes f.Fuzz to call runtime.GoExit when it has finished running. This would mean that any code after an f.Fuzz function within a fuzz target would not be executed. In the future, vet should fail if someone tries to do this. This change also adds the missing code that would execute any cleanup functions added by f.Cleanup. Change-Id: Ib4d1e6bcafbe189986d0667a1e87dabae67ee621 Reviewed-on: https://go-review.googlesource.com/c/go/+/260338 Run-TryBot: Katie Hockman TryBot-Result: Go Bot Trust: Katie Hockman Trust: Jay Conrod Reviewed-by: Jay Conrod --- diff --git a/src/cmd/go/testdata/script/test_fuzz.txt b/src/cmd/go/testdata/script/test_fuzz.txt index 8f3242dc5e..2a3e6e26c7 100644 --- a/src/cmd/go/testdata/script/test_fuzz.txt +++ b/src/cmd/go/testdata/script/test_fuzz.txt @@ -57,16 +57,22 @@ stdout 'f.Fuzz function' stdout FAIL stdout 'f.Fuzz function' -# Test that multiple calls to f.Fuzz causes a non-zero exit status. -! go test multiple_fuzz_calls_fuzz_test.go -! stdout ^ok -stdout FAIL +# Test that a call to f.Fatal after the Fuzz func is never executed. +go test fatal_after_fuzz_func_fuzz_test.go +stdout ok +! stdout FAIL # Test that missing *T in f.Fuzz causes a non-zero exit status. ! go test incomplete_fuzz_call_fuzz_test.go ! stdout ^ok stdout FAIL +# Test that a panic in the Cleanup func is executed. +! go test cleanup_fuzz_test.go +! stdout ^ok +stdout FAIL +stdout 'failed some precondition' + # Test success with seed corpus in f.Fuzz go test -run FuzzPass fuzz_add_test.go stdout ok @@ -161,8 +167,8 @@ func Fuzz(f *testing.F) { }) } --- multiple_fuzz_calls_fuzz_test.go -- -package multiple_fuzz_calls_fuzz +-- fatal_after_fuzz_func_fuzz_test.go -- +package fatal_after_fuzz_func_fuzz import "testing" @@ -170,10 +176,7 @@ func Fuzz(f *testing.F) { f.Fuzz(func(t *testing.T, b []byte) { // no-op }) - - f.Fuzz(func(t *testing.T, b []byte) { - // this second call should panic - }) + f.Fatal("this shouldn't be called") } -- incomplete_fuzz_call_fuzz_test.go -- @@ -187,6 +190,20 @@ func Fuzz(f *testing.F) { }) } +-- cleanup_fuzz_test.go -- +package cleanup_fuzz_test + +import "testing" + +func Fuzz(f *testing.F) { + f.Cleanup(func() { + panic("failed some precondition") + }) + f.Fuzz(func(t *testing.T, b []byte) { + // no-op + }) +} + -- fuzz_add_test.go -- package fuzz_add diff --git a/src/testing/fuzz.go b/src/testing/fuzz.go index 01895e8d7d..11bbd8fb16 100644 --- a/src/testing/fuzz.go +++ b/src/testing/fuzz.go @@ -9,6 +9,7 @@ import ( "flag" "fmt" "os" + "runtime" "time" ) @@ -28,12 +29,11 @@ type InternalFuzzTarget struct { // F is a type passed to fuzz targets for fuzz testing. type F struct { common - context *fuzzContext - 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 - fuzzCalled bool // fuzzCalled indicates whether f.Fuzz has been called for this target + context *fuzzContext + 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 @@ -61,21 +61,20 @@ func (f *F) Add(args ...interface{}) { } } -// Fuzz runs the fuzz function, ff, for fuzz testing. It runs ff in a separate -// goroutine. Only the first call to Fuzz will be executed, and any subsequent -// calls will panic. If ff fails for a set of arguments, those arguments will be -// added to the seed corpus. +// Fuzz runs the fuzz function, ff, for fuzz testing. If ff fails for a set of +// arguments, those arguments will be added to the seed corpus. +// +// This is a terminal function which will terminate the currently running fuzz +// target by calling runtime.Goexit. To run any code after this function, use +// Cleanup. func (f *F) Fuzz(ff interface{}) { - if f.fuzzCalled { - panic("testing: found more than one call to Fuzz, will skip") - } - f.fuzzCalled = true - fn, ok := ff.(func(*T, []byte)) if !ok { panic("testing: Fuzz function must have type func(*testing.T, []byte)") } + defer runtime.Goexit() // exit after this function + var errStr string run := func(t *T, b []byte) { defer func() { @@ -118,6 +117,7 @@ func (f *F) Fuzz(ff interface{}) { errStr += string(t.output) } } + f.finished = true if f.Failed() { f.result = FuzzResult{Error: errors.New(errStr)} return @@ -169,12 +169,13 @@ func (f *F) runTarget(fn func(*F)) { } if err != nil { f.Fail() - f.result = FuzzResult{Error: fmt.Errorf("%s", err)} + f.result = FuzzResult{Error: fmt.Errorf(" %s", err)} } f.report() f.setRan() f.signal <- true // signal that the test has finished }() + defer f.runCleanup(normalPanic) fn(f) f.finished = true }