| // 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 testing |
| |
| import ( |
| "errors" |
| "flag" |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| "reflect" |
| "runtime" |
| "strings" |
| "time" |
| ) |
| |
| 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. |
| type InternalFuzzTarget struct { |
| 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. |
| type F struct { |
| 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 *F) Helper() { |
| if f.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. |
| f.mu.Lock() |
| defer f.mu.Unlock() |
| if f.helperPCs == nil { |
| f.helperPCs = make(map[uintptr]struct{}) |
| } |
| // repeating code from callerName here to save walking a stack frame |
| var pc [1]uintptr |
| n := runtime.Callers(2, pc[:]) // skip runtime.Callers + Helper |
| if n == 0 { |
| panic("testing: zero callers found") |
| } |
| if _, found := f.helperPCs[pc[0]]; !found { |
| f.helperPCs[pc[0]] = struct{}{} |
| f.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() { |
| // (*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 fuzz target, use t.Fail instead") |
| } |
| f.common.Helper() |
| f.common.Fail() |
| } |
| |
| // Skipped reports whether the test was skipped. |
| 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 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 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 ...any) { |
| var values []any |
| for i := range args { |
| if t := reflect.TypeOf(args[i]); !supportedTypes[t] { |
| panic(fmt.Sprintf("testing: unsupported type to Add %v", t)) |
| } |
| values = append(values, args[i]) |
| } |
| f.corpus = append(f.corpus, corpusEntry{Values: values, IsSeed: true, Path: fmt.Sprintf("seed#%d", len(f.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 mutatable 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 *F) Fuzz(ff any) { |
| if f.fuzzCalled { |
| panic("testing: F.Fuzz called more than once") |
| } |
| f.fuzzCalled = true |
| if f.failed { |
| return |
| } |
| f.Helper() |
| |
| // ff should be in the form func(*testing.T, ...interface{}) |
| fn := reflect.ValueOf(ff) |
| fnType := fn.Type() |
| if fnType.Kind() != reflect.Func { |
| panic("testing: F.Fuzz must receive a function") |
| } |
| if fnType.NumIn() < 2 || fnType.In(0) != reflect.TypeOf((*T)(nil)) { |
| panic("testing: fuzz target must receive at least two arguments, where the first argument is a *T") |
| } |
| if fnType.NumOut() != 0 { |
| panic("testing: fuzz target must not return a value") |
| } |
| |
| // Save the types of the function to compare against the corpus. |
| var types []reflect.Type |
| for i := 1; i < fnType.NumIn(); i++ { |
| t := fnType.In(i) |
| if !supportedTypes[t] { |
| panic(fmt.Sprintf("testing: unsupported type for fuzzing %v", t)) |
| } |
| types = append(types, t) |
| } |
| |
| // 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 f.fuzzContext.mode != fuzzWorker { |
| for _, c := range f.corpus { |
| if err := f.fuzzContext.deps.CheckCorpus(c.Values, types); err != nil { |
| // TODO(#48302): Report the source location of the F.Add call. |
| f.Fatal(err) |
| } |
| } |
| |
| // Load seed corpus |
| c, err := f.fuzzContext.deps.ReadCorpus(filepath.Join(corpusDir, f.name), types) |
| if err != nil { |
| f.Fatal(err) |
| } |
| for i := range c { |
| c[i].IsSeed = true // these are all seed corpus values |
| if f.fuzzContext.mode == fuzzCoordinator { |
| // If this is the coordinator process, zero the values, since we don't need |
| // to hold onto them. |
| c[i].Values = nil |
| } |
| } |
| |
| f.corpus = append(f.corpus, c...) |
| } |
| |
| // 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. |
| run := func(captureOut io.Writer, e corpusEntry) (ok bool) { |
| if e.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", e.Path)) |
| } |
| if shouldFailFast() { |
| return true |
| } |
| testName := f.name |
| if e.Path != "" { |
| testName = fmt.Sprintf("%s/%s", testName, filepath.Base(e.Path)) |
| } |
| if f.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. |
| f.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 pc [maxStackLen]uintptr |
| n := runtime.Callers(2, pc[:]) |
| t := &T{ |
| common: common{ |
| barrier: make(chan bool), |
| signal: make(chan bool), |
| name: testName, |
| parent: &f.common, |
| level: f.level + 1, |
| creator: pc[:n], |
| chatty: f.chatty, |
| }, |
| context: f.testContext, |
| } |
| if captureOut != nil { |
| // t.parent aliases f.common. |
| t.parent.w = captureOut |
| } |
| t.w = indenter{&t.common} |
| if t.chatty != nil { |
| t.chatty.Updatef(t.name, "=== RUN %s\n", t.name) |
| } |
| f.common.inFuzzFn, f.inFuzzFn = true, true |
| go tRunner(t, func(t *T) { |
| args := []reflect.Value{reflect.ValueOf(t)} |
| for _, v := range e.Values { |
| args = append(args, reflect.ValueOf(v)) |
| } |
| // 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 f.testContext.isFuzzing { |
| defer f.fuzzContext.deps.SnapshotCoverage() |
| f.fuzzContext.deps.ResetCoverage() |
| } |
| fn.Call(args) |
| }) |
| <-t.signal |
| if t.chatty != nil && t.chatty.json { |
| t.chatty.Updatef(t.parent.name, "=== NAME %s\n", t.parent.name) |
| } |
| f.common.inFuzzFn, f.inFuzzFn = false, false |
| return !t.Failed() |
| } |
| |
| switch f.fuzzContext.mode { |
| case fuzzCoordinator: |
| // 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. |
| corpusTargetDir := filepath.Join(corpusDir, f.name) |
| cacheTargetDir := filepath.Join(*fuzzCacheDir, f.name) |
| err := f.fuzzContext.deps.CoordinateFuzzing( |
| fuzzDuration.d, |
| int64(fuzzDuration.n), |
| minimizeDuration.d, |
| int64(minimizeDuration.n), |
| *parallel, |
| f.corpus, |
| types, |
| corpusTargetDir, |
| cacheTargetDir) |
| if err != nil { |
| f.result = fuzzResult{Error: err} |
| f.Fail() |
| fmt.Fprintf(f.w, "%v\n", err) |
| if crashErr, ok := err.(fuzzCrashError); ok { |
| crashPath := crashErr.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) |
| } |
| } |
| // TODO(jayconrod,katiehockman): Aggregate statistics across workers |
| // and add to FuzzResult (ie. time taken, num iterations) |
| |
| case fuzzWorker: |
| // Fuzzing is enabled, and this is a worker process. Follow instructions |
| // from the coordinator. |
| if err := f.fuzzContext.deps.RunFuzzWorker(func(e 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. |
| var buf strings.Builder |
| if ok := run(&buf, e); !ok { |
| return errors.New(buf.String()) |
| } |
| return nil |
| }); 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 failing input should not be recorded. |
| f.Errorf("communicating with fuzzing coordinator: %v", err) |
| } |
| |
| default: |
| // Fuzzing is not enabled, or will be done later. Only run the seed |
| // corpus now. |
| for _, e := range f.corpus { |
| name := fmt.Sprintf("%s/%s", f.name, filepath.Base(e.Path)) |
| if _, ok, _ := f.testContext.match.fullName(nil, name); ok { |
| run(f.w, e) |
| } |
| } |
| } |
| } |
| |
| func (f *F) report() { |
| if *isFuzzWorker || f.parent == nil { |
| return |
| } |
| dstr := fmtDuration(f.duration) |
| format := "--- %s: %s (%s)\n" |
| if f.Failed() { |
| f.flushToParent(f.name, format, "FAIL", f.name, dstr) |
| } else if f.chatty != nil { |
| if f.Skipped() { |
| f.flushToParent(f.name, format, "SKIP", f.name, dstr) |
| } else { |
| f.flushToParent(f.name, format, "PASS", f.name, dstr) |
| } |
| } |
| } |
| |
| // 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 (r fuzzResult) String() string { |
| if r.Error == nil { |
| return "" |
| } |
| return r.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 uint8 |
| |
| const ( |
| 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(deps testDeps, fuzzTests []InternalFuzzTarget, deadline time.Time) (ran, ok bool) { |
| ok = true |
| if len(fuzzTests) == 0 || *isFuzzWorker { |
| return ran, ok |
| } |
| m := newMatcher(deps.MatchString, *match, "-test.run", *skip) |
| var mFuzz *matcher |
| if *matchFuzz != "" { |
| mFuzz = newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip) |
| } |
| |
| for _, procs := range cpuList { |
| runtime.GOMAXPROCS(procs) |
| for i := uint(0); i < *count; i++ { |
| if shouldFailFast() { |
| break |
| } |
| |
| tctx := newTestContext(*parallel, m) |
| tctx.deadline = deadline |
| fctx := &fuzzContext{deps: deps, mode: seedCorpusOnly} |
| root := common{w: os.Stdout} // gather output in one place |
| if Verbose() { |
| root.chatty = newChattyPrinter(root.w) |
| } |
| for _, ft := range fuzzTests { |
| if shouldFailFast() { |
| break |
| } |
| testName, matched, _ := tctx.match.fullName(nil, ft.Name) |
| if !matched { |
| continue |
| } |
| if mFuzz != nil { |
| if _, fuzzMatched, _ := mFuzz.fullName(nil, ft.Name); fuzzMatched { |
| // If this will be fuzzed, then don't run the seed corpus |
| // right now. That will happen later. |
| continue |
| } |
| } |
| f := &F{ |
| common: common{ |
| signal: make(chan bool), |
| barrier: make(chan bool), |
| name: testName, |
| parent: &root, |
| level: root.level + 1, |
| chatty: root.chatty, |
| }, |
| testContext: tctx, |
| fuzzContext: fctx, |
| } |
| f.w = indenter{&f.common} |
| if f.chatty != nil { |
| f.chatty.Updatef(f.name, "=== RUN %s\n", f.name) |
| } |
| go fRunner(f, ft.Fn) |
| <-f.signal |
| if f.chatty != nil && f.chatty.json { |
| f.chatty.Updatef(f.parent.name, "=== NAME %s\n", f.parent.name) |
| } |
| ok = ok && !f.Failed() |
| ran = ran || f.ran |
| } |
| if !ran { |
| // There were no tests to run on this iteration. |
| // This won't change, so no reason to keep trying. |
| break |
| } |
| } |
| } |
| |
| return ran, ok |
| } |
| |
| // 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, fuzzTests []InternalFuzzTarget) (ok bool) { |
| if len(fuzzTests) == 0 || *matchFuzz == "" { |
| return true |
| } |
| m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip) |
| tctx := newTestContext(1, m) |
| tctx.isFuzzing = true |
| fctx := &fuzzContext{ |
| deps: deps, |
| } |
| root := common{w: os.Stdout} |
| if *isFuzzWorker { |
| root.w = io.Discard |
| fctx.mode = fuzzWorker |
| } else { |
| fctx.mode = fuzzCoordinator |
| } |
| if Verbose() && !*isFuzzWorker { |
| root.chatty = newChattyPrinter(root.w) |
| } |
| var fuzzTest *InternalFuzzTarget |
| var testName string |
| var matched []string |
| for i := range fuzzTests { |
| name, ok, _ := tctx.match.fullName(nil, fuzzTests[i].Name) |
| if !ok { |
| continue |
| } |
| matched = append(matched, name) |
| fuzzTest = &fuzzTests[i] |
| testName = name |
| } |
| if len(matched) == 0 { |
| 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 fuzz test: %v\n", matched) |
| return false |
| } |
| |
| f := &F{ |
| common: common{ |
| signal: make(chan bool), |
| barrier: nil, // T.Parallel has no effect when fuzzing. |
| name: testName, |
| parent: &root, |
| level: root.level + 1, |
| chatty: root.chatty, |
| }, |
| fuzzContext: fctx, |
| testContext: tctx, |
| } |
| f.w = indenter{&f.common} |
| if f.chatty != nil { |
| f.chatty.Updatef(f.name, "=== RUN %s\n", f.name) |
| } |
| go fRunner(f, fuzzTest.Fn) |
| <-f.signal |
| if f.chatty != nil { |
| f.chatty.Updatef(f.parent.name, "=== NAME %s\n", f.parent.name) |
| } |
| return !f.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 *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 test is done. |
| defer func() { |
| // 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. |
| f.checkRaces() |
| if f.Failed() { |
| numFailed.Add(1) |
| } |
| err := recover() |
| if err == nil { |
| f.mu.RLock() |
| fuzzNotCalled := !f.fuzzCalled && !f.skipped && !f.failed |
| if !f.finished && !f.skipped && !f.failed { |
| err = errNilPanicOrGoexit |
| } |
| f.mu.RUnlock() |
| if fuzzNotCalled && err == nil { |
| f.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. |
| didPanic := false |
| defer func() { |
| if !didPanic { |
| // 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. |
| f.signal <- true |
| } |
| }() |
| |
| // If we recovered a panic or inappropriate runtime.Goexit, fail the test, |
| // flush the output log up to the root, then panic. |
| doPanic := func(err any) { |
| f.Fail() |
| if r := f.runCleanup(recoverAndReturnPanic); r != nil { |
| f.Logf("cleanup panicked with %v", r) |
| } |
| for root := &f.common; root.parent != nil; root = root.parent { |
| root.mu.Lock() |
| root.duration += time.Since(root.start) |
| d := root.duration |
| root.mu.Unlock() |
| root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d)) |
| } |
| didPanic = true |
| panic(err) |
| } |
| if err != nil { |
| doPanic(err) |
| } |
| |
| // No panic or inappropriate Goexit. |
| f.duration += time.Since(f.start) |
| |
| if len(f.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. |
| f.testContext.release() |
| close(f.barrier) |
| // Wait for the subtests to complete. |
| for _, sub := range f.sub { |
| <-sub.signal |
| } |
| cleanupStart := time.Now() |
| err := f.runCleanup(recoverAndReturnPanic) |
| f.duration += time.Since(cleanupStart) |
| if err != nil { |
| doPanic(err) |
| } |
| } |
| |
| // Report after all subtests have finished. |
| f.report() |
| f.done = true |
| f.setRan() |
| }() |
| defer func() { |
| if len(f.sub) == 0 { |
| f.runCleanup(normalPanic) |
| } |
| }() |
| |
| f.start = time.Now() |
| f.resetRaces() |
| fn(f) |
| |
| // Code beyond this point will not be executed when FailNow or SkipNow |
| // is invoked. |
| f.mu.Lock() |
| f.finished = true |
| f.mu.Unlock() |
| } |