summaryrefslogtreecommitdiff
path: root/src/testing
diff options
context:
space:
mode:
Diffstat (limited to 'src/testing')
-rw-r--r--src/testing/benchmark.go18
-rw-r--r--src/testing/example.go11
-rw-r--r--src/testing/internal/testdeps/deps.go5
-rw-r--r--src/testing/run_example.go4
-rw-r--r--src/testing/run_example_js.go4
-rw-r--r--src/testing/sub_test.go46
-rw-r--r--src/testing/testing.go252
-rw-r--r--src/testing/testing_test.go32
8 files changed, 248 insertions, 124 deletions
diff --git a/src/testing/benchmark.go b/src/testing/benchmark.go
index 52766005bf..e9687bf26d 100644
--- a/src/testing/benchmark.go
+++ b/src/testing/benchmark.go
@@ -242,7 +242,7 @@ func (b *B) run1() bool {
if b.skipped {
tag = "SKIP"
}
- if b.chatty && (len(b.output) > 0 || b.finished) {
+ if b.chatty != nil && (len(b.output) > 0 || b.finished) {
b.trimOutput()
fmt.Fprintf(b.w, "--- %s: %s\n%s", tag, b.name, b.output)
}
@@ -523,10 +523,9 @@ func runBenchmarks(importPath string, matchString func(pat, str string) (bool, e
}
main := &B{
common: common{
- name: "Main",
- w: os.Stdout,
- chatty: *chatty,
- bench: true,
+ name: "Main",
+ w: os.Stdout,
+ bench: true,
},
importPath: importPath,
benchFunc: func(b *B) {
@@ -537,6 +536,9 @@ func runBenchmarks(importPath string, matchString func(pat, str string) (bool, e
benchTime: benchTime,
context: ctx,
}
+ if Verbose() {
+ main.chatty = newChattyPrinter(main.w)
+ }
main.runN(1)
return !main.failed
}
@@ -549,7 +551,7 @@ func (ctx *benchContext) processBench(b *B) {
benchName := benchmarkName(b.name, procs)
// If it's chatty, we've already printed this information.
- if !b.chatty {
+ if b.chatty == nil {
fmt.Fprintf(b.w, "%-*s\t", ctx.maxLen, benchName)
}
// Recompute the running time for all but the first iteration.
@@ -576,7 +578,7 @@ func (ctx *benchContext) processBench(b *B) {
continue
}
results := r.String()
- if b.chatty {
+ if b.chatty != nil {
fmt.Fprintf(b.w, "%-*s\t", ctx.maxLen, benchName)
}
if *benchmarkMemory || b.showAllocResult {
@@ -639,7 +641,7 @@ func (b *B) Run(name string, f func(b *B)) bool {
atomic.StoreInt32(&sub.hasSub, 1)
}
- if b.chatty {
+ if b.chatty != nil {
labelsOnce.Do(func() {
fmt.Printf("goos: %s\n", runtime.GOOS)
fmt.Printf("goarch: %s\n", runtime.GOARCH)
diff --git a/src/testing/example.go b/src/testing/example.go
index adc91d5faf..0217c5d242 100644
--- a/src/testing/example.go
+++ b/src/testing/example.go
@@ -62,9 +62,10 @@ func sortLines(output string) string {
// If stdout doesn't match the expected output or if recovered is non-nil, it'll print the cause of failure to stdout.
// If the test is chatty/verbose, it'll print a success message to stdout.
// If recovered is non-nil, it'll panic with that value.
-func (eg *InternalExample) processRunResult(stdout string, timeSpent time.Duration, recovered interface{}) (passed bool) {
+// If the test panicked with nil, or invoked runtime.Goexit, it'll be
+// made to fail and panic with errNilPanicOrGoexit
+func (eg *InternalExample) processRunResult(stdout string, timeSpent time.Duration, finished bool, recovered interface{}) (passed bool) {
passed = true
-
dstr := fmtDuration(timeSpent)
var fail string
got := strings.TrimSpace(stdout)
@@ -78,16 +79,20 @@ func (eg *InternalExample) processRunResult(stdout string, timeSpent time.Durati
fail = fmt.Sprintf("got:\n%s\nwant:\n%s\n", got, want)
}
}
- if fail != "" || recovered != nil {
+ if fail != "" || !finished || recovered != nil {
fmt.Printf("--- FAIL: %s (%s)\n%s", eg.Name, dstr, fail)
passed = false
} else if *chatty {
fmt.Printf("--- PASS: %s (%s)\n", eg.Name, dstr)
}
+
if recovered != nil {
// Propagate the previously recovered result, by panicking.
panic(recovered)
}
+ if !finished && recovered == nil {
+ panic(errNilPanicOrGoexit)
+ }
return
}
diff --git a/src/testing/internal/testdeps/deps.go b/src/testing/internal/testdeps/deps.go
index af08dd768a..3608d33294 100644
--- a/src/testing/internal/testdeps/deps.go
+++ b/src/testing/internal/testdeps/deps.go
@@ -121,3 +121,8 @@ func (TestDeps) StopTestLog() error {
log.w = nil
return err
}
+
+// SetPanicOnExit0 tells the os package whether to panic on os.Exit(0).
+func (TestDeps) SetPanicOnExit0(v bool) {
+ testlog.SetPanicOnExit0(v)
+}
diff --git a/src/testing/run_example.go b/src/testing/run_example.go
index 10bde49e5b..4dc83f7d32 100644
--- a/src/testing/run_example.go
+++ b/src/testing/run_example.go
@@ -43,6 +43,7 @@ func runExample(eg InternalExample) (ok bool) {
outC <- buf.String()
}()
+ finished := false
start := time.Now()
// Clean up in a deferred call so we can recover if the example panics.
@@ -55,10 +56,11 @@ func runExample(eg InternalExample) (ok bool) {
out := <-outC
err := recover()
- ok = eg.processRunResult(out, timeSpent, err)
+ ok = eg.processRunResult(out, timeSpent, finished, err)
}()
// Run example.
eg.F()
+ finished = true
return
}
diff --git a/src/testing/run_example_js.go b/src/testing/run_example_js.go
index 472e0c57fa..1d4164b61f 100644
--- a/src/testing/run_example_js.go
+++ b/src/testing/run_example_js.go
@@ -26,6 +26,7 @@ func runExample(eg InternalExample) (ok bool) {
stdout := os.Stdout
f := createTempFile(eg.Name)
os.Stdout = f
+ finished := false
start := time.Now()
// Clean up in a deferred call so we can recover if the example panics.
@@ -50,11 +51,12 @@ func runExample(eg InternalExample) (ok bool) {
}
err := recover()
- ok = eg.processRunResult(out, timeSpent, err)
+ ok = eg.processRunResult(out, timeSpent, finished, err)
}()
// Run example.
eg.F()
+ finished = true
return
}
diff --git a/src/testing/sub_test.go b/src/testing/sub_test.go
index 8eb0084b1c..5b226f85ad 100644
--- a/src/testing/sub_test.go
+++ b/src/testing/sub_test.go
@@ -483,10 +483,12 @@ func TestTRun(t *T) {
signal: make(chan bool),
name: "Test",
w: buf,
- chatty: tc.chatty,
},
context: ctx,
}
+ if tc.chatty {
+ root.chatty = newChattyPrinter(root.w)
+ }
ok := root.Run(tc.desc, tc.f)
ctx.release()
@@ -665,11 +667,13 @@ func TestBRun(t *T) {
signal: make(chan bool),
name: "root",
w: buf,
- chatty: tc.chatty,
},
benchFunc: func(b *B) { ok = b.Run("test", tc.f) }, // Use Run to catch failure.
benchTime: benchTimeFlag{d: 1 * time.Microsecond},
}
+ if tc.chatty {
+ root.chatty = newChattyPrinter(root.w)
+ }
root.runN(1)
if ok != !tc.failed {
t.Errorf("%s:ok: got %v; want %v", tc.desc, ok, !tc.failed)
@@ -741,9 +745,13 @@ func TestParallelSub(t *T) {
}
}
-type funcWriter func([]byte) (int, error)
+type funcWriter struct {
+ write func([]byte) (int, error)
+}
-func (fw funcWriter) Write(b []byte) (int, error) { return fw(b) }
+func (fw *funcWriter) Write(b []byte) (int, error) {
+ return fw.write(b)
+}
func TestRacyOutput(t *T) {
var runs int32 // The number of running Writes
@@ -761,9 +769,10 @@ func TestRacyOutput(t *T) {
var wg sync.WaitGroup
root := &T{
- common: common{w: funcWriter(raceDetector), chatty: true},
+ common: common{w: &funcWriter{raceDetector}},
context: newTestContext(1, newMatcher(regexp.MatchString, "", "")),
}
+ root.chatty = newChattyPrinter(root.w)
root.Run("", func(t *T) {
for i := 0; i < 100; i++ {
wg.Add(1)
@@ -928,3 +937,30 @@ func TestCleanupParallelSubtests(t *T) {
t.Errorf("unexpected cleanup count; got %d want 1", ranCleanup)
}
}
+
+func TestNestedCleanup(t *T) {
+ ranCleanup := 0
+ t.Run("test", func(t *T) {
+ t.Cleanup(func() {
+ if ranCleanup != 2 {
+ t.Errorf("unexpected cleanup count in first cleanup: got %d want 2", ranCleanup)
+ }
+ ranCleanup++
+ })
+ t.Cleanup(func() {
+ if ranCleanup != 0 {
+ t.Errorf("unexpected cleanup count in second cleanup: got %d want 0", ranCleanup)
+ }
+ ranCleanup++
+ t.Cleanup(func() {
+ if ranCleanup != 1 {
+ t.Errorf("unexpected cleanup count in nested cleanup: got %d want 1", ranCleanup)
+ }
+ ranCleanup++
+ })
+ })
+ })
+ if ranCleanup != 3 {
+ t.Errorf("unexpected cleanup count: got %d want 3", ranCleanup)
+ }
+}
diff --git a/src/testing/testing.go b/src/testing/testing.go
index 6fc8c4fa9f..66f296234a 100644
--- a/src/testing/testing.go
+++ b/src/testing/testing.go
@@ -294,6 +294,7 @@ func Init() {
blockProfileRate = flag.Int("test.blockprofilerate", 1, "set blocking profile `rate` (see runtime.SetBlockProfileRate)")
mutexProfile = flag.String("test.mutexprofile", "", "write a mutex contention profile to the named file after execution")
mutexProfileFraction = flag.Int("test.mutexprofilefraction", 1, "if >= 0, calls runtime.SetMutexProfileFraction()")
+ panicOnExit0 = flag.Bool("test.paniconexit0", false, "panic on call to os.Exit(0)")
traceFile = flag.String("test.trace", "", "write an execution trace to `file`")
timeout = flag.Duration("test.timeout", 0, "panic test binary after duration `d` (default 0, timeout disabled)")
cpuListStr = flag.String("test.cpu", "", "comma-separated `list` of cpu counts to run each test with")
@@ -320,12 +321,12 @@ var (
blockProfileRate *int
mutexProfile *string
mutexProfileFraction *int
+ panicOnExit0 *bool
traceFile *string
timeout *time.Duration
cpuListStr *string
parallel *int
testlog *string
- printer *testPrinter
haveExamples bool // are there examples?
@@ -335,55 +336,45 @@ var (
numFailed uint32 // number of test failures
)
-type testPrinter struct {
- chatty bool
-
+type chattyPrinter struct {
+ w io.Writer
lastNameMu sync.Mutex // guards lastName
lastName string // last printed test name in chatty mode
}
-func newTestPrinter(chatty bool) *testPrinter {
- return &testPrinter{
- chatty: chatty,
- }
+func newChattyPrinter(w io.Writer) *chattyPrinter {
+ return &chattyPrinter{w: w}
}
-func (p *testPrinter) Print(testName, out string) {
- p.Fprint(os.Stdout, testName, out)
+// Updatef prints a message about the status of the named test to w.
+//
+// The formatted message must include the test name itself.
+func (p *chattyPrinter) Updatef(testName, format string, args ...interface{}) {
+ p.lastNameMu.Lock()
+ defer p.lastNameMu.Unlock()
+
+ // Since the message already implies an association with a specific new test,
+ // we don't need to check what the old test name was or log an extra CONT line
+ // for it. (We're updating it anyway, and the current message already includes
+ // the test name.)
+ p.lastName = testName
+ fmt.Fprintf(p.w, format, args...)
}
-func (p *testPrinter) Fprint(w io.Writer, testName, out string) {
+// Printf prints a message, generated by the named test, that does not
+// necessarily mention that tests's name itself.
+func (p *chattyPrinter) Printf(testName, format string, args ...interface{}) {
p.lastNameMu.Lock()
defer p.lastNameMu.Unlock()
- if !p.chatty ||
- strings.HasPrefix(out, "--- PASS: ") ||
- strings.HasPrefix(out, "--- FAIL: ") ||
- strings.HasPrefix(out, "--- SKIP: ") ||
- strings.HasPrefix(out, "=== RUN ") ||
- strings.HasPrefix(out, "=== CONT ") ||
- strings.HasPrefix(out, "=== PAUSE ") {
- // If we're buffering test output (!p.chatty), we don't really care which
- // test is emitting which line so long as they are serialized.
- //
- // If the message already implies an association with a specific new test,
- // we don't need to check what the old test name was or log an extra CONT
- // line for it. (We're updating it anyway, and the current message already
- // includes the test name.)
- p.lastName = testName
- fmt.Fprint(w, out)
- return
- }
-
if p.lastName == "" {
p.lastName = testName
} else if p.lastName != testName {
- // Always printed as-is, with 0 decoration or indentation. So, we skip
- // printing to w.
- fmt.Printf("=== CONT %s\n", testName)
+ fmt.Fprintf(p.w, "=== CONT %s\n", testName)
p.lastName = testName
}
- fmt.Fprint(w, out)
+
+ fmt.Fprintf(p.w, format, args...)
}
// The maximum number of stack frames to go through when skipping helper functions for
@@ -401,16 +392,16 @@ type common struct {
skipped bool // Test of benchmark has been skipped.
done bool // Test is finished and all subtests have completed.
helpers map[string]struct{} // functions to be skipped when writing file/line info
- cleanup func() // optional function to be called at the end of the test
+ cleanups []func() // optional functions to be called at the end of the test
cleanupName string // Name of the cleanup function.
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
- chatty bool // A copy of the chatty flag.
- bench bool // Whether the current test is a benchmark.
- finished bool // Test function has completed.
- hasSub int32 // Written atomically.
- raceErrors int // Number of races detected during test.
- runner string // Function name of tRunner running the test.
+ chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
+ bench bool // Whether the current test is a benchmark.
+ finished bool // Test function has completed.
+ hasSub int32 // Written atomically.
+ raceErrors int // Number of races detected during test.
+ runner string // Function name of tRunner running the test.
parent *common
level int // Nesting depth of test or benchmark.
@@ -422,10 +413,10 @@ type common struct {
signal chan bool // To signal a test is done.
sub []*T // Queue of subtests to be run in parallel.
- tempDirOnce sync.Once
- tempDir string
- tempDirErr error
- tempDirSeq int32
+ tempDirMu sync.Mutex
+ tempDir string
+ tempDirErr error
+ tempDirSeq int32
}
// Short reports whether the -test.short flag is set.
@@ -572,12 +563,31 @@ func (c *common) flushToParent(testName, format string, args ...interface{}) {
p.mu.Lock()
defer p.mu.Unlock()
- printer.Fprint(p.w, testName, fmt.Sprintf(format, args...))
-
c.mu.Lock()
defer c.mu.Unlock()
- io.Copy(p.w, bytes.NewReader(c.output))
- c.output = c.output[:0]
+
+ if len(c.output) > 0 {
+ format += "%s"
+ args = append(args[:len(args):len(args)], c.output)
+ c.output = c.output[:0] // but why?
+ }
+
+ if c.chatty != nil && p.w == c.chatty.w {
+ // We're flushing to the actual output, so track that this output is
+ // associated with a specific test (and, specifically, that the next output
+ // is *not* associated with that test).
+ //
+ // Moreover, if c.output is non-empty it is important that this write be
+ // atomic with respect to the output of other tests, so that we don't end up
+ // with confusing '=== CONT' lines in the middle of our '--- PASS' block.
+ // Neither humans nor cmd/test2json can parse those easily.
+ // (See https://golang.org/issue/40771.)
+ c.chatty.Updatef(testName, format, args...)
+ } else {
+ // We're flushing to the output buffer of the parent test, which will
+ // itself follow a test-name header when it is finally flushed to stdout.
+ fmt.Fprintf(p.w, format, args...)
+ }
}
type indenter struct {
@@ -746,13 +756,13 @@ func (c *common) logDepth(s string, depth int) {
}
panic("Log in goroutine after " + c.name + " has completed")
} else {
- if c.chatty {
+ if c.chatty != nil {
if c.bench {
// Benchmarks don't print === CONT, so we should skip the test
// printer and just print straight to stdout.
fmt.Print(c.decorate(s, depth+1))
} else {
- printer.Print(c.name, c.decorate(s, depth+1))
+ c.chatty.Printf(c.name, "%s", c.decorate(s, depth+1))
}
return
@@ -853,24 +863,31 @@ func (c *common) Helper() {
// subtests complete. Cleanup functions will be called in last added,
// first called order.
func (c *common) Cleanup(f func()) {
- c.mu.Lock()
- defer c.mu.Unlock()
- oldCleanup := c.cleanup
- oldCleanupPc := c.cleanupPc
- c.cleanup = func() {
- if oldCleanup != nil {
- defer func() {
- c.cleanupPc = oldCleanupPc
- oldCleanup()
- }()
- }
- c.cleanupName = callerName(0)
- f()
- }
var pc [maxStackLen]uintptr
// Skip two extra frames to account for this function and runtime.Callers itself.
n := runtime.Callers(2, pc[:])
- c.cleanupPc = pc[:n]
+ cleanupPc := pc[:n]
+
+ fn := func() {
+ defer func() {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.cleanupName = ""
+ c.cleanupPc = nil
+ }()
+
+ name := callerName(0)
+ c.mu.Lock()
+ c.cleanupName = name
+ c.cleanupPc = cleanupPc
+ c.mu.Unlock()
+
+ f()
+ }
+
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.cleanups = append(c.cleanups, fn)
}
var tempDirReplacer struct {
@@ -886,7 +903,19 @@ var tempDirReplacer struct {
func (c *common) TempDir() string {
// Use a single parent directory for all the temporary directories
// created by a test, each numbered sequentially.
- c.tempDirOnce.Do(func() {
+ c.tempDirMu.Lock()
+ var nonExistent bool
+ if c.tempDir == "" { // Usually the case with js/wasm
+ nonExistent = true
+ } else {
+ _, err := os.Stat(c.tempDir)
+ nonExistent = os.IsNotExist(err)
+ if err != nil && !nonExistent {
+ c.Fatalf("TempDir: %v", err)
+ }
+ }
+
+ if nonExistent {
c.Helper()
// ioutil.TempDir doesn't like path separators in its pattern,
@@ -904,7 +933,9 @@ func (c *common) TempDir() string {
}
})
}
- })
+ }
+ c.tempDirMu.Unlock()
+
if c.tempDirErr != nil {
c.Fatalf("TempDir: %v", c.tempDirErr)
}
@@ -928,22 +959,37 @@ const (
// If catchPanic is true, this will catch panics, and return the recovered
// value if any.
func (c *common) runCleanup(ph panicHandling) (panicVal interface{}) {
- c.mu.Lock()
- cleanup := c.cleanup
- c.cleanup = nil
- c.mu.Unlock()
- if cleanup == nil {
- return nil
- }
-
if ph == recoverAndReturnPanic {
defer func() {
panicVal = recover()
}()
}
- cleanup()
- return nil
+ // Make sure that if a cleanup function panics,
+ // we still run the remaining cleanup functions.
+ defer func() {
+ c.mu.Lock()
+ recur := len(c.cleanups) > 0
+ c.mu.Unlock()
+ if recur {
+ c.runCleanup(normalPanic)
+ }
+ }()
+
+ for {
+ var cleanup func()
+ c.mu.Lock()
+ if len(c.cleanups) > 0 {
+ last := len(c.cleanups) - 1
+ cleanup = c.cleanups[last]
+ c.cleanups = c.cleanups[:last]
+ }
+ c.mu.Unlock()
+ if cleanup == nil {
+ return nil
+ }
+ cleanup()
+ }
}
// callerName gives the function name (qualified with a package path)
@@ -979,34 +1025,22 @@ func (t *T) Parallel() {
t.parent.sub = append(t.parent.sub, t)
t.raceErrors += race.Errors()
- if t.chatty {
- // Print directly to root's io.Writer so there is no delay.
- root := t.parent
- for ; root.parent != nil; root = root.parent {
- }
- root.mu.Lock()
+ if t.chatty != nil {
// Unfortunately, even though PAUSE indicates that the named test is *no
// longer* running, cmd/test2json interprets it as changing the active test
// for the purpose of log parsing. We could fix cmd/test2json, but that
// won't fix existing deployments of third-party tools that already shell
// out to older builds of cmd/test2json — so merely fixing cmd/test2json
// isn't enough for now.
- printer.Fprint(root.w, t.name, fmt.Sprintf("=== PAUSE %s\n", t.name))
- root.mu.Unlock()
+ t.chatty.Updatef(t.name, "=== PAUSE %s\n", t.name)
}
t.signal <- true // Release calling test.
<-t.parent.barrier // Wait for the parent test to complete.
t.context.waitParallel()
- if t.chatty {
- // Print directly to root's io.Writer so there is no delay.
- root := t.parent
- for ; root.parent != nil; root = root.parent {
- }
- root.mu.Lock()
- printer.Fprint(root.w, t.name, fmt.Sprintf("=== CONT %s\n", t.name))
- root.mu.Unlock()
+ if t.chatty != nil {
+ t.chatty.Updatef(t.name, "=== CONT %s\n", t.name)
}
t.start = time.Now()
@@ -1157,14 +1191,8 @@ func (t *T) Run(name string, f func(t *T)) bool {
}
t.w = indenter{&t.common}
- if t.chatty {
- // Print directly to root's io.Writer so there is no delay.
- root := t.parent
- for ; root.parent != nil; root = root.parent {
- }
- root.mu.Lock()
- printer.Fprint(root.w, t.name, fmt.Sprintf("=== RUN %s\n", t.name))
- root.mu.Unlock()
+ if t.chatty != nil {
+ t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)
}
// Instead of reducing the running count of this test before calling the
// tRunner and increasing it afterwards, we rely on tRunner keeping the
@@ -1257,6 +1285,7 @@ func (f matchStringOnly) WriteProfileTo(string, io.Writer, int) error { return e
func (f matchStringOnly) ImportPath() string { return "" }
func (f matchStringOnly) StartTestLog(io.Writer) {}
func (f matchStringOnly) StopTestLog() error { return errMain }
+func (f matchStringOnly) SetPanicOnExit0(bool) {}
// Main is an internal function, part of the implementation of the "go test" command.
// It was exported because it is cross-package and predates "internal" packages.
@@ -1292,6 +1321,7 @@ type M struct {
type testDeps interface {
ImportPath() string
MatchString(pat, str string) (bool, error)
+ SetPanicOnExit0(bool)
StartCPUProfile(io.Writer) error
StopCPUProfile()
StartTestLog(io.Writer)
@@ -1329,8 +1359,6 @@ func (m *M) Run() (code int) {
flag.Parse()
}
- printer = newTestPrinter(Verbose())
-
if *parallel < 1 {
fmt.Fprintln(os.Stderr, "testing: -parallel can only be given a positive integer")
flag.Usage()
@@ -1375,7 +1403,7 @@ func (t *T) report() {
format := "--- %s: %s (%s)\n"
if t.Failed() {
t.flushToParent(t.name, format, "FAIL", t.name, dstr)
- } else if t.chatty {
+ } else if t.chatty != nil {
if t.Skipped() {
t.flushToParent(t.name, format, "SKIP", t.name, dstr)
} else {
@@ -1436,10 +1464,12 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT
signal: make(chan bool),
barrier: make(chan bool),
w: os.Stdout,
- chatty: *chatty,
},
context: ctx,
}
+ if Verbose() {
+ t.chatty = newChattyPrinter(t.w)
+ }
tRunner(t, func(t *T) {
for _, test := range tests {
t.Run(test.Name, test.F)
@@ -1517,6 +1547,9 @@ func (m *M) before() {
m.deps.StartTestLog(f)
testlogFile = f
}
+ if *panicOnExit0 {
+ m.deps.SetPanicOnExit0(true)
+ }
}
// after runs after all testing.
@@ -1524,6 +1557,13 @@ func (m *M) after() {
m.afterOnce.Do(func() {
m.writeProfiles()
})
+
+ // Restore PanicOnExit0 after every run, because we set it to true before
+ // every run. Otherwise, if m.Run is called multiple times the behavior of
+ // os.Exit(0) will not be restored after the second run.
+ if *panicOnExit0 {
+ m.deps.SetPanicOnExit0(false)
+ }
}
func (m *M) writeProfiles() {
diff --git a/src/testing/testing_test.go b/src/testing/testing_test.go
index dbef7066e0..d665a334e4 100644
--- a/src/testing/testing_test.go
+++ b/src/testing/testing_test.go
@@ -19,6 +19,38 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
+func TestTempDirInCleanup(t *testing.T) {
+ var dir string
+
+ t.Run("test", func(t *testing.T) {
+ t.Cleanup(func() {
+ dir = t.TempDir()
+ })
+ _ = t.TempDir()
+ })
+
+ fi, err := os.Stat(dir)
+ if fi != nil {
+ t.Fatalf("Directory %q from user Cleanup still exists", dir)
+ }
+ if !os.IsNotExist(err) {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+}
+
+func TestTempDirInBenchmark(t *testing.T) {
+ testing.Benchmark(func(b *testing.B) {
+ if !b.Run("test", func(b *testing.B) {
+ // Add a loop so that the test won't fail. See issue 38677.
+ for i := 0; i < b.N; i++ {
+ _ = b.TempDir()
+ }
+ }) {
+ t.Fatal("Sub test failure in a benchmark")
+ }
+ })
+}
+
func TestTempDir(t *testing.T) {
testTempDir(t)
t.Run("InSubtest", testTempDir)