]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.fuzz] testing: exit after f.Fuzz function
authorKatie Hockman <katie@golang.org>
Wed, 7 Oct 2020 18:08:53 +0000 (14:08 -0400)
committerFilippo Valsorda <filippo@golang.org>
Fri, 4 Dec 2020 18:17:29 +0000 (19:17 +0100)
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 <katie@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Katie Hockman <katie@golang.org>
Trust: Jay Conrod <jayconrod@google.com>
Reviewed-by: Jay Conrod <jayconrod@google.com>
src/cmd/go/testdata/script/test_fuzz.txt
src/testing/fuzz.go

index 8f3242dc5ee4e2776289973ef0b39152295448f6..2a3e6e26c737dfeddaf8f4c3628662bec11a33c9 100644 (file)
@@ -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
 
index 01895e8d7d9d95cff5d2b308abb3d8296c0613ab..11bbd8fb1642e4a711e34c48816db778c9f0dbc5 100644 (file)
@@ -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
 }