aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/testing
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/testing')
-rw-r--r--libgo/go/testing/benchmark.go7
-rw-r--r--libgo/go/testing/benchmark_test.go24
-rw-r--r--libgo/go/testing/fstest/mapfs.go6
-rw-r--r--libgo/go/testing/fstest/testfs.go19
-rw-r--r--libgo/go/testing/fstest/testfs_test.go9
-rw-r--r--libgo/go/testing/run_example.go1
-rw-r--r--libgo/go/testing/run_example_js.go1
-rw-r--r--libgo/go/testing/sub_test.go2
-rw-r--r--libgo/go/testing/testing.go160
-rw-r--r--libgo/go/testing/testing_test.go93
10 files changed, 275 insertions, 47 deletions
diff --git a/libgo/go/testing/benchmark.go b/libgo/go/testing/benchmark.go
index a8f75e9..15b4426 100644
--- a/libgo/go/testing/benchmark.go
+++ b/libgo/go/testing/benchmark.go
@@ -238,12 +238,15 @@ func (b *B) run1() bool {
}
// Only print the output if we know we are not going to proceed.
// Otherwise it is printed in processBench.
- if atomic.LoadInt32(&b.hasSub) != 0 || b.finished {
+ b.mu.RLock()
+ finished := b.finished
+ b.mu.RUnlock()
+ if atomic.LoadInt32(&b.hasSub) != 0 || finished {
tag := "BENCH"
if b.skipped {
tag = "SKIP"
}
- if b.chatty != nil && (len(b.output) > 0 || b.finished) {
+ if b.chatty != nil && (len(b.output) > 0 || finished) {
b.trimOutput()
fmt.Fprintf(b.w, "--- %s: %s\n%s", tag, b.name, b.output)
}
diff --git a/libgo/go/testing/benchmark_test.go b/libgo/go/testing/benchmark_test.go
index 4c1cbd1..3b1dc82 100644
--- a/libgo/go/testing/benchmark_test.go
+++ b/libgo/go/testing/benchmark_test.go
@@ -102,6 +102,30 @@ func TestRunParallelFail(t *testing.T) {
})
}
+func TestRunParallelFatal(t *testing.T) {
+ testing.Benchmark(func(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ if b.N > 1 {
+ b.Fatal("error")
+ }
+ }
+ })
+ })
+}
+
+func TestRunParallelSkipNow(t *testing.T) {
+ testing.Benchmark(func(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ if b.N > 1 {
+ b.SkipNow()
+ }
+ }
+ })
+ })
+}
+
func ExampleB_RunParallel() {
// Parallel benchmark for text/template.Template.Execute on a single object.
testing.Benchmark(func(b *testing.B) {
diff --git a/libgo/go/testing/fstest/mapfs.go b/libgo/go/testing/fstest/mapfs.go
index a5d4a23fa..9fef2f4 100644
--- a/libgo/go/testing/fstest/mapfs.go
+++ b/libgo/go/testing/fstest/mapfs.go
@@ -223,12 +223,12 @@ func (d *mapDir) Read(b []byte) (int, error) {
func (d *mapDir) ReadDir(count int) ([]fs.DirEntry, error) {
n := len(d.entry) - d.offset
- if count > 0 && n > count {
- n = count
- }
if n == 0 && count > 0 {
return nil, io.EOF
}
+ if count > 0 && n > count {
+ n = count
+ }
list := make([]fs.DirEntry, n)
for i := range list {
list[i] = &d.entry[d.offset+i]
diff --git a/libgo/go/testing/fstest/testfs.go b/libgo/go/testing/fstest/testfs.go
index 8fc8aca..5c4f30a 100644
--- a/libgo/go/testing/fstest/testfs.go
+++ b/libgo/go/testing/fstest/testfs.go
@@ -10,7 +10,6 @@ import (
"fmt"
"io"
"io/fs"
- "io/ioutil"
"path"
"reflect"
"sort"
@@ -23,7 +22,7 @@ import (
// opening and checking that each file behaves correctly.
// It also checks that the file system contains at least the expected files.
// As a special case, if no expected files are listed, fsys must be empty.
-// Otherwise, fsys must only contain at least the listed files: it can also contain others.
+// Otherwise, fsys must contain at least the listed files; it can also contain others.
// The contents of fsys must not change concurrently with TestFS.
//
// If TestFS finds any misbehaviors, it returns an error reporting all of them.
@@ -303,7 +302,7 @@ func (t *fsTester) checkGlob(dir string, list []fs.DirEntry) {
for i, e := range elem {
var pattern []rune
for j, r := range e {
- if r == '*' || r == '?' || r == '\\' || r == '[' {
+ if r == '*' || r == '?' || r == '\\' || r == '[' || r == '-' {
pattern = append(pattern, '\\', r)
continue
}
@@ -514,7 +513,7 @@ func (t *fsTester) checkFile(file string) {
return
}
- data, err := ioutil.ReadAll(f)
+ data, err := io.ReadAll(f)
if err != nil {
f.Close()
t.errorf("%s: Open+ReadAll: %v", file, err)
@@ -538,6 +537,18 @@ func (t *fsTester) checkFile(file string) {
}
t.checkFileRead(file, "ReadAll vs fsys.ReadFile", data, data2)
+ // Modify the data and check it again. Modifying the
+ // returned byte slice should not affect the next call.
+ for i := range data2 {
+ data2[i]++
+ }
+ data2, err = fsys.ReadFile(file)
+ if err != nil {
+ t.errorf("%s: second call to fsys.ReadFile: %v", file, err)
+ return
+ }
+ t.checkFileRead(file, "Readall vs second fsys.ReadFile", data, data2)
+
t.checkBadPath(file, "ReadFile",
func(name string) error { _, err := fsys.ReadFile(name); return err })
}
diff --git a/libgo/go/testing/fstest/testfs_test.go b/libgo/go/testing/fstest/testfs_test.go
index 5b8813c..aefb4b3 100644
--- a/libgo/go/testing/fstest/testfs_test.go
+++ b/libgo/go/testing/fstest/testfs_test.go
@@ -29,3 +29,12 @@ func TestSymlink(t *testing.T) {
t.Fatal(err)
}
}
+
+func TestDash(t *testing.T) {
+ m := MapFS{
+ "a-b/a": {Data: []byte("a-b/a")},
+ }
+ if err := TestFS(m, "a-b/a"); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/libgo/go/testing/run_example.go b/libgo/go/testing/run_example.go
index 4dc83f7..d9e342d 100644
--- a/libgo/go/testing/run_example.go
+++ b/libgo/go/testing/run_example.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !js
// +build !js
// TODO(@musiol, @odeke-em): re-unify this entire file back into
diff --git a/libgo/go/testing/run_example_js.go b/libgo/go/testing/run_example_js.go
index 1d4164b..d914633 100644
--- a/libgo/go/testing/run_example_js.go
+++ b/libgo/go/testing/run_example_js.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build js
// +build js
package testing
diff --git a/libgo/go/testing/sub_test.go b/libgo/go/testing/sub_test.go
index 5b226f8..6c7d83a 100644
--- a/libgo/go/testing/sub_test.go
+++ b/libgo/go/testing/sub_test.go
@@ -822,7 +822,7 @@ func TestLogAfterComplete(t *T) {
c2 <- fmt.Sprintf("subtest panic with unexpected value %v", p)
return
}
- const want = "Log in goroutine after TestLateLog has completed"
+ const want = "Log in goroutine after TestLateLog has completed: log after test"
if !strings.Contains(s, want) {
c2 <- fmt.Sprintf("subtest panic %q does not contain %q", s, want)
}
diff --git a/libgo/go/testing/testing.go b/libgo/go/testing/testing.go
index 143d81c..4c823dc 100644
--- a/libgo/go/testing/testing.go
+++ b/libgo/go/testing/testing.go
@@ -190,7 +190,7 @@
// }
// }
//
-// The race detector kills the program if it exceeds 8192 concurrent goroutines,
+// The race detector kills the program if it exceeds 8128 concurrent goroutines,
// so use care when running parallel tests with the -race flag set.
//
// Run does not return until parallel subtests have completed, providing a way
@@ -208,14 +208,14 @@
//
// Main
//
-// It is sometimes necessary for a test program to do extra setup or teardown
-// before or after testing. It is also sometimes necessary for a test to control
+// It is sometimes necessary for a test or benchmark program to do extra setup or teardown
+// before or after it executes. It is also sometimes necessary to control
// which code runs on the main thread. To support these and other cases,
// if a test file contains a function:
//
// func TestMain(m *testing.M)
//
-// then the generated test will call TestMain(m) instead of running the tests
+// then the generated test will call TestMain(m) instead of running the tests or benchmarks
// directly. TestMain runs in the main goroutine and can do whatever setup
// and teardown is necessary around a call to m.Run. m.Run will return an exit
// code that may be passed to os.Exit. If TestMain returns, the test wrapper
@@ -233,6 +233,8 @@
// os.Exit(m.Run())
// }
//
+// TestMain is a low-level primitive and should not be necessary for casual
+// testing needs, where ordinary test functions suffice.
package testing
import (
@@ -242,6 +244,7 @@ import (
"fmt"
"internal/race"
"io"
+ "math/rand"
"os"
"runtime"
"runtime/debug"
@@ -251,6 +254,8 @@ import (
"sync"
"sync/atomic"
"time"
+ "unicode"
+ "unicode/utf8"
)
var initRan bool
@@ -299,6 +304,7 @@ func Init() {
cpuListStr = flag.String("test.cpu", "", "comma-separated `list` of cpu counts to run each test with")
parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "run at most `n` tests in parallel")
testlog = flag.String("test.testlogfile", "", "write test action log to `file` (for use only by cmd/go)")
+ shuffle = flag.String("test.shuffle", "off", "randomize the execution order of tests and benchmarks")
initBenchmarkFlags()
}
@@ -325,6 +331,7 @@ var (
timeout *time.Duration
cpuListStr *string
parallel *int
+ shuffle *string
testlog *string
haveExamples bool // are there examples?
@@ -388,17 +395,17 @@ type common struct {
w io.Writer // For flushToParent.
ran bool // Test or benchmark (or one of its subtests) was executed.
failed bool // Test or benchmark has failed.
- skipped bool // Test of benchmark has been skipped.
+ skipped bool // Test or benchmark has been skipped.
done bool // Test is finished and all subtests have completed.
helperPCs map[uintptr]struct{} // functions to be skipped when writing file/line info
helperNames map[string]struct{} // helperPCs converted to function names
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.
+ finished bool // Test function has completed.
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.
@@ -639,6 +646,7 @@ type TB interface {
Log(args ...interface{})
Logf(format string, args ...interface{})
Name() string
+ Setenv(key, value string)
Skip(args ...interface{})
SkipNow()
Skipf(format string, args ...interface{})
@@ -666,12 +674,17 @@ var _ TB = (*B)(nil)
type T struct {
common
isParallel bool
+ isEnvSet bool
context *testContext // For running tests and subtests.
}
func (c *common) private() {}
-// Name returns the name of the running test or benchmark.
+// Name returns the name of the running (sub-) test or benchmark.
+//
+// The name will include the name of the test along with the names of
+// any nested sub-tests. If two sibling sub-tests have the same name,
+// Name will append a suffix to guarantee the returned name is unique.
func (c *common) Name() string {
return c.name
}
@@ -737,7 +750,9 @@ func (c *common) FailNow() {
// it would run on a test failure. Because we send on c.signal during
// a top-of-stack deferred function now, we know that the send
// only happens after any other stacked defers have completed.
+ c.mu.Lock()
c.finished = true
+ c.mu.Unlock()
runtime.Goexit()
}
@@ -761,7 +776,7 @@ func (c *common) logDepth(s string, depth int) {
return
}
}
- panic("Log in goroutine after " + c.name + " has completed")
+ panic("Log in goroutine after " + c.name + " has completed: " + s)
} else {
if c.chatty != nil {
if c.bench {
@@ -872,15 +887,11 @@ func (c *common) Skipf(format string, args ...interface{}) {
// other goroutines created during the test. Calling SkipNow does not stop
// those other goroutines.
func (c *common) SkipNow() {
- c.skip()
- c.finished = true
- runtime.Goexit()
-}
-
-func (c *common) skip() {
c.mu.Lock()
- defer c.mu.Unlock()
c.skipped = true
+ c.finished = true
+ c.mu.Unlock()
+ runtime.Goexit()
}
// Skipped reports whether the test was skipped.
@@ -911,7 +922,7 @@ func (c *common) Helper() {
}
}
-// Cleanup registers a function to be called when the test and all its
+// Cleanup registers a function to be called when the test (or subtest) and all its
// subtests complete. Cleanup functions will be called in last added,
// first called order.
func (c *common) Cleanup(f func()) {
@@ -942,11 +953,6 @@ func (c *common) Cleanup(f func()) {
c.cleanups = append(c.cleanups, fn)
}
-var tempDirReplacer struct {
- sync.Once
- r *strings.Replacer
-}
-
// 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.
@@ -970,13 +976,26 @@ func (c *common) TempDir() string {
if nonExistent {
c.Helper()
- // os.MkdirTemp doesn't like path separators in its pattern,
- // so mangle the name to accommodate subtests.
- tempDirReplacer.Do(func() {
- tempDirReplacer.r = strings.NewReplacer("/", "_", "\\", "_", ":", "_")
- })
- pattern := tempDirReplacer.r.Replace(c.Name())
-
+ // 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() {
@@ -999,6 +1018,29 @@ func (c *common) TempDir() string {
return dir
}
+// Setenv calls os.Setenv(key, value) and uses Cleanup to
+// restore the environment variable to its original value
+// after the test.
+//
+// This cannot be used in parallel tests.
+func (c *common) Setenv(key, value string) {
+ prevValue, ok := os.LookupEnv(key)
+
+ if err := os.Setenv(key, value); err != nil {
+ c.Fatalf("cannot set environment variable: %v", err)
+ }
+
+ if ok {
+ c.Cleanup(func() {
+ os.Setenv(key, prevValue)
+ })
+ } else {
+ c.Cleanup(func() {
+ os.Unsetenv(key)
+ })
+ }
+}
+
// panicHanding is an argument to runCleanup.
type panicHandling int
@@ -1070,6 +1112,9 @@ func (t *T) Parallel() {
if t.isParallel {
panic("testing: t.Parallel called multiple times")
}
+ if t.isEnvSet {
+ panic("testing: t.Parallel called after t.Setenv; cannot set environment variables in parallel tests")
+ }
t.isParallel = true
// We don't want to include the time we spend waiting for serial tests
@@ -1103,6 +1148,21 @@ func (t *T) Parallel() {
t.raceErrors += -race.Errors()
}
+// Setenv calls os.Setenv(key, value) and uses Cleanup to
+// restore the environment variable to its original value
+// after the test.
+//
+// This cannot be used in parallel tests.
+func (t *T) Setenv(key, value string) {
+ if t.isParallel {
+ panic("testing: t.Setenv called after t.Parallel; cannot set environment variables in parallel tests")
+ }
+
+ t.isEnvSet = true
+
+ t.common.Setenv(key, value)
+}
+
// InternalTest is an internal type but exported because it is cross-package;
// it is part of the implementation of the "go test" command.
type InternalTest struct {
@@ -1132,10 +1192,16 @@ func tRunner(t *T, fn func(t *T)) {
err := recover()
signal := true
- if !t.finished && err == nil {
+ t.mu.RLock()
+ finished := t.finished
+ t.mu.RUnlock()
+ if !finished && err == nil {
err = errNilPanicOrGoexit
for p := t.parent; p != nil; p = p.parent {
- if p.finished {
+ p.mu.RLock()
+ finished = p.finished
+ p.mu.RUnlock()
+ if finished {
t.Errorf("%v: subtest may have called FailNow on a parent test", err)
err = nil
signal = false
@@ -1229,7 +1295,9 @@ func tRunner(t *T, fn func(t *T)) {
fn(t)
// code beyond here will not be executed when FailNow is invoked
+ t.mu.Lock()
t.finished = true
+ t.mu.Unlock()
}
// Run runs f as a subtest of t called name. It runs f in a separate goroutine
@@ -1252,7 +1320,7 @@ func (t *T) Run(name string, f func(t *T)) bool {
t = &T{
common: common{
barrier: make(chan bool),
- signal: make(chan bool),
+ signal: make(chan bool, 1),
name: testName,
parent: &t.common,
level: t.level + 1,
@@ -1444,6 +1512,25 @@ func (m *M) Run() (code int) {
return
}
+ if *shuffle != "off" {
+ var n int64
+ var err error
+ if *shuffle == "on" {
+ n = time.Now().UnixNano()
+ } else {
+ n, err = strconv.ParseInt(*shuffle, 10, 64)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, `testing: -shuffle should be "off", "on", or a valid integer:`, err)
+ m.exitCode = 2
+ return
+ }
+ }
+ fmt.Println("-test.shuffle", n)
+ rng := rand.New(rand.NewSource(n))
+ rng.Shuffle(len(m.tests), func(i, j int) { m.tests[i], m.tests[j] = m.tests[j], m.tests[i] })
+ rng.Shuffle(len(m.benchmarks), func(i, j int) { m.benchmarks[i], m.benchmarks[j] = m.benchmarks[j], m.benchmarks[i] })
+ }
+
parseCpuList()
m.before()
@@ -1533,7 +1620,7 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT
ctx.deadline = deadline
t := &T{
common: common{
- signal: make(chan bool),
+ signal: make(chan bool, 1),
barrier: make(chan bool),
w: os.Stdout,
},
@@ -1546,11 +1633,12 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT
for _, test := range tests {
t.Run(test.Name, test.F)
}
- // Run catching the signal rather than the tRunner as a separate
- // goroutine to avoid adding a goroutine during the sequential
- // phase as this pollutes the stacktrace output when aborting.
- go func() { <-t.signal }()
})
+ select {
+ case <-t.signal:
+ default:
+ panic("internal error: tRunner exited without sending on t.signal")
+ }
ok = ok && !t.Failed()
ran = ran || t.ran
}
diff --git a/libgo/go/testing/testing_test.go b/libgo/go/testing/testing_test.go
index 0f09698..08ae239 100644
--- a/libgo/go/testing/testing_test.go
+++ b/libgo/go/testing/testing_test.go
@@ -58,6 +58,9 @@ func TestTempDir(t *testing.T) {
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) {
@@ -74,7 +77,7 @@ func testTempDir(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- t.Errorf("directory %q stil exists: %v, isDir=%v", dir, fi, fi.IsDir())
+ t.Errorf("directory %q still exists: %v, isDir=%v", dir, fi, fi.IsDir())
default:
if !t.Failed() {
t.Fatal("never received dir channel")
@@ -108,4 +111,92 @@ func testTempDir(t *testing.T) {
if len(files) > 0 {
t.Errorf("unexpected %d files in TempDir: %v", len(files), files)
}
+
+ glob := filepath.Join(dir, "*.txt")
+ if _, err := filepath.Glob(glob); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestSetenv(t *testing.T) {
+ tests := []struct {
+ name string
+ key string
+ initialValueExists bool
+ initialValue string
+ newValue string
+ }{
+ {
+ name: "initial value exists",
+ key: "GO_TEST_KEY_1",
+ initialValueExists: true,
+ initialValue: "111",
+ newValue: "222",
+ },
+ {
+ name: "initial value exists but empty",
+ key: "GO_TEST_KEY_2",
+ initialValueExists: true,
+ initialValue: "",
+ newValue: "222",
+ },
+ {
+ name: "initial value is not exists",
+ key: "GO_TEST_KEY_3",
+ initialValueExists: false,
+ initialValue: "",
+ newValue: "222",
+ },
+ }
+
+ for _, test := range tests {
+ if test.initialValueExists {
+ if err := os.Setenv(test.key, test.initialValue); err != nil {
+ t.Fatalf("unable to set env: got %v", err)
+ }
+ } else {
+ os.Unsetenv(test.key)
+ }
+
+ t.Run(test.name, func(t *testing.T) {
+ t.Setenv(test.key, test.newValue)
+ if os.Getenv(test.key) != test.newValue {
+ t.Fatalf("unexpected value after t.Setenv: got %s, want %s", os.Getenv(test.key), test.newValue)
+ }
+ })
+
+ got, exists := os.LookupEnv(test.key)
+ if got != test.initialValue {
+ t.Fatalf("unexpected value after t.Setenv cleanup: got %s, want %s", got, test.initialValue)
+ }
+ if exists != test.initialValueExists {
+ t.Fatalf("unexpected value after t.Setenv cleanup: got %t, want %t", exists, test.initialValueExists)
+ }
+ }
+}
+
+func TestSetenvWithParallelAfterSetenv(t *testing.T) {
+ defer func() {
+ want := "testing: t.Parallel called after t.Setenv; cannot set environment variables in parallel tests"
+ if got := recover(); got != want {
+ t.Fatalf("expected panic; got %#v want %q", got, want)
+ }
+ }()
+
+ t.Setenv("GO_TEST_KEY_1", "value")
+
+ t.Parallel()
+}
+
+func TestSetenvWithParallelBeforeSetenv(t *testing.T) {
+ defer func() {
+ want := "testing: t.Setenv called after t.Parallel; cannot set environment variables in parallel tests"
+ if got := recover(); got != want {
+ t.Fatalf("expected panic; got %#v want %q", got, want)
+ }
+ }()
+
+ t.Parallel()
+
+ t.Setenv("GO_TEST_KEY_1", "value")
}