Skip to content

Commit 203dedc

Browse files
committed
testing: implement testing.TempDir
TODO: enable test on windows once os.Readdir and os.RemoveAll are implemented there
1 parent 83739be commit 203dedc

File tree

2 files changed

+173
-0
lines changed

2 files changed

+173
-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
"os"
1616
"strings"
1717
"time"
18+
"unicode"
19+
"unicode/utf8"
1820
)
1921

2022
// Testing flags.
@@ -58,6 +60,10 @@ type common struct {
5860
name string // Name of test or benchmark.
5961
start time.Time // Time test or benchmark started
6062
duration time.Duration
63+
64+
tempDir string
65+
tempDirErr error
66+
tempDirSeq int32
6167
}
6268

6369
// Short reports whether the -test.short flag is set.
@@ -258,6 +264,70 @@ func (c *common) Cleanup(f func()) {
258264
c.cleanups = append(c.cleanups, f)
259265
}
260266

267+
// TempDir returns a temporary directory for the test to use.
268+
// The directory is automatically removed by Cleanup when the test and
269+
// all its subtests complete.
270+
// Each subsequent call to t.TempDir returns a unique directory;
271+
// if the directory creation fails, TempDir terminates the test by calling Fatal.
272+
func (c *common) TempDir() string {
273+
// Use a single parent directory for all the temporary directories
274+
// created by a test, each numbered sequentially.
275+
var nonExistent bool
276+
if c.tempDir == "" { // Usually the case with js/wasm
277+
nonExistent = true
278+
} else {
279+
_, err := os.Stat(c.tempDir)
280+
nonExistent = os.IsNotExist(err)
281+
if err != nil && !nonExistent {
282+
c.Fatalf("TempDir: %v", err)
283+
}
284+
}
285+
286+
if nonExistent {
287+
c.Helper()
288+
289+
// Drop unusual characters (such as path separators or
290+
// characters interacting with globs) from the directory name to
291+
// avoid surprising os.MkdirTemp behavior.
292+
mapper := func(r rune) rune {
293+
if r < utf8.RuneSelf {
294+
const allowed = "!#$%&()+,-.=@^_{}~ "
295+
if '0' <= r && r <= '9' ||
296+
'a' <= r && r <= 'z' ||
297+
'A' <= r && r <= 'Z' {
298+
return r
299+
}
300+
if strings.ContainsRune(allowed, r) {
301+
return r
302+
}
303+
} else if unicode.IsLetter(r) || unicode.IsNumber(r) {
304+
return r
305+
}
306+
return -1
307+
}
308+
pattern := strings.Map(mapper, c.Name())
309+
c.tempDir, c.tempDirErr = os.MkdirTemp("", pattern)
310+
if c.tempDirErr == nil {
311+
c.Cleanup(func() {
312+
if err := os.RemoveAll(c.tempDir); err != nil {
313+
c.Errorf("TempDir RemoveAll cleanup: %v", err)
314+
}
315+
})
316+
}
317+
}
318+
319+
if c.tempDirErr != nil {
320+
c.Fatalf("TempDir: %v", c.tempDirErr)
321+
}
322+
seq := c.tempDirSeq
323+
c.tempDirSeq++
324+
dir := fmt.Sprintf("%s%c%03d", c.tempDir, os.PathSeparator, seq)
325+
if err := os.Mkdir(dir, 0777); err != nil {
326+
c.Fatalf("TempDir: %v", err)
327+
}
328+
return dir
329+
}
330+
261331
// runCleanup is called at the end of the test.
262332
func (c *common) runCleanup() {
263333
for {

src/testing/testing_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// +build !windows
2+
3+
// TODO: implement readdir for windows, then enable this file
4+
15
// Copyright 2014 The Go Authors. All rights reserved.
26
// Use of this source code is governed by a BSD-style
37
// license that can be found in the LICENSE file.
@@ -6,6 +10,7 @@ package testing_test
610

711
import (
812
"os"
13+
"path/filepath"
914
"testing"
1015
)
1116

@@ -16,3 +21,101 @@ import (
1621
func TestMain(m *testing.M) {
1722
os.Exit(m.Run())
1823
}
24+
25+
func TestTempDirInCleanup(t *testing.T) {
26+
var dir string
27+
28+
t.Run("test", func(t *testing.T) {
29+
t.Cleanup(func() {
30+
dir = t.TempDir()
31+
})
32+
_ = t.TempDir()
33+
})
34+
35+
fi, err := os.Stat(dir)
36+
if fi != nil {
37+
t.Fatalf("Directory %q from user Cleanup still exists", dir)
38+
}
39+
if !os.IsNotExist(err) {
40+
t.Fatalf("Unexpected error: %v", err)
41+
}
42+
}
43+
44+
func TestTempDirInBenchmark(t *testing.T) {
45+
testing.Benchmark(func(b *testing.B) {
46+
if !b.Run("test", func(b *testing.B) {
47+
// Add a loop so that the test won't fail. See issue 38677.
48+
for i := 0; i < b.N; i++ {
49+
_ = b.TempDir()
50+
}
51+
}) {
52+
t.Fatal("Sub test failure in a benchmark")
53+
}
54+
})
55+
}
56+
57+
func TestTempDir(t *testing.T) {
58+
testTempDir(t)
59+
t.Run("InSubtest", testTempDir)
60+
t.Run("test/subtest", testTempDir)
61+
t.Run("test\\subtest", testTempDir)
62+
t.Run("test:subtest", testTempDir)
63+
t.Run("test/..", testTempDir)
64+
t.Run("../test", testTempDir)
65+
t.Run("test[]", testTempDir)
66+
t.Run("test*", testTempDir)
67+
t.Run("äöüéè", testTempDir)
68+
}
69+
70+
func testTempDir(t *testing.T) {
71+
dirCh := make(chan string, 1)
72+
t.Cleanup(func() {
73+
// Verify directory has been removed.
74+
select {
75+
case dir := <-dirCh:
76+
fi, err := os.Stat(dir)
77+
if os.IsNotExist(err) {
78+
// All good
79+
return
80+
}
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
t.Errorf("directory %q still exists: %v, isDir=%v", dir, fi, fi.IsDir())
85+
default:
86+
if !t.Failed() {
87+
t.Fatal("never received dir channel")
88+
}
89+
}
90+
})
91+
92+
dir := t.TempDir()
93+
if dir == "" {
94+
t.Fatal("expected dir")
95+
}
96+
dir2 := t.TempDir()
97+
if dir == dir2 {
98+
t.Fatal("subsequent calls to TempDir returned the same directory")
99+
}
100+
if filepath.Dir(dir) != filepath.Dir(dir2) {
101+
t.Fatalf("calls to TempDir do not share a parent; got %q, %q", dir, dir2)
102+
}
103+
dirCh <- dir
104+
fi, err := os.Stat(dir)
105+
if err != nil {
106+
t.Fatal(err)
107+
}
108+
if !fi.IsDir() {
109+
t.Errorf("dir %q is not a dir", dir)
110+
}
111+
112+
glob := filepath.Join(dir, "*.txt")
113+
if _, err := filepath.Glob(glob); err != nil {
114+
t.Error(err)
115+
}
116+
117+
err = os.Remove(dir)
118+
if err != nil {
119+
t.Errorf("unexpected files in TempDir")
120+
}
121+
}

0 commit comments

Comments
 (0)