diff options
Diffstat (limited to 'libgo/go/testing')
-rw-r--r-- | libgo/go/testing/benchmark.go | 7 | ||||
-rw-r--r-- | libgo/go/testing/benchmark_test.go | 24 | ||||
-rw-r--r-- | libgo/go/testing/fstest/mapfs.go | 6 | ||||
-rw-r--r-- | libgo/go/testing/fstest/testfs.go | 19 | ||||
-rw-r--r-- | libgo/go/testing/fstest/testfs_test.go | 9 | ||||
-rw-r--r-- | libgo/go/testing/run_example.go | 1 | ||||
-rw-r--r-- | libgo/go/testing/run_example_js.go | 1 | ||||
-rw-r--r-- | libgo/go/testing/sub_test.go | 2 | ||||
-rw-r--r-- | libgo/go/testing/testing.go | 160 | ||||
-rw-r--r-- | libgo/go/testing/testing_test.go | 93 |
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") } |