Skip to content

Commit e9fc27f

Browse files
committed
testing: implement testing.TempDir
1 parent 89f0700 commit e9fc27f

File tree

2 files changed

+179
-0
lines changed

2 files changed

+179
-0
lines changed

src/testing/testing.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"io"
1616
"os"
1717
"strings"
18+
"unicode"
19+
"unicode/utf8"
1820
)
1921

2022
// Testing flags.
@@ -54,6 +56,10 @@ type common struct {
5456
parent *common
5557
level int // Nesting depth of test or benchmark.
5658
name string // Name of test or benchmark.
59+
60+
tempDir string
61+
tempDirErr error
62+
tempDirSeq int32
5763
}
5864

5965
// TB is the interface common to T and B.
@@ -216,6 +222,70 @@ func (c *common) Cleanup(f func()) {
216222
c.cleanups = append(c.cleanups, f)
217223
}
218224

225+
// TempDir returns a temporary directory for the test to use.
226+
// The directory is automatically removed by Cleanup when the test and
227+
// all its subtests complete.
228+
// Each subsequent call to t.TempDir returns a unique directory;
229+
// if the directory creation fails, TempDir terminates the test by calling Fatal.
230+
func (c *common) TempDir() string {
231+
// Use a single parent directory for all the temporary directories
232+
// created by a test, each numbered sequentially.
233+
var nonExistent bool
234+
if c.tempDir == "" { // Usually the case with js/wasm
235+
nonExistent = true
236+
} else {
237+
_, err := os.Stat(c.tempDir)
238+
nonExistent = os.IsNotExist(err)
239+
if err != nil && !nonExistent {
240+
c.Fatalf("TempDir: %v", err)
241+
}
242+
}
243+
244+
if nonExistent {
245+
c.Helper()
246+
247+
// Drop unusual characters (such as path separators or
248+
// characters interacting with globs) from the directory name to
249+
// avoid surprising os.MkdirTemp behavior.
250+
mapper := func(r rune) rune {
251+
if r < utf8.RuneSelf {
252+
const allowed = "!#$%&()+,-.=@^_{}~ "
253+
if '0' <= r && r <= '9' ||
254+
'a' <= r && r <= 'z' ||
255+
'A' <= r && r <= 'Z' {
256+
return r
257+
}
258+
if strings.ContainsRune(allowed, r) {
259+
return r
260+
}
261+
} else if unicode.IsLetter(r) || unicode.IsNumber(r) {
262+
return r
263+
}
264+
return -1
265+
}
266+
pattern := strings.Map(mapper, c.Name())
267+
c.tempDir, c.tempDirErr = os.MkdirTemp("", pattern)
268+
if c.tempDirErr == nil {
269+
c.Cleanup(func() {
270+
if err := os.RemoveAll(c.tempDir); err != nil {
271+
c.Errorf("TempDir RemoveAll cleanup: %v", err)
272+
}
273+
})
274+
}
275+
}
276+
277+
if c.tempDirErr != nil {
278+
c.Fatalf("TempDir: %v", c.tempDirErr)
279+
}
280+
seq := c.tempDirSeq
281+
c.tempDirSeq++
282+
dir := fmt.Sprintf("%s%c%03d", c.tempDir, os.PathSeparator, seq)
283+
if err := os.Mkdir(dir, 0777); err != nil {
284+
c.Fatalf("TempDir: %v", err)
285+
}
286+
return dir
287+
}
288+
219289
// runCleanup is called at the end of the test.
220290
func (c *common) runCleanup() {
221291
for {

src/testing/testing_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2014 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package testing_test
6+
7+
import (
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
)
12+
13+
func TestTempDirInCleanup(t *testing.T) {
14+
var dir string
15+
16+
t.Run("test", func(t *testing.T) {
17+
t.Cleanup(func() {
18+
dir = t.TempDir()
19+
})
20+
_ = t.TempDir()
21+
})
22+
23+
fi, err := os.Stat(dir)
24+
if fi != nil {
25+
t.Fatalf("Directory %q from user Cleanup still exists", dir)
26+
}
27+
if !os.IsNotExist(err) {
28+
t.Fatalf("Unexpected error: %v", err)
29+
}
30+
}
31+
32+
func TestTempDirInBenchmark(t *testing.T) {
33+
testing.Benchmark(func(b *testing.B) {
34+
if !b.Run("test", func(b *testing.B) {
35+
// Add a loop so that the test won't fail. See issue 38677.
36+
for i := 0; i < b.N; i++ {
37+
_ = b.TempDir()
38+
}
39+
}) {
40+
t.Fatal("Sub test failure in a benchmark")
41+
}
42+
})
43+
}
44+
45+
func TestTempDir(t *testing.T) {
46+
testTempDir(t)
47+
t.Run("InSubtest", testTempDir)
48+
t.Run("test/subtest", testTempDir)
49+
t.Run("test\\subtest", testTempDir)
50+
t.Run("test:subtest", testTempDir)
51+
t.Run("test/..", testTempDir)
52+
t.Run("../test", testTempDir)
53+
t.Run("test[]", testTempDir)
54+
t.Run("test*", testTempDir)
55+
t.Run("äöüéè", testTempDir)
56+
}
57+
58+
func testTempDir(t *testing.T) {
59+
dirCh := make(chan string, 1)
60+
t.Cleanup(func() {
61+
// Verify directory has been removed.
62+
select {
63+
case dir := <-dirCh:
64+
fi, err := os.Stat(dir)
65+
if os.IsNotExist(err) {
66+
// All good
67+
return
68+
}
69+
if err != nil {
70+
t.Fatal(err)
71+
}
72+
t.Errorf("directory %q still exists: %v, isDir=%v", dir, fi, fi.IsDir())
73+
default:
74+
if !t.Failed() {
75+
t.Fatal("never received dir channel")
76+
}
77+
}
78+
})
79+
80+
dir := t.TempDir()
81+
if dir == "" {
82+
t.Fatal("expected dir")
83+
}
84+
dir2 := t.TempDir()
85+
if dir == dir2 {
86+
t.Fatal("subsequent calls to TempDir returned the same directory")
87+
}
88+
if filepath.Dir(dir) != filepath.Dir(dir2) {
89+
t.Fatalf("calls to TempDir do not share a parent; got %q, %q", dir, dir2)
90+
}
91+
dirCh <- dir
92+
fi, err := os.Stat(dir)
93+
if err != nil {
94+
t.Fatal(err)
95+
}
96+
if !fi.IsDir() {
97+
t.Errorf("dir %q is not a dir", dir)
98+
}
99+
100+
glob := filepath.Join(dir, "*.txt")
101+
if _, err := filepath.Glob(glob); err != nil {
102+
t.Error(err)
103+
}
104+
105+
err = os.Remove(dir)
106+
if err != nil {
107+
t.Errorf("unexpected files in TempDir")
108+
}
109+
}

0 commit comments

Comments
 (0)