aboutsummaryrefslogtreecommitdiff
path: root/src/testing
diff options
context:
space:
mode:
Diffstat (limited to 'src/testing')
-rw-r--r--src/testing/fuzz.go138
-rw-r--r--src/testing/testing.go55
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 == "" {