codekingpro/portable-devtools
114k
1// Copyright 2020 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4 5package testing6 7import (8 "context"9 "errors"10 "flag"11 "fmt"12 "io"13 "os"14 "path/filepath"15 "reflect"16 "runtime"17 "strings"18 "time"19)20 21func initFuzzFlags() {22 matchFuzz = flag.String("test.fuzz", "", "run the fuzz test matching `regexp`")23 flag.Var(&fuzzDuration, "test.fuzztime", "time to spend fuzzing; default is to run indefinitely")24 flag.Var(&minimizeDuration, "test.fuzzminimizetime", "time to spend minimizing a value after finding a failing input")25 26 fuzzCacheDir = flag.String("test.fuzzcachedir", "", "directory where interesting fuzzing inputs are stored (for use only by cmd/go)")27 isFuzzWorker = flag.Bool("test.fuzzworker", false, "coordinate with the parent process to fuzz random values (for use only by cmd/go)")28}29 30var (31 matchFuzz *string32 fuzzDuration durationOrCountFlag33 minimizeDuration = durationOrCountFlag{d: 60 * time.Second, allowZero: true}34 fuzzCacheDir *string35 isFuzzWorker *bool36 37 // corpusDir is the parent directory of the fuzz test's seed corpus within38 // the package.39 corpusDir = "testdata/fuzz"40)41 42// fuzzWorkerExitCode is used as an exit code by fuzz worker processes after an43// internal error. This distinguishes internal errors from uncontrolled panics44// and other failures. Keep in sync with internal/fuzz.workerExitCode.45const fuzzWorkerExitCode = 7046 47// InternalFuzzTarget is an internal type but exported because it is48// cross-package; it is part of the implementation of the "go test" command.49type InternalFuzzTarget struct {50 Name string51 Fn func(f *F)52}53 54// F is a type passed to fuzz tests.55//56// Fuzz tests run generated inputs against a provided fuzz target, which can57// find and report potential bugs in the code being tested.58//59// A fuzz test runs the seed corpus by default, which includes entries provided60// by [F.Add] and entries in the testdata/fuzz/<FuzzTestName> directory. After61// any necessary setup and calls to [F.Add], the fuzz test must then call62// [F.Fuzz] to provide the fuzz target. See the testing package documentation63// for an example, and see the [F.Fuzz] and [F.Add] method documentation for64// details.65//66// *F methods can only be called before [F.Fuzz]. Once the test is67// executing the fuzz target, only [*T] methods can be used. The only *F methods68// that are allowed in the [F.Fuzz] function are [F.Failed] and [F.Name].69type F struct {70 common71 fstate *fuzzState72 tstate *testState73 74 // inFuzzFn is true when the fuzz function is running. Most F methods cannot75 // be called when inFuzzFn is true.76 inFuzzFn bool77 78 // corpus is a set of seed corpus entries, added with F.Add and loaded79 // from testdata.80 corpus []corpusEntry81 82 result fuzzResult83 fuzzCalled bool84}85 86var _ TB = (*F)(nil)87 88// corpusEntry is an alias to the same type as internal/fuzz.CorpusEntry.89// We use a type alias because we don't want to export this type, and we can't90// import internal/fuzz from testing.91type corpusEntry = struct {92 Parent string93 Path string94 Data []byte95 Values []any96 Generation int97 IsSeed bool98}99 100// Helper marks the calling function as a test helper function.101// When printing file and line information, that function will be skipped.102// Helper may be called simultaneously from multiple goroutines.103func (f *F) Helper() {104 if f.inFuzzFn {105 panic("testing: f.Helper was called inside the fuzz target, use t.Helper instead")106 }107 108 // common.Helper is inlined here.109 // If we called it, it would mark F.Helper as the helper110 // instead of the caller.111 f.mu.Lock()112 defer f.mu.Unlock()113 if f.helperPCs == nil {114 f.helperPCs = make(map[uintptr]struct{})115 }116 // repeating code from callerName here to save walking a stack frame117 var pc [1]uintptr118 n := runtime.Callers(2, pc[:]) // skip runtime.Callers + Helper119 if n == 0 {120 panic("testing: zero callers found")121 }122 if _, found := f.helperPCs[pc[0]]; !found {123 f.helperPCs[pc[0]] = struct{}{}124 f.helperNames = nil // map will be recreated next time it is needed125 }126}127 128// Fail marks the function as having failed but continues execution.129func (f *F) Fail() {130 // (*F).Fail may be called by (*T).Fail, which we should allow. However, we131 // shouldn't allow direct (*F).Fail calls from inside the (*F).Fuzz function.132 if f.inFuzzFn {133 panic("testing: f.Fail was called inside the fuzz target, use t.Fail instead")134 }135 f.common.Helper()136 f.common.Fail()137}138 139// Skipped reports whether the test was skipped.140func (f *F) Skipped() bool {141 // (*F).Skipped may be called by tRunner, which we should allow. However, we142 // shouldn't allow direct (*F).Skipped calls from inside the (*F).Fuzz function.143 if f.inFuzzFn {144 panic("testing: f.Skipped was called inside the fuzz target, use t.Skipped instead")145 }146 f.common.Helper()147 return f.common.Skipped()148}149 150// Add will add the arguments to the seed corpus for the fuzz test. This will be151// a no-op if called after or within the fuzz target, and args must match the152// arguments for the fuzz target.153func (f *F) Add(args ...any) {154 var values []any155 for i := range args {156 if t := reflect.TypeOf(args[i]); !supportedTypes[t] {157 panic(fmt.Sprintf("testing: unsupported type to Add %v", t))158 }159 values = append(values, args[i])160 }161 f.corpus = append(f.corpus, corpusEntry{Values: values, IsSeed: true, Path: fmt.Sprintf("seed#%d", len(f.corpus))})162}163 164// supportedTypes represents all of the supported types which can be fuzzed.165var supportedTypes = map[reflect.Type]bool{166 reflect.TypeFor[[]byte](): true,167 reflect.TypeFor[string](): true,168 reflect.TypeFor[bool](): true,169 reflect.TypeFor[byte](): true,170 reflect.TypeFor[rune](): true,171 reflect.TypeFor[float32](): true,172 reflect.TypeFor[float64](): true,173 reflect.TypeFor[int](): true,174 reflect.TypeFor[int8](): true,175 reflect.TypeFor[int16](): true,176 reflect.TypeFor[int32](): true,177 reflect.TypeFor[int64](): true,178 reflect.TypeFor[uint](): true,179 reflect.TypeFor[uint8](): true,180 reflect.TypeFor[uint16](): true,181 reflect.TypeFor[uint32](): true,182 reflect.TypeFor[uint64](): true,183}184 185// Fuzz runs the fuzz function, ff, for fuzz testing. If ff fails for a set of186// arguments, those arguments will be added to the seed corpus.187//188// ff must be a function with no return value whose first argument is [*T] and189// whose remaining arguments are the types to be fuzzed.190// For example:191//192// f.Fuzz(func(t *testing.T, b []byte, i int) { ... })193//194// The following types are allowed: []byte, string, bool, byte, rune, float32,195// float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64.196// More types may be supported in the future.197//198// ff must not call any [*F] methods, e.g. [F.Log], [F.Error], [F.Skip]. Use199// the corresponding [*T] method instead. The only [*F] methods that are allowed in200// the F.Fuzz function are [F.Failed] and [F.Name].201//202// This function should be fast and deterministic, and its behavior should not203// depend on shared state. No mutable input arguments, or pointers to them,204// should be retained between executions of the fuzz function, as the memory205// backing them may be mutated during a subsequent invocation. ff must not206// modify the underlying data of the arguments provided by the fuzzing engine.207//208// When fuzzing, F.Fuzz does not return until a problem is found, time runs out209// (set with -fuzztime), or the test process is interrupted by a signal. F.Fuzz210// should be called exactly once, unless [F.Skip] or [F.Fail] is called beforehand.211func (f *F) Fuzz(ff any) {212 if f.fuzzCalled {213 panic("testing: F.Fuzz called more than once")214 }215 f.fuzzCalled = true216 if f.failed {217 return218 }219 f.Helper()220 221 // ff should be in the form func(*testing.T, ...interface{})222 fn := reflect.ValueOf(ff)223 fnType := fn.Type()224 if fnType.Kind() != reflect.Func {225 panic("testing: F.Fuzz must receive a function")226 }227 if fnType.NumIn() < 2 || fnType.In(0) != reflect.TypeFor[*T]() {228 panic("testing: fuzz target must receive at least two arguments, where the first argument is a *T")229 }230 if fnType.NumOut() != 0 {231 panic("testing: fuzz target must not return a value")232 }233 234 // Save the types of the function to compare against the corpus.235 var types []reflect.Type236 for i := 1; i < fnType.NumIn(); i++ {237 t := fnType.In(i)238 if !supportedTypes[t] {239 panic(fmt.Sprintf("testing: unsupported type for fuzzing %v", t))240 }241 types = append(types, t)242 }243 244 // Load the testdata seed corpus. Check types of entries in the testdata245 // corpus and entries declared with F.Add.246 //247 // Don't load the seed corpus if this is a worker process; we won't use it.248 if f.fstate.mode != fuzzWorker {249 for _, c := range f.corpus {250 if err := f.fstate.deps.CheckCorpus(c.Values, types); err != nil {251 // TODO(#48302): Report the source location of the F.Add call.252 f.Fatal(err)253 }254 }255 256 // Load seed corpus257 c, err := f.fstate.deps.ReadCorpus(filepath.Join(corpusDir, f.name), types)258 if err != nil {259 f.Fatal(err)260 }261 for i := range c {262 c[i].IsSeed = true // these are all seed corpus values263 if f.fstate.mode == fuzzCoordinator {264 // If this is the coordinator process, zero the values, since we don't need265 // to hold onto them.266 c[i].Values = nil267 }268 }269 270 f.corpus = append(f.corpus, c...)271 }272 273 // run calls fn on a given input, as a subtest with its own T.274 // run is analogous to T.Run. The test filtering and cleanup works similarly.275 // fn is called in its own goroutine.276 run := func(captureOut io.Writer, e corpusEntry) (ok bool) {277 if e.Values == nil {278 // The corpusEntry must have non-nil Values in order to run the279 // test. If Values is nil, it is a bug in our code.280 panic(fmt.Sprintf("corpus file %q was not unmarshaled", e.Path))281 }282 if shouldFailFast() {283 return true284 }285 testName := f.name286 if e.Path != "" {287 testName = fmt.Sprintf("%s/%s", testName, filepath.Base(e.Path))288 }289 if f.tstate.isFuzzing {290 // Don't preserve subtest names while fuzzing. If fn calls T.Run,291 // there will be a very large number of subtests with duplicate names,292 // which will use a large amount of memory. The subtest names aren't293 // useful since there's no way to re-run them deterministically.294 f.tstate.match.clearSubNames()295 }296 297 ctx, cancelCtx := context.WithCancel(f.ctx)298 299 // Record the stack trace at the point of this call so that if the subtest300 // function - which runs in a separate stack - is marked as a helper, we can301 // continue walking the stack into the parent test.302 var pc [maxStackLen]uintptr303 n := runtime.Callers(2, pc[:])304 t := &T{305 common: common{306 barrier: make(chan bool),307 signal: make(chan bool),308 name: testName,309 parent: &f.common,310 level: f.level + 1,311 creator: pc[:n],312 chatty: f.chatty,313 ctx: ctx,314 cancelCtx: cancelCtx,315 },316 tstate: f.tstate,317 }318 if captureOut != nil {319 // t.parent aliases f.common.320 t.parent.w = captureOut321 }322 t.w = indenter{&t.common}323 t.setOutputWriter()324 if t.chatty != nil {325 t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)326 }327 f.common.inFuzzFn, f.inFuzzFn = true, true328 go tRunner(t, func(t *T) {329 args := []reflect.Value{reflect.ValueOf(t)}330 for _, v := range e.Values {331 args = append(args, reflect.ValueOf(v))332 }333 // Before resetting the current coverage, defer the snapshot so that334 // we make sure it is called right before the tRunner function335 // exits, regardless of whether it was executed cleanly, panicked,336 // or if the fuzzFn called t.Fatal.337 if f.tstate.isFuzzing {338 defer f.fstate.deps.SnapshotCoverage()339 f.fstate.deps.ResetCoverage()340 }341 fn.Call(args)342 })343 <-t.signal344 if t.chatty != nil && t.chatty.json {345 t.chatty.Updatef(t.parent.name, "=== NAME %s\n", t.parent.name)346 }347 f.common.inFuzzFn, f.inFuzzFn = false, false348 return !t.Failed()349 }350 351 switch f.fstate.mode {352 case fuzzCoordinator:353 // Fuzzing is enabled, and this is the test process started by 'go test'.354 // Act as the coordinator process, and coordinate workers to perform the355 // actual fuzzing.356 corpusTargetDir := filepath.Join(corpusDir, f.name)357 cacheTargetDir := filepath.Join(*fuzzCacheDir, f.name)358 err := f.fstate.deps.CoordinateFuzzing(359 fuzzDuration.d,360 int64(fuzzDuration.n),361 minimizeDuration.d,362 int64(minimizeDuration.n),363 *parallel,364 f.corpus,365 types,366 corpusTargetDir,367 cacheTargetDir)368 if err != nil {369 f.result = fuzzResult{Error: err}370 f.Fail()371 fmt.Fprintf(f.w, "%v\n", err)372 if crashErr, ok := err.(fuzzCrashError); ok {373 crashPath := crashErr.CrashPath()374 fmt.Fprintf(f.w, "Failing input written to %s\n", crashPath)375 testName := filepath.Base(crashPath)376 fmt.Fprintf(f.w, "To re-run:\ngo test -run=%s/%s\n", f.name, testName)377 }378 }379 // TODO(jayconrod,katiehockman): Aggregate statistics across workers380 // and add to FuzzResult (ie. time taken, num iterations)381 382 case fuzzWorker:383 // Fuzzing is enabled, and this is a worker process. Follow instructions384 // from the coordinator.385 if err := f.fstate.deps.RunFuzzWorker(func(e corpusEntry) error {386 // Don't write to f.w (which points to Stdout) if running from a387 // fuzz worker. This would become very verbose, particularly during388 // minimization. Return the error instead, and let the caller deal389 // with the output.390 var buf strings.Builder391 if ok := run(&buf, e); !ok {392 return errors.New(buf.String())393 }394 return nil395 }); err != nil {396 // Internal errors are marked with f.Fail; user code may call this too, before F.Fuzz.397 // The worker will exit with fuzzWorkerExitCode, indicating this is a failure398 // (and 'go test' should exit non-zero) but a failing input should not be recorded.399 f.Errorf("communicating with fuzzing coordinator: %v", err)400 }401 402 default:403 // Fuzzing is not enabled, or will be done later. Only run the seed404 // corpus now.405 for _, e := range f.corpus {406 name := fmt.Sprintf("%s/%s", f.name, filepath.Base(e.Path))407 if _, ok, _ := f.tstate.match.fullName(nil, name); ok {408 run(f.w, e)409 }410 }411 }412}413 414func (f *F) report() {415 if *isFuzzWorker || f.parent == nil {416 return417 }418 dstr := fmtDuration(f.duration)419 format := "--- %s: %s (%s)\n"420 if f.Failed() {421 f.flushToParent(f.name, format, "FAIL", f.name, dstr)422 } else if f.chatty != nil {423 if f.Skipped() {424 f.flushToParent(f.name, format, "SKIP", f.name, dstr)425 } else {426 f.flushToParent(f.name, format, "PASS", f.name, dstr)427 }428 }429}430 431// fuzzResult contains the results of a fuzz run.432type fuzzResult struct {433 N int // The number of iterations.434 T time.Duration // The total time taken.435 Error error // Error is the error from the failing input436}437 438func (r fuzzResult) String() string {439 if r.Error == nil {440 return ""441 }442 return r.Error.Error()443}444 445// fuzzCrashError is satisfied by a failing input detected while fuzzing.446// These errors are written to the seed corpus and can be re-run with 'go test'.447// Errors within the fuzzing framework (like I/O errors between coordinator448// and worker processes) don't satisfy this interface.449type fuzzCrashError interface {450 error451 Unwrap() error452 453 // CrashPath returns the path of the subtest that corresponds to the saved454 // crash input file in the seed corpus. The test can be re-run with go test455 // -run=$test/$name $test is the fuzz test name, and $name is the456 // filepath.Base of the string returned here.457 CrashPath() string458}459 460// fuzzState holds fields common to all fuzz tests.461type fuzzState struct {462 deps testDeps463 mode fuzzMode464}465 466type fuzzMode uint8467 468const (469 seedCorpusOnly fuzzMode = iota470 fuzzCoordinator471 fuzzWorker472)473 474// runFuzzTests runs the fuzz tests matching the pattern for -run. This will475// only run the (*F).Fuzz function for each seed corpus without using the476// fuzzing engine to generate or mutate inputs.477func runFuzzTests(deps testDeps, fuzzTests []InternalFuzzTarget, deadline time.Time) (ran, ok bool) {478 ok = true479 if len(fuzzTests) == 0 || *isFuzzWorker {480 return ran, ok481 }482 m := newMatcher(deps.MatchString, *match, "-test.run", *skip)483 var mFuzz *matcher484 if *matchFuzz != "" {485 mFuzz = newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip)486 }487 488 for _, procs := range cpuList {489 runtime.GOMAXPROCS(procs)490 for i := uint(0); i < *count; i++ {491 if shouldFailFast() {492 break493 }494 495 tstate := newTestState(*parallel, m)496 tstate.deadline = deadline497 fstate := &fuzzState{deps: deps, mode: seedCorpusOnly}498 root := common{w: os.Stdout} // gather output in one place499 if Verbose() {500 root.chatty = newChattyPrinter(root.w)501 }502 for _, ft := range fuzzTests {503 if shouldFailFast() {504 break505 }506 testName, matched, _ := tstate.match.fullName(nil, ft.Name)507 if !matched {508 continue509 }510 if mFuzz != nil {511 if _, fuzzMatched, _ := mFuzz.fullName(nil, ft.Name); fuzzMatched {512 // If this will be fuzzed, then don't run the seed corpus513 // right now. That will happen later.514 continue515 }516 }517 ctx, cancelCtx := context.WithCancel(context.Background())518 f := &F{519 common: common{520 signal: make(chan bool),521 barrier: make(chan bool),522 name: testName,523 parent: &root,524 level: root.level + 1,525 chatty: root.chatty,526 ctx: ctx,527 cancelCtx: cancelCtx,528 },529 tstate: tstate,530 fstate: fstate,531 }532 f.w = indenter{&f.common}533 f.setOutputWriter()534 if f.chatty != nil {535 f.chatty.Updatef(f.name, "=== RUN %s\n", f.name)536 }537 go fRunner(f, ft.Fn)538 <-f.signal539 if f.chatty != nil && f.chatty.json {540 f.chatty.Updatef(f.parent.name, "=== NAME %s\n", f.parent.name)541 }542 ok = ok && !f.Failed()543 ran = ran || f.ran544 }545 if !ran {546 // There were no tests to run on this iteration.547 // This won't change, so no reason to keep trying.548 break549 }550 }551 }552 553 return ran, ok554}555 556// runFuzzing runs the fuzz test matching the pattern for -fuzz. Only one such557// fuzz test must match. This will run the fuzzing engine to generate and558// mutate new inputs against the fuzz target.559//560// If fuzzing is disabled (-test.fuzz is not set), runFuzzing561// returns immediately.562func runFuzzing(deps testDeps, fuzzTests []InternalFuzzTarget) (ok bool) {563 if len(fuzzTests) == 0 || *matchFuzz == "" {564 return true565 }566 m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip)567 tstate := newTestState(1, m)568 tstate.isFuzzing = true569 fstate := &fuzzState{570 deps: deps,571 }572 root := common{w: os.Stdout}573 if *isFuzzWorker {574 root.w = io.Discard575 fstate.mode = fuzzWorker576 } else {577 fstate.mode = fuzzCoordinator578 }579 if Verbose() && !*isFuzzWorker {580 root.chatty = newChattyPrinter(root.w)581 }582 var fuzzTest *InternalFuzzTarget583 var testName string584 var matched []string585 for i := range fuzzTests {586 name, ok, _ := tstate.match.fullName(nil, fuzzTests[i].Name)587 if !ok {588 continue589 }590 matched = append(matched, name)591 fuzzTest = &fuzzTests[i]592 testName = name593 }594 if len(matched) == 0 {595 fmt.Fprintln(os.Stderr, "testing: warning: no fuzz tests to fuzz")596 return true597 }598 if len(matched) > 1 {599 fmt.Fprintf(os.Stderr, "testing: will not fuzz, -fuzz matches more than one fuzz test: %v\n", matched)600 return false601 }602 603 ctx, cancelCtx := context.WithCancel(context.Background())604 f := &F{605 common: common{606 signal: make(chan bool),607 barrier: nil, // T.Parallel has no effect when fuzzing.608 name: testName,609 parent: &root,610 level: root.level + 1,611 chatty: root.chatty,612 ctx: ctx,613 cancelCtx: cancelCtx,614 },615 fstate: fstate,616 tstate: tstate,617 }618 f.w = indenter{&f.common}619 f.setOutputWriter()620 if f.chatty != nil {621 f.chatty.Updatef(f.name, "=== RUN %s\n", f.name)622 }623 go fRunner(f, fuzzTest.Fn)624 <-f.signal625 if f.chatty != nil {626 f.chatty.Updatef(f.parent.name, "=== NAME %s\n", f.parent.name)627 }628 return !f.failed629}630 631// fRunner wraps a call to a fuzz test and ensures that cleanup functions are632// called and status flags are set. fRunner should be called in its own633// goroutine. To wait for its completion, receive from f.signal.634//635// fRunner is analogous to tRunner, which wraps subtests started with T.Run.636// Unit tests and fuzz tests work a little differently, so for now, these637// functions aren't consolidated. In particular, because there are no F.Run and638// F.Parallel methods, i.e., no fuzz sub-tests or parallel fuzz tests, a few639// simplifications are made. We also require that F.Fuzz, F.Skip, or F.Fail is640// called.641func fRunner(f *F, fn func(*F)) {642 // When this goroutine is done, either because runtime.Goexit was called, a643 // panic started, or fn returned normally, record the duration and send644 // t.signal, indicating the fuzz test is done.645 defer func() {646 // Detect whether the fuzz test panicked or called runtime.Goexit647 // without calling F.Fuzz, F.Fail, or F.Skip. If it did, panic (possibly648 // replacing a nil panic value). Nothing should recover after fRunner649 // unwinds, so this should crash the process and print stack.650 // Unfortunately, recovering here adds stack frames, but the location of651 // the original panic should still be652 // clear.653 f.checkRaces()654 if f.Failed() {655 numFailed.Add(1)656 }657 err := recover()658 if err == nil {659 f.mu.RLock()660 fuzzNotCalled := !f.fuzzCalled && !f.skipped && !f.failed661 if !f.finished && !f.skipped && !f.failed {662 err = errNilPanicOrGoexit663 }664 f.mu.RUnlock()665 if fuzzNotCalled && err == nil {666 f.Error("returned without calling F.Fuzz, F.Fail, or F.Skip")667 }668 }669 670 // Use a deferred call to ensure that we report that the test is671 // complete even if a cleanup function calls F.FailNow. See issue 41355.672 didPanic := false673 defer func() {674 if !didPanic {675 // Only report that the test is complete if it doesn't panic,676 // as otherwise the test binary can exit before the panic is677 // reported to the user. See issue 41479.678 f.signal <- true679 }680 }()681 682 // If we recovered a panic or inappropriate runtime.Goexit, fail the test,683 // flush the output log up to the root, then panic.684 doPanic := func(err any) {685 f.Fail()686 if r := f.runCleanup(recoverAndReturnPanic); r != nil {687 f.Logf("cleanup panicked with %v", r)688 }689 for root := &f.common; root.parent != nil; root = root.parent {690 root.mu.Lock()691 root.duration += highPrecisionTimeSince(root.start)692 d := root.duration693 root.mu.Unlock()694 root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d))695 }696 didPanic = true697 panic(err)698 }699 if err != nil {700 doPanic(err)701 }702 703 // No panic or inappropriate Goexit.704 f.duration += highPrecisionTimeSince(f.start)705 706 if len(f.sub) > 0 {707 // Unblock inputs that called T.Parallel while running the seed corpus.708 // This only affects fuzz tests run as normal tests.709 // While fuzzing, T.Parallel has no effect, so f.sub is empty, and this710 // branch is not taken. f.barrier is nil in that case.711 f.tstate.release()712 close(f.barrier)713 // Wait for the subtests to complete.714 for _, sub := range f.sub {715 <-sub.signal716 }717 cleanupStart := highPrecisionTimeNow()718 err := f.runCleanup(recoverAndReturnPanic)719 f.duration += highPrecisionTimeSince(cleanupStart)720 if err != nil {721 doPanic(err)722 }723 }724 725 // Report after all subtests have finished.726 f.report()727 f.done = true728 f.setRan()729 }()730 defer func() {731 if len(f.sub) == 0 {732 f.runCleanup(normalPanic)733 }734 }()735 736 f.start = highPrecisionTimeNow()737 f.resetRaces()738 fn(f)739 740 // Code beyond this point will not be executed when FailNow or SkipNow741 // is invoked.742 f.mu.Lock()743 f.finished = true744 f.mu.Unlock()745}746 