diff options
Diffstat (limited to 'src/testing')
| -rw-r--r-- | src/testing/fuzz.go | 138 | ||||
| -rw-r--r-- | src/testing/testing.go | 55 |
2 files changed, 98 insertions, 95 deletions
diff --git a/src/testing/fuzz.go b/src/testing/fuzz.go index 46c9d63df4..24a0080730 100644 --- a/src/testing/fuzz.go +++ b/src/testing/fuzz.go @@ -19,9 +19,9 @@ import ( ) func initFuzzFlags() { - matchFuzz = flag.String("test.fuzz", "", "run the fuzz target matching `regexp`") + matchFuzz = flag.String("test.fuzz", "", "run the fuzz test matching `regexp`") flag.Var(&fuzzDuration, "test.fuzztime", "time to spend fuzzing; default is to run indefinitely") - flag.Var(&minimizeDuration, "test.fuzzminimizetime", "time to spend minimizing a value after finding a crash") + flag.Var(&minimizeDuration, "test.fuzzminimizetime", "time to spend minimizing a value after finding a failing input") fuzzCacheDir = flag.String("test.fuzzcachedir", "", "directory where interesting fuzzing inputs are stored") isFuzzWorker = flag.Bool("test.fuzzworker", false, "coordinate with the parent process to fuzz random values") } @@ -33,34 +33,38 @@ var ( fuzzCacheDir *string isFuzzWorker *bool - // corpusDir is the parent directory of the target's seed corpus within + // corpusDir is the parent directory of the fuzz test's seed corpus within // the package. corpusDir = "testdata/fuzz" ) -// fuzzWorkerExitCode is used as an exit code by fuzz worker processes after an internal error. -// This distinguishes internal errors from uncontrolled panics and other crashes. -// Keep in sync with internal/fuzz.workerExitCode. +// fuzzWorkerExitCode is used as an exit code by fuzz worker processes after an +// internal error. This distinguishes internal errors from uncontrolled panics +// and other failiures. Keep in sync with internal/fuzz.workerExitCode. const fuzzWorkerExitCode = 70 -// InternalFuzzTarget is an internal type but exported because it is cross-package; -// it is part of the implementation of the "go test" command. +// InternalFuzzTarget is an internal type but exported because it is +// cross-package; it is part of the implementation of the "go test" command. type InternalFuzzTarget struct { Name string Fn func(f *F) } -// F is a type passed to fuzz targets. +// F is a type passed to fuzz tests. // -// A fuzz target may add seed corpus entries using F.Add or by storing files in -// the testdata/fuzz/<FuzzTargetName> directory. The fuzz target must then -// call F.Fuzz once to provide a fuzz function. See the testing package -// documentation for an example, and see the F.Fuzz and F.Add method -// documentation for details. +// Fuzz tests run generated inputs against a provided fuzz target, which can +// find and report potential bugs in the code being tested. // -// *F methods can only be called before (*F).Fuzz. Once inside the function -// passed to (*F).Fuzz, only (*T) methods can be used. The only *F methods that -// are allowed in the (*F).Fuzz function are (*F).Failed and (*F).Name. +// A fuzz test runs the seed corpus by default, which includes entries provided +// by (*F).Add and entries in the testdata/fuzz/<FuzzTestName> directory. After +// any necessary setup and calls to (*F).Add, the fuzz test must then call +// (*F).Fuzz to provide the fuzz target. See the testing package documentation +// for an example, and see the F.Fuzz and F.Add method documentation for +// details. +// +// *F methods can only be called before (*F).Fuzz. Once the the test is +// executing the fuzz target, only (*T) methods can be used. The only *F methods +// that are allowed in the (*F).Fuzz function are (*F).Failed and (*F).Name. type F struct { common fuzzContext *fuzzContext @@ -97,7 +101,7 @@ type corpusEntry = struct { // Helper may be called simultaneously from multiple goroutines. func (f *F) Helper() { if f.inFuzzFn { - panic("testing: f.Helper was called inside the f.Fuzz function, use t.Helper instead") + panic("testing: f.Helper was called inside the fuzz target, use t.Helper instead") } // common.Helper is inlined here. @@ -125,7 +129,7 @@ func (f *F) Fail() { // (*F).Fail may be called by (*T).Fail, which we should allow. However, we // shouldn't allow direct (*F).Fail calls from inside the (*F).Fuzz function. if f.inFuzzFn { - panic("testing: f.Fail was called inside the f.Fuzz function, use t.Fail instead") + panic("testing: f.Fail was called inside the fuzz target, use t.Fail instead") } f.common.Helper() f.common.Fail() @@ -136,15 +140,15 @@ func (f *F) Skipped() bool { // (*F).Skipped may be called by tRunner, which we should allow. However, we // shouldn't allow direct (*F).Skipped calls from inside the (*F).Fuzz function. if f.inFuzzFn { - panic("testing: f.Skipped was called inside the f.Fuzz function, use t.Skipped instead") + panic("testing: f.Skipped was called inside the fuzz target, use t.Skipped instead") } f.common.Helper() return f.common.Skipped() } -// 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. +// Add will add the arguments to the seed corpus for the fuzz test. This will be +// a no-op if called after or within the fuzz target, and args must match the +// arguments for the fuzz target. func (f *F) Add(args ...interface{}) { var values []interface{} for i := range args { @@ -220,7 +224,7 @@ func (f *F) Fuzz(ff interface{}) { panic("testing: F.Fuzz must receive a function") } if fnType.NumIn() < 2 || fnType.In(0) != reflect.TypeOf((*T)(nil)) { - panic("testing: F.Fuzz function must receive at least two arguments, where the first argument is a *T") + panic("testing: fuzz target must receive at least two arguments, where the first argument is a *T") } // Save the types of the function to compare against the corpus. @@ -354,7 +358,7 @@ func (f *F) Fuzz(ff interface{}) { fmt.Fprintf(f.w, "%v\n", err) if crashErr, ok := err.(fuzzCrashError); ok { crashPath := crashErr.CrashPath() - fmt.Fprintf(f.w, "Crash written to %s\n", crashPath) + fmt.Fprintf(f.w, "Failing input written to %s\n", crashPath) testName := filepath.Base(crashPath) fmt.Fprintf(f.w, "To re-run:\ngo test -run=%s/%s\n", f.name, testName) } @@ -378,7 +382,7 @@ func (f *F) Fuzz(ff interface{}) { }); err != nil { // Internal errors are marked with f.Fail; user code may call this too, before F.Fuzz. // The worker will exit with fuzzWorkerExitCode, indicating this is a failure - // (and 'go test' should exit non-zero) but a crasher should not be recorded. + // (and 'go test' should exit non-zero) but a failing input should not be recorded. f.Errorf("communicating with fuzzing coordinator: %v", err) } @@ -415,7 +419,7 @@ func (f *F) report() { type fuzzResult struct { N int // The number of iterations. T time.Duration // The total time taken. - Error error // Error is the error from the crash + Error error // Error is the error from the failing input } func (r fuzzResult) String() string { @@ -427,7 +431,7 @@ func (r fuzzResult) String() string { return s } -// fuzzCrashError is satisfied by a crash detected within the fuzz function. +// fuzzCrashError is satisfied by a failing input detected while fuzzing. // These errors are written to the seed corpus and can be re-run with 'go test'. // Errors within the fuzzing framework (like I/O errors between coordinator // and worker processes) don't satisfy this interface. @@ -437,12 +441,12 @@ type fuzzCrashError interface { // CrashPath returns the path of the subtest that corresponds to the saved // crash input file in the seed corpus. The test can be re-run with go test - // -run=$target/$name $target is the fuzz target name, and $name is the + // -run=$test/$name $test is the fuzz test name, and $name is the // filepath.Base of the string returned here. CrashPath() string } -// fuzzContext holds fields common to all fuzz targets. +// fuzzContext holds fields common to all fuzz tests. type fuzzContext struct { deps testDeps mode fuzzMode @@ -456,12 +460,12 @@ const ( fuzzWorker ) -// 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(deps testDeps, fuzzTargets []InternalFuzzTarget, deadline time.Time) (ran, ok bool) { +// runFuzzTests runs the fuzz tests 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 runFuzzTests(deps testDeps, fuzzTests []InternalFuzzTarget, deadline time.Time) (ran, ok bool) { ok = true - if len(fuzzTargets) == 0 || *isFuzzWorker { + if len(fuzzTests) == 0 || *isFuzzWorker { return ran, ok } m := newMatcher(deps.MatchString, *match, "-test.run") @@ -476,7 +480,7 @@ func runFuzzTargets(deps testDeps, fuzzTargets []InternalFuzzTarget, deadline ti if Verbose() { root.chatty = newChattyPrinter(root.w) } - for _, ft := range fuzzTargets { + for _, ft := range fuzzTests { if shouldFailFast() { break } @@ -486,7 +490,7 @@ func runFuzzTargets(deps testDeps, fuzzTargets []InternalFuzzTarget, deadline ti } if mFuzz != nil { if _, fuzzMatched, _ := mFuzz.fullName(nil, ft.Name); fuzzMatched { - // If this target will be fuzzed, then don't run the seed corpus + // If this will be fuzzed, then don't run the seed corpus // right now. That will happen later. continue } @@ -515,17 +519,14 @@ func runFuzzTargets(deps testDeps, fuzzTargets []InternalFuzzTarget, deadline ti return root.ran, !root.Failed() } -// runFuzzing runs the fuzz target matching the pattern for -fuzz. Only one such -// fuzz target must match. This will run the fuzzing engine to generate and -// mutate new inputs against the f.Fuzz function. +// runFuzzing runs the fuzz test matching the pattern for -fuzz. Only one such +// fuzz test must match. This will run the fuzzing engine to generate and +// mutate new inputs against the fuzz target. // // If fuzzing is disabled (-test.fuzz is not set), runFuzzing // returns immediately. -func runFuzzing(deps testDeps, fuzzTargets []InternalFuzzTarget) (ok bool) { - // TODO(katiehockman,jayconrod): Should we do something special to make sure - // we don't print f.Log statements again with runFuzzing, since we already - // would have printed them when we ran runFuzzTargets (ie. seed corpus run)? - if len(fuzzTargets) == 0 || *matchFuzz == "" { +func runFuzzing(deps testDeps, fuzzTests []InternalFuzzTarget) (ok bool) { + if len(fuzzTests) == 0 || *matchFuzz == "" { return true } m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz") @@ -544,24 +545,24 @@ func runFuzzing(deps testDeps, fuzzTargets []InternalFuzzTarget) (ok bool) { if Verbose() && !*isFuzzWorker { root.chatty = newChattyPrinter(root.w) } - var target *InternalFuzzTarget - var targetName string + var fuzzTest *InternalFuzzTarget + var testName string var matched []string - for i := range fuzzTargets { - name, ok, _ := tctx.match.fullName(nil, fuzzTargets[i].Name) + for i := range fuzzTests { + name, ok, _ := tctx.match.fullName(nil, fuzzTests[i].Name) if !ok { continue } matched = append(matched, name) - target = &fuzzTargets[i] - targetName = name + fuzzTest = &fuzzTests[i] + testName = name } if len(matched) == 0 { - fmt.Fprintln(os.Stderr, "testing: warning: no targets to fuzz") + fmt.Fprintln(os.Stderr, "testing: warning: no fuzz tests to fuzz") return true } if len(matched) > 1 { - fmt.Fprintf(os.Stderr, "testing: will not fuzz, -fuzz matches more than one target: %v\n", matched) + fmt.Fprintf(os.Stderr, "testing: will not fuzz, -fuzz matches more than one fuzz test: %v\n", matched) return false } @@ -569,7 +570,7 @@ func runFuzzing(deps testDeps, fuzzTargets []InternalFuzzTarget) (ok bool) { common: common{ signal: make(chan bool), barrier: nil, // T.Parallel has no effect when fuzzing. - name: targetName, + name: testName, parent: &root, level: root.level + 1, chatty: root.chatty, @@ -582,31 +583,32 @@ func runFuzzing(deps testDeps, fuzzTargets []InternalFuzzTarget) (ok bool) { // TODO(#48132): adjust this to work with test2json. f.chatty.Updatef(f.name, "=== FUZZ %s\n", f.name) } - go fRunner(f, target.Fn) + go fRunner(f, fuzzTest.Fn) <-f.signal return !f.failed } -// fRunner wraps a call to a fuzz target and ensures that cleanup functions are +// fRunner wraps a call to a fuzz test and ensures that cleanup functions are // called and status flags are set. fRunner should be called in its own // goroutine. To wait for its completion, receive from f.signal. // // fRunner is analogous to tRunner, which wraps subtests started with T.Run. -// Tests and fuzz targets work a little differently, so for now, these functions -// aren't consolidated. In particular, because there are no F.Run and F.Parallel -// methods, i.e., no fuzz sub-targets or parallel fuzz targets, a few +// Unit tests and fuzz tests work a little differently, so for now, these +// functions aren't consolidated. In particular, because there are no F.Run and +// F.Parallel methods, i.e., no fuzz sub-tests or parallel fuzz tests, a few // simplifications are made. We also require that F.Fuzz, F.Skip, or F.Fail is // called. func fRunner(f *F, fn func(*F)) { - // When this goroutine is done, either because runtime.Goexit was called, - // a panic started, or fn returned normally, record the duration and send - // t.signal, indicating the fuzz target is done. + // When this goroutine is done, either because runtime.Goexit was called, a + // panic started, or fn returned normally, record the duration and send + // t.signal, indicating the fuzz test is done. defer func() { - // Detect whether the fuzz target panicked or called runtime.Goexit without - // calling F.Fuzz, F.Fail, or F.Skip. If it did, panic (possibly replacing a - // nil panic value). Nothing should recover after fRunner unwinds, so this - // should crash the process and print stack. Unfortunately, recovering here - // adds stack frames, but the location of the original panic should still be + // Detect whether the fuzz test panicked or called runtime.Goexit + // without calling F.Fuzz, F.Fail, or F.Skip. If it did, panic (possibly + // replacing a nil panic value). Nothing should recover after fRunner + // unwinds, so this should crash the process and print stack. + // Unfortunately, recovering here adds stack frames, but the location of + // the original panic should still be // clear. if f.Failed() { atomic.AddUint32(&numFailed, 1) @@ -662,7 +664,7 @@ func fRunner(f *F, fn func(*F)) { if len(f.sub) > 0 { // Unblock inputs that called T.Parallel while running the seed corpus. - // This only affects fuzz targets run as normal tests. + // This only affects fuzz tests run as normal tests. // While fuzzing, T.Parallel has no effect, so f.sub is empty, and this // branch is not taken. f.barrier is nil in that case. close(f.barrier) diff --git a/src/testing/testing.go b/src/testing/testing.go index 2ad2266e2d..3458b46d97 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -146,11 +146,9 @@ // a function is called with randomly generated inputs to find bugs not // anticipated by unit tests. // -// A fuzz target is a function that declares a set of "seed" inputs by calling -// F.Add, then provides a fuzz function by calling F.Fuzz. A fuzz target has -// the form: -// +// Functions of the form // func FuzzXxx(*testing.F) +// are considered fuzz tests. // // For example: // @@ -170,35 +168,38 @@ // }) // } // -// Seed inputs may be registered by calling F.Add or by storing files in the -// directory testdata/fuzz/<Name> (where <Name> is the name of the fuzz target) -// within the package containing the fuzz target. Seed inputs are optional, but -// the fuzzing engine may find bugs more efficiently when provided with a set -// of small seed inputs with good code coverage. +// A fuzz test maintains a seed corpus, or a set of inputs which are run by +// default, and can seed input generation. Seed inputs may be registered by +// calling (*F).Add or by storing files in the directory testdata/fuzz/<Name> +// (where <Name> is the name of the fuzz test) within the package containing +// the fuzz test. Seed inputs are optional, but the fuzzing engine may find +// bugs more efficiently when provided with a set of small seed inputs with good +// code coverage. These seed inputs can also serve as regression tests for bugs +// identified through fuzzing. // -// The fuzz function provided to F.Fuzz must accept a *testing.T parameter, -// followed by one or more parameters for random inputs. The types of arguments -// passed to F.Add must be identical to the types of these parameters. The fuzz -// function may signal that it's found a problem the same way tests do: by -// calling T.Fail (or any method that calls it like T.Error or T.Fatal) or by -// panicking. +// The function passed to (*F).Fuzz within the fuzz test is considered the fuzz +// target. A fuzz target must accept a *T parameter, followed by one or more +// parameters for random inputs. The types of arguments passed to (*F).Add must +// be identical to the types of these parameters. The fuzz target may signal +// that it's found a problem the same way tests do: by calling T.Fail (or any +// method that calls it like T.Error or T.Fatal) or by panicking. // // When fuzzing is enabled (by setting the -fuzz flag to a regular expression -// that matches a specific fuzz target), the fuzz function is called with -// arguments generated by repeatedly making random changes to the seed inputs. -// On supported platforms, 'go test' compiles the test executable with fuzzing +// that matches a specific fuzz test), the fuzz target is called with arguments +// generated by repeatedly making random changes to the seed inputs. On +// supported platforms, 'go test' compiles the test executable with fuzzing // coverage instrumentation. The fuzzing engine uses that instrumentation to -// find and cache inputs that expand coverage, increasing the liklihood of -// finding bugs. If the fuzz function finds a problem, the fuzzing engine writes -// the inputs that caused the problem to a file in the directory +// find and cache inputs that expand coverage, increasing the likelihood of +// finding bugs. If the fuzz target fails for a given input, the fuzzing engine +// writes the inputs that caused the failure to a file in the directory // testdata/fuzz/<Name> within the package directory. This file later serves as // a seed input. If the file can't be written at that location (for example, // because the directory is read-only), the fuzzing engine writes the file to // the fuzz cache directory within the build cache instead. // -// When fuzzing is disabled, the fuzz function is called with the seed inputs +// When fuzzing is disabled, the fuzz target is called with the seed inputs // registered with F.Add and seed inputs from testdata/fuzz/<Name>. In this -// mode, the fuzz target acts much like a regular test, with subtests started +// mode, the fuzz test acts much like a regular test, with subtests started // with F.Fuzz instead of T.Run. // // TODO(#48255): write and link to documentation that will be helpful to users @@ -217,7 +218,7 @@ // } // // The Skip method of *T can be used in a fuzz target if the input is invalid, -// but should not be considered a crash. For example: +// but should not be considered a failing input. For example: // // func FuzzJSONMarshalling(f *testing.F) { // f.Fuzz(func(t *testing.T, b []byte) { @@ -500,7 +501,7 @@ type common struct { cleanupName string // Name of the cleanup function. cleanupPc []uintptr // The stack trace at the point where Cleanup was called. finished bool // Test function has completed. - inFuzzFn bool // Whether the fuzz function, if this is one, is running. + inFuzzFn bool // Whether the fuzz target, if this is one, is running. chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set. bench bool // Whether the current test is a benchmark. @@ -558,7 +559,7 @@ func Verbose() bool { func (c *common) checkFuzzFn(name string) { if c.inFuzzFn { - panic(fmt.Sprintf("testing: f.%s was called inside the f.Fuzz function, use t.%s instead", name, name)) + panic(fmt.Sprintf("testing: f.%s was called inside the fuzz target, use t.%s instead", name, name)) } } @@ -1687,7 +1688,7 @@ func (m *M) Run() (code int) { deadline := m.startAlarm() haveExamples = len(m.examples) > 0 testRan, testOk := runTests(m.deps.MatchString, m.tests, deadline) - fuzzTargetsRan, fuzzTargetsOk := runFuzzTargets(m.deps, m.fuzzTargets, deadline) + fuzzTargetsRan, fuzzTargetsOk := runFuzzTests(m.deps, m.fuzzTargets, deadline) exampleRan, exampleOk := runExamples(m.deps.MatchString, m.examples) m.stopAlarm() if !testRan && !exampleRan && !fuzzTargetsRan && *matchBenchmarks == "" && *matchFuzz == "" { |
