diff --git a/src/testing/is_wasi_no_test.go b/src/testing/is_wasi_no_test.go new file mode 100644 index 00000000..6c1a765e --- /dev/null +++ b/src/testing/is_wasi_no_test.go @@ -0,0 +1,10 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +//go:build !wasi +// +build !wasi + +package testing_test + +const isWASI = false diff --git a/src/testing/is_wasi_test.go b/src/testing/is_wasi_test.go new file mode 100644 index 00000000..588327eb --- /dev/null +++ b/src/testing/is_wasi_test.go @@ -0,0 +1,10 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +//go:build wasi +// +build wasi + +package testing_test + +const isWASI = true diff --git a/src/testing/testing.go b/src/testing/testing.go index 5ca60de4..47679ebd 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -15,6 +15,8 @@ import ( "os" "strings" "time" + "unicode" + "unicode/utf8" ) // Testing flags. @@ -58,6 +60,10 @@ type common struct { name string // Name of test or benchmark. start time.Time // Time test or benchmark started duration time.Duration + + tempDir string + tempDirErr error + tempDirSeq int32 } // Short reports whether the -test.short flag is set. @@ -258,6 +264,70 @@ func (c *common) Cleanup(f func()) { c.cleanups = append(c.cleanups, f) } +// TempDir returns a temporary directory for the test to use. +// The directory is automatically removed by Cleanup when the test and +// all its subtests complete. +// Each subsequent call to t.TempDir returns a unique directory; +// if the directory creation fails, TempDir terminates the test by calling Fatal. +func (c *common) TempDir() string { + // Use a single parent directory for all the temporary directories + // created by a test, each numbered sequentially. + 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() + + // Drop unusual characters (such as path separators or + // characters interacting with globs) from the directory name to + // avoid surprising os.MkdirTemp behavior. + mapper := func(r rune) rune { + if r < utf8.RuneSelf { + const allowed = "!#$%&()+,-.=@^_{}~ " + if '0' <= r && r <= '9' || + 'a' <= r && r <= 'z' || + 'A' <= r && r <= 'Z' { + return r + } + if strings.ContainsRune(allowed, r) { + return r + } + } else if unicode.IsLetter(r) || unicode.IsNumber(r) { + return r + } + return -1 + } + pattern := strings.Map(mapper, c.Name()) + c.tempDir, c.tempDirErr = os.MkdirTemp("", pattern) + if c.tempDirErr == nil { + c.Cleanup(func() { + if err := os.RemoveAll(c.tempDir); err != nil { + c.Errorf("TempDir RemoveAll cleanup: %v", err) + } + }) + } + } + + if c.tempDirErr != nil { + c.Fatalf("TempDir: %v", c.tempDirErr) + } + seq := c.tempDirSeq + c.tempDirSeq++ + dir := fmt.Sprintf("%s%c%03d", c.tempDir, os.PathSeparator, seq) + if err := os.Mkdir(dir, 0777); err != nil { + c.Fatalf("TempDir: %v", err) + } + return dir +} + // runCleanup is called at the end of the test. func (c *common) runCleanup() { for { diff --git a/src/testing/testing_test.go b/src/testing/testing_test.go index 45e44683..36c1a396 100644 --- a/src/testing/testing_test.go +++ b/src/testing/testing_test.go @@ -1,3 +1,8 @@ +//go:build !windows +// +build !windows + +// TODO: implement readdir for windows, then enable this file + // Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -6,6 +11,7 @@ package testing_test import ( "os" + "path/filepath" "testing" ) @@ -16,3 +22,111 @@ import ( func TestMain(m *testing.M) { os.Exit(m.Run()) } + +func TestTempDirInCleanup(t *testing.T) { + if isWASI { + t.Log("Skipping. TODO: implement RemoveAll for wasi") + return + } + + 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) { + if isWASI { + t.Log("Skipping. TODO: implement RemoveAll for wasi") + return + } + + testTempDir(t) + t.Run("InSubtest", testTempDir) + t.Run("test/subtest", testTempDir) + t.Run("test\\subtest", testTempDir) + t.Run("test:subtest", testTempDir) + t.Run("test/..", testTempDir) + t.Run("../test", testTempDir) + t.Run("test[]", testTempDir) + t.Run("test*", testTempDir) + t.Run("äöüéè", testTempDir) +} + +func testTempDir(t *testing.T) { + dirCh := make(chan string, 1) + t.Cleanup(func() { + // Verify directory has been removed. + select { + case dir := <-dirCh: + fi, err := os.Stat(dir) + if os.IsNotExist(err) { + // All good + return + } + if err != nil { + t.Fatal(err) + } + t.Errorf("directory %q still exists: %v, isDir=%v", dir, fi, fi.IsDir()) + default: + if !t.Failed() { + t.Fatal("never received dir channel") + } + } + }) + + dir := t.TempDir() + if dir == "" { + t.Fatal("expected dir") + } + dir2 := t.TempDir() + if dir == dir2 { + t.Fatal("subsequent calls to TempDir returned the same directory") + } + if filepath.Dir(dir) != filepath.Dir(dir2) { + t.Fatalf("calls to TempDir do not share a parent; got %q, %q", dir, dir2) + } + dirCh <- dir + fi, err := os.Stat(dir) + if err != nil { + t.Fatal(err) + } + if !fi.IsDir() { + t.Errorf("dir %q is not a dir", dir) + } + + glob := filepath.Join(dir, "*.txt") + if _, err := filepath.Glob(glob); err != nil { + t.Error(err) + } + + err = os.Remove(dir) + if err != nil { + t.Errorf("unexpected files in TempDir") + } +}