Skip to content

Commit d032b2b

Browse files
author
Jay Conrod
committed
testing: don't create unique subtest names while fuzzing
T.Run uses a map[string]int64 to keep track of subtest names that may be returned through T.Name. T.Name can't return duplicate names for subtests started with T.Run. If a fuzz target calls T.Run, this map takes a large amount of memory, since there are a very large number of subtests that would otherwise have duplicate names, and the map stores one entry per subtest. The unique suffixes are not useful (and may be confusing) since the full sequence of tests cannot be re-run deterministically. This change deletes all entries in the map before each call to the function being fuzzed. There is a slight change in the contract of T.Name while fuzzing. This change was discussed in CL 351452. Fixes golang#44517 Change-Id: I3093a2e5568099ce54aca1006fac84a6fd2c3ddf Reviewed-on: https://go-review.googlesource.com/c/go/+/354430 Trust: Jay Conrod <jayconrod@google.com> Trust: Katie Hockman <katie@golang.org> Trust: Bryan C. Mills <bcmills@google.com> Run-TryBot: Jay Conrod <jayconrod@google.com> Run-TryBot: Katie Hockman <katie@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Katie Hockman <katie@golang.org>
1 parent 4186db6 commit d032b2b

File tree

3 files changed

+26
-14
lines changed

3 files changed

+26
-14
lines changed

src/testing/fuzz.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ func (f *F) Fuzz(ff interface{}) {
380380
if e.Path != "" {
381381
testName = fmt.Sprintf("%s/%s", testName, filepath.Base(e.Path))
382382
}
383+
if f.testContext.isFuzzing {
384+
// Don't preserve subtest names while fuzzing. If fn calls T.Run,
385+
// there will be a very large number of subtests with duplicate names,
386+
// which will use a large amount of memory. The subtest names aren't
387+
// useful since there's no way to re-run them deterministically.
388+
f.testContext.match.clearSubNames()
389+
}
383390

384391
// Record the stack trace at the point of this call so that if the subtest
385392
// function - which runs in a separate stack - is marked as a helper, we can
@@ -395,7 +402,6 @@ func (f *F) Fuzz(ff interface{}) {
395402
level: f.level + 1,
396403
creator: pc[:n],
397404
chatty: f.chatty,
398-
fuzzing: true,
399405
},
400406
context: f.testContext,
401407
}
@@ -615,6 +621,7 @@ func runFuzzing(deps testDeps, fuzzTargets []InternalFuzzTarget) (ok bool) {
615621
}
616622
m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz")
617623
tctx := newTestContext(1, m)
624+
tctx.isFuzzing = true
618625
fctx := &fuzzContext{
619626
deps: deps,
620627
}

src/testing/match.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,17 @@ func (m *matcher) fullName(c *common, subname string) (name string, ok, partial
8282
return name, ok, partial
8383
}
8484

85+
// clearSubNames clears the matcher's internal state, potentially freeing
86+
// memory. After this is called, T.Name may return the same strings as it did
87+
// for earlier subtests.
88+
func (m *matcher) clearSubNames() {
89+
m.mu.Lock()
90+
defer m.mu.Unlock()
91+
for key := range m.subNames {
92+
delete(m.subNames, key)
93+
}
94+
}
95+
8596
func (m simpleMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) {
8697
for i, s := range name {
8798
if i >= len(m) {

src/testing/testing.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,6 @@ type common struct {
495495

496496
chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
497497
bench bool // Whether the current test is a benchmark.
498-
fuzzing bool // Whether the current test is a fuzzing target.
499498
hasSub int32 // Written atomically.
500499
raceErrors int // Number of races detected during test.
501500
runner string // Function name of tRunner running the test.
@@ -697,17 +696,6 @@ func (c *common) flushToParent(testName, format string, args ...interface{}) {
697696
}
698697
}
699698

700-
// isFuzzing returns whether the current context, or any of the parent contexts,
701-
// are a fuzzing target
702-
func (c *common) isFuzzing() bool {
703-
for com := c; com != nil; com = com.parent {
704-
if com.fuzzing {
705-
return true
706-
}
707-
}
708-
return false
709-
}
710-
711699
type indenter struct {
712700
c *common
713701
}
@@ -1291,7 +1279,7 @@ func tRunner(t *T, fn func(t *T)) {
12911279
}
12921280
}
12931281

1294-
if err != nil && t.isFuzzing() {
1282+
if err != nil && t.context.isFuzzing {
12951283
prefix := "panic: "
12961284
if err == errNilPanicOrGoexit {
12971285
prefix = ""
@@ -1457,6 +1445,12 @@ type testContext struct {
14571445
match *matcher
14581446
deadline time.Time
14591447

1448+
// isFuzzing is true in the context used when generating random inputs
1449+
// for fuzz targets. isFuzzing is false when running normal tests and
1450+
// when running fuzz tests as unit tests (without -fuzz or when -fuzz
1451+
// does not match).
1452+
isFuzzing bool
1453+
14601454
mu sync.Mutex
14611455

14621456
// Channel used to signal tests that are ready to be run in parallel.

0 commit comments

Comments
 (0)