Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
fuzz.go746 linesDownload Raw Back to testing
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 
codekingpro/portable-devtools · Team Ai