// Copyright 2020 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package testingimport ()func initFuzzFlags() {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 failing input")fuzzCacheDir = flag.String("test.fuzzcachedir", "", "directory where interesting fuzzing inputs are stored (for use only by cmd/go)")isFuzzWorker = flag.Bool("test.fuzzworker", false, "coordinate with the parent process to fuzz random values (for use only by cmd/go)")}var ( matchFuzz *string fuzzDuration durationOrCountFlag minimizeDuration = durationOrCountFlag{d: 60 * time.Second, allowZero: true} fuzzCacheDir *string isFuzzWorker *bool// 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 failures. 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.typeInternalFuzzTargetstruct { Name string Fn func(f *F)}// F is a type passed to fuzz tests.//// Fuzz tests run generated inputs against a provided fuzz target, which can// find and report potential bugs in the code being tested.//// 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 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.typeFstruct {common fuzzContext *fuzzContext testContext *testContext// inFuzzFn is true when the fuzz function is running. Most F methods cannot // be called when inFuzzFn is true. inFuzzFn bool// corpus is a set of seed corpus entries, added with F.Add and loaded // from testdata. corpus []corpusEntry result fuzzResult fuzzCalled bool}var _ TB = (*F)(nil)// corpusEntry is an alias to the same type as internal/fuzz.CorpusEntry.// We use a type alias because we don't want to export this type, and we can't// import internal/fuzz from testing.type corpusEntry = struct { Parent string Path string Data []byte Values []any Generation int IsSeed bool}// Helper marks the calling function as a test helper function.// When printing file and line information, that function will be skipped.// Helper may be called simultaneously from multiple goroutines.func ( *F) () {if .inFuzzFn {panic("testing: f.Helper was called inside the fuzz target, use t.Helper instead") }// common.Helper is inlined here. // If we called it, it would mark F.Helper as the helper // instead of the caller. .mu.Lock()defer .mu.Unlock()if .helperPCs == nil { .helperPCs = make(map[uintptr]struct{}) }// repeating code from callerName here to save walking a stack framevar [1]uintptr := runtime.Callers(2, [:]) // skip runtime.Callers + Helperif == 0 {panic("testing: zero callers found") }if , := .helperPCs[[0]]; ! { .helperPCs[[0]] = struct{}{} .helperNames = nil// map will be recreated next time it is needed }}// Fail marks the function as having failed but continues execution.func ( *F) () {// (*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 .inFuzzFn {panic("testing: f.Fail was called inside the fuzz target, use t.Fail instead") } .common.Helper() .common.Fail()}// Skipped reports whether the test was skipped.func ( *F) () 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 .inFuzzFn {panic("testing: f.Skipped was called inside the fuzz target, use t.Skipped instead") } .common.Helper()return .common.Skipped()}// 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) ( ...any) {var []anyfor := range {if := reflect.TypeOf([]); !supportedTypes[] {panic(fmt.Sprintf("testing: unsupported type to Add %v", )) } = append(, []) } .corpus = append(.corpus, corpusEntry{Values: , IsSeed: true, Path: fmt.Sprintf("seed#%d", len(.corpus))})}// supportedTypes represents all of the supported types which can be fuzzed.var supportedTypes = map[reflect.Type]bool{reflect.TypeOf(([]byte)("")): true,reflect.TypeOf((string)("")): true,reflect.TypeOf((bool)(false)): true,reflect.TypeOf((byte)(0)): true,reflect.TypeOf((rune)(0)): true,reflect.TypeOf((float32)(0)): true,reflect.TypeOf((float64)(0)): true,reflect.TypeOf((int)(0)): true,reflect.TypeOf((int8)(0)): true,reflect.TypeOf((int16)(0)): true,reflect.TypeOf((int32)(0)): true,reflect.TypeOf((int64)(0)): true,reflect.TypeOf((uint)(0)): true,reflect.TypeOf((uint8)(0)): true,reflect.TypeOf((uint16)(0)): true,reflect.TypeOf((uint32)(0)): true,reflect.TypeOf((uint64)(0)): true,}// 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.//// ff must be a function with no return value whose first argument is *T and// whose remaining arguments are the types to be fuzzed.// For example://// f.Fuzz(func(t *testing.T, b []byte, i int) { ... })//// The following types are allowed: []byte, string, bool, byte, rune, float32,// float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64.// More types may be supported in the future.//// ff must not call any *F methods, e.g. (*F).Log, (*F).Error, (*F).Skip. Use// the corresponding *T method instead. The only *F methods that are allowed in// the (*F).Fuzz function are (*F).Failed and (*F).Name.//// This function should be fast and deterministic, and its behavior should not// depend on shared state. No mutable input arguments, or pointers to them,// should be retained between executions of the fuzz function, as the memory// backing them may be mutated during a subsequent invocation. ff must not// modify the underlying data of the arguments provided by the fuzzing engine.//// When fuzzing, F.Fuzz does not return until a problem is found, time runs out// (set with -fuzztime), or the test process is interrupted by a signal. F.Fuzz// should be called exactly once, unless F.Skip or [F.Fail] is called beforehand.func ( *F) ( any) {if .fuzzCalled {panic("testing: F.Fuzz called more than once") } .fuzzCalled = trueif .failed {return } .Helper()// ff should be in the form func(*testing.T, ...interface{}) := reflect.ValueOf() := .Type()if .Kind() != reflect.Func {panic("testing: F.Fuzz must receive a function") }if .NumIn() < 2 || .In(0) != reflect.TypeOf((*T)(nil)) {panic("testing: fuzz target must receive at least two arguments, where the first argument is a *T") }if .NumOut() != 0 {panic("testing: fuzz target must not return a value") }// Save the types of the function to compare against the corpus.var []reflect.Typefor := 1; < .NumIn(); ++ { := .In()if !supportedTypes[] {panic(fmt.Sprintf("testing: unsupported type for fuzzing %v", )) } = append(, ) }// Load the testdata seed corpus. Check types of entries in the testdata // corpus and entries declared with F.Add. // // Don't load the seed corpus if this is a worker process; we won't use it.if .fuzzContext.mode != fuzzWorker {for , := range .corpus {if := .fuzzContext.deps.CheckCorpus(.Values, ); != nil {// TODO(#48302): Report the source location of the F.Add call. .Fatal() } }// Load seed corpus , := .fuzzContext.deps.ReadCorpus(filepath.Join(corpusDir, .name), )if != nil { .Fatal() }for := range { [].IsSeed = true// these are all seed corpus valuesif .fuzzContext.mode == fuzzCoordinator {// If this is the coordinator process, zero the values, since we don't need // to hold onto them. [].Values = nil } } .corpus = append(.corpus, ...) }// run calls fn on a given input, as a subtest with its own T. // run is analogous to T.Run. The test filtering and cleanup works similarly. // fn is called in its own goroutine. := func( io.Writer, corpusEntry) ( bool) {if .Values == nil {// The corpusEntry must have non-nil Values in order to run the // test. If Values is nil, it is a bug in our code.panic(fmt.Sprintf("corpus file %q was not unmarshaled", .Path)) }ifshouldFailFast() {returntrue } := .nameif .Path != "" { = fmt.Sprintf("%s/%s", , filepath.Base(.Path)) }if .testContext.isFuzzing {// Don't preserve subtest names while fuzzing. If fn calls T.Run, // there will be a very large number of subtests with duplicate names, // which will use a large amount of memory. The subtest names aren't // useful since there's no way to re-run them deterministically. .testContext.match.clearSubNames() }// Record the stack trace at the point of this call so that if the subtest // function - which runs in a separate stack - is marked as a helper, we can // continue walking the stack into the parent test.var [maxStackLen]uintptr := runtime.Callers(2, [:]) := &T{common: common{barrier: make(chanbool),signal: make(chanbool),name: ,parent: &.common,level: .level + 1,creator: [:],chatty: .chatty, },context: .testContext, }if != nil {// t.parent aliases f.common. .parent.w = } .w = indenter{&.common}if .chatty != nil { .chatty.Updatef(.name, "=== RUN %s\n", .name) } .common.inFuzzFn, .inFuzzFn = true, truegotRunner(, func( *T) { := []reflect.Value{reflect.ValueOf()}for , := range .Values { = append(, reflect.ValueOf()) }// Before resetting the current coverage, defer the snapshot so that // we make sure it is called right before the tRunner function // exits, regardless of whether it was executed cleanly, panicked, // or if the fuzzFn called t.Fatal.if .testContext.isFuzzing {defer .fuzzContext.deps.SnapshotCoverage() .fuzzContext.deps.ResetCoverage() } .Call() }) <-.signalif .chatty != nil && .chatty.json { .chatty.Updatef(.parent.name, "=== NAME %s\n", .parent.name) } .common.inFuzzFn, .inFuzzFn = false, falsereturn !.Failed() }switch .fuzzContext.mode {casefuzzCoordinator:// Fuzzing is enabled, and this is the test process started by 'go test'. // Act as the coordinator process, and coordinate workers to perform the // actual fuzzing. := filepath.Join(corpusDir, .name) := filepath.Join(*fuzzCacheDir, .name) := .fuzzContext.deps.CoordinateFuzzing(fuzzDuration.d,int64(fuzzDuration.n),minimizeDuration.d,int64(minimizeDuration.n), *parallel, .corpus, , , )if != nil { .result = fuzzResult{Error: } .Fail()fmt.Fprintf(.w, "%v\n", )if , := .(fuzzCrashError); { := .CrashPath()fmt.Fprintf(.w, "Failing input written to %s\n", ) := filepath.Base()fmt.Fprintf(.w, "To re-run:\ngo test -run=%s/%s\n", .name, ) } }// TODO(jayconrod,katiehockman): Aggregate statistics across workers // and add to FuzzResult (ie. time taken, num iterations)casefuzzWorker:// Fuzzing is enabled, and this is a worker process. Follow instructions // from the coordinator.if := .fuzzContext.deps.RunFuzzWorker(func( corpusEntry) error {// Don't write to f.w (which points to Stdout) if running from a // fuzz worker. This would become very verbose, particularly during // minimization. Return the error instead, and let the caller deal // with the output.varstrings.Builderif := (&, ); ! {returnerrors.New(.String()) }returnnil }); != 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 failing input should not be recorded. .Errorf("communicating with fuzzing coordinator: %v", ) }default:// Fuzzing is not enabled, or will be done later. Only run the seed // corpus now.for , := range .corpus { := fmt.Sprintf("%s/%s", .name, filepath.Base(.Path))if , , := .testContext.match.fullName(nil, ); { (.w, ) } } }}func ( *F) () {if *isFuzzWorker || .parent == nil {return } := fmtDuration(.duration) := "--- %s: %s (%s)\n"if .Failed() { .flushToParent(.name, , "FAIL", .name, ) } elseif .chatty != nil {if .Skipped() { .flushToParent(.name, , "SKIP", .name, ) } else { .flushToParent(.name, , "PASS", .name, ) } }}// fuzzResult contains the results of a fuzz run.type fuzzResult struct { N int// The number of iterations. T time.Duration// The total time taken. Error error// Error is the error from the failing input}func ( fuzzResult) () string {if .Error == nil {return"" }return .Error.Error()}// 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.type fuzzCrashError interface {error Unwrap() error// 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=$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 tests.type fuzzContext struct { deps testDeps mode fuzzMode}type fuzzMode uint8const ( seedCorpusOnly fuzzMode = iota fuzzCoordinator fuzzWorker)// 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( testDeps, []InternalFuzzTarget, time.Time) (, bool) { = trueiflen() == 0 || *isFuzzWorker {return , } := newMatcher(.MatchString, *match, "-test.run", *skip)var *matcherif *matchFuzz != "" { = newMatcher(.MatchString, *matchFuzz, "-test.fuzz", *skip) }for , := rangecpuList {runtime.GOMAXPROCS()for := uint(0); < *count; ++ {ifshouldFailFast() {break } := newTestContext(*parallel, ) .deadline = := &fuzzContext{deps: , mode: seedCorpusOnly} := common{w: os.Stdout} // gather output in one placeifVerbose() { .chatty = newChattyPrinter(.w) }for , := range {ifshouldFailFast() {break } , , := .match.fullName(nil, .Name)if ! {continue }if != nil {if , , := .fullName(nil, .Name); {// If this will be fuzzed, then don't run the seed corpus // right now. That will happen later.continue } } := &F{common: common{signal: make(chanbool),barrier: make(chanbool),name: ,parent: &,level: .level + 1,chatty: .chatty, },testContext: ,fuzzContext: , } .w = indenter{&.common}if .chatty != nil { .chatty.Updatef(.name, "=== RUN %s\n", .name) }gofRunner(, .Fn) <-.signalif .chatty != nil && .chatty.json { .chatty.Updatef(.parent.name, "=== NAME %s\n", .parent.name) } = && !.Failed() = || .ran }if ! {// There were no tests to run on this iteration. // This won't change, so no reason to keep trying.break } } }return , }// 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( testDeps, []InternalFuzzTarget) ( bool) {iflen() == 0 || *matchFuzz == "" {returntrue } := newMatcher(.MatchString, *matchFuzz, "-test.fuzz", *skip) := newTestContext(1, ) .isFuzzing = true := &fuzzContext{deps: , } := common{w: os.Stdout}if *isFuzzWorker { .w = io.Discard .mode = fuzzWorker } else { .mode = fuzzCoordinator }ifVerbose() && !*isFuzzWorker { .chatty = newChattyPrinter(.w) }var *InternalFuzzTargetvarstringvar []stringfor := range { , , := .match.fullName(nil, [].Name)if ! {continue } = append(, ) = &[] = }iflen() == 0 {fmt.Fprintln(os.Stderr, "testing: warning: no fuzz tests to fuzz")returntrue }iflen() > 1 {fmt.Fprintf(os.Stderr, "testing: will not fuzz, -fuzz matches more than one fuzz test: %v\n", )returnfalse } := &F{common: common{signal: make(chanbool),barrier: nil, // T.Parallel has no effect when fuzzing.name: ,parent: &,level: .level + 1,chatty: .chatty, },fuzzContext: ,testContext: , } .w = indenter{&.common}if .chatty != nil { .chatty.Updatef(.name, "=== RUN %s\n", .name) }gofRunner(, .Fn) <-.signalif .chatty != nil { .chatty.Updatef(.parent.name, "=== NAME %s\n", .parent.name) }return !.failed}// 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.// 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, 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 test is done.deferfunc() {// 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. .checkRaces()if .Failed() {numFailed.Add(1) } := recover()if == nil { .mu.RLock() := !.fuzzCalled && !.skipped && !.failedif !.finished && !.skipped && !.failed { = errNilPanicOrGoexit } .mu.RUnlock()if && == nil { .Error("returned without calling F.Fuzz, F.Fail, or F.Skip") } }// Use a deferred call to ensure that we report that the test is // complete even if a cleanup function calls F.FailNow. See issue 41355. := falsedeferfunc() {if ! {// Only report that the test is complete if it doesn't panic, // as otherwise the test binary can exit before the panic is // reported to the user. See issue 41479. .signal <- true } }()// If we recovered a panic or inappropriate runtime.Goexit, fail the test, // flush the output log up to the root, then panic. := func( any) { .Fail()if := .runCleanup(recoverAndReturnPanic); != nil { .Logf("cleanup panicked with %v", ) }for := &.common; .parent != nil; = .parent { .mu.Lock() .duration += highPrecisionTimeSince(.start) := .duration .mu.Unlock() .flushToParent(.name, "--- FAIL: %s (%s)\n", .name, fmtDuration()) } = truepanic() }if != nil { () }// No panic or inappropriate Goexit. .duration += highPrecisionTimeSince(.start)iflen(.sub) > 0 {// Unblock inputs that called T.Parallel while running the seed corpus. // 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. .testContext.release()close(.barrier)// Wait for the subtests to complete.for , := range .sub { <-.signal } := highPrecisionTimeNow() := .runCleanup(recoverAndReturnPanic) .duration += highPrecisionTimeSince()if != nil { () } }// Report after all subtests have finished. .report() .done = true .setRan() }()deferfunc() {iflen(.sub) == 0 { .runCleanup(normalPanic) } }() .start = highPrecisionTimeNow() .resetRaces() ()// Code beyond this point will not be executed when FailNow or SkipNow // is invoked. .mu.Lock() .finished = true .mu.Unlock()}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.