aboutsummaryrefslogtreecommitdiff
path: root/src/testing
diff options
context:
space:
mode:
Diffstat (limited to 'src/testing')
-rw-r--r--src/testing/benchmark.go27
-rw-r--r--src/testing/benchmark_test.go21
-rw-r--r--src/testing/fstest/mapfs.go238
-rw-r--r--src/testing/fstest/mapfs_test.go19
-rw-r--r--src/testing/fstest/testfs.go602
-rw-r--r--src/testing/helper_test.go31
-rw-r--r--src/testing/iotest/reader.go174
-rw-r--r--src/testing/iotest/reader_test.go10
-rw-r--r--src/testing/testing.go79
-rw-r--r--src/testing/testing_test.go7
10 files changed, 1161 insertions, 47 deletions
diff --git a/src/testing/benchmark.go b/src/testing/benchmark.go
index e9687bf26d..a8f75e9712 100644
--- a/src/testing/benchmark.go
+++ b/src/testing/benchmark.go
@@ -8,6 +8,7 @@ import (
"flag"
"fmt"
"internal/race"
+ "internal/sysinfo"
"io"
"math"
"os"
@@ -262,6 +263,9 @@ func (b *B) run() {
if b.importPath != "" {
fmt.Fprintf(b.w, "pkg: %s\n", b.importPath)
}
+ if cpu := sysinfo.CPU.Name(); cpu != "" {
+ fmt.Fprintf(b.w, "cpu: %s\n", cpu)
+ }
})
if b.context != nil {
// Running go test --test.bench
@@ -447,23 +451,27 @@ func (r BenchmarkResult) String() string {
func prettyPrint(w io.Writer, x float64, unit string) {
// Print all numbers with 10 places before the decimal point
- // and small numbers with three sig figs.
+ // and small numbers with four sig figs. Field widths are
+ // chosen to fit the whole part in 10 places while aligning
+ // the decimal point of all fractional formats.
var format string
switch y := math.Abs(x); {
- case y == 0 || y >= 99.95:
+ case y == 0 || y >= 999.95:
format = "%10.0f %s"
- case y >= 9.995:
+ case y >= 99.995:
format = "%12.1f %s"
- case y >= 0.9995:
+ case y >= 9.9995:
format = "%13.2f %s"
- case y >= 0.09995:
+ case y >= 0.99995:
format = "%14.3f %s"
- case y >= 0.009995:
+ case y >= 0.099995:
format = "%15.4f %s"
- case y >= 0.0009995:
+ case y >= 0.0099995:
format = "%16.5f %s"
- default:
+ case y >= 0.00099995:
format = "%17.6f %s"
+ default:
+ format = "%18.7f %s"
}
fmt.Fprintf(w, format, x, unit)
}
@@ -648,6 +656,9 @@ func (b *B) Run(name string, f func(b *B)) bool {
if b.importPath != "" {
fmt.Printf("pkg: %s\n", b.importPath)
}
+ if cpu := sysinfo.CPU.Name(); cpu != "" {
+ fmt.Printf("cpu: %s\n", cpu)
+ }
})
fmt.Println(benchName)
diff --git a/src/testing/benchmark_test.go b/src/testing/benchmark_test.go
index 1434c2613f..4c1cbd1933 100644
--- a/src/testing/benchmark_test.go
+++ b/src/testing/benchmark_test.go
@@ -22,13 +22,14 @@ var prettyPrintTests = []struct {
{0, " 0 x"},
{1234.1, " 1234 x"},
{-1234.1, " -1234 x"},
- {99.950001, " 100 x"},
- {99.949999, " 99.9 x"},
- {9.9950001, " 10.0 x"},
- {9.9949999, " 9.99 x"},
- {-9.9949999, " -9.99 x"},
- {0.0099950001, " 0.0100 x"},
- {0.0099949999, " 0.00999 x"},
+ {999.950001, " 1000 x"},
+ {999.949999, " 999.9 x"},
+ {99.9950001, " 100.0 x"},
+ {99.9949999, " 99.99 x"},
+ {-99.9949999, " -99.99 x"},
+ {0.000999950001, " 0.001000 x"},
+ {0.000999949999, " 0.0009999 x"}, // smallest case
+ {0.0000999949999, " 0.0001000 x"},
}
func TestPrettyPrint(t *testing.T) {
@@ -50,13 +51,13 @@ func TestResultString(t *testing.T) {
if r.NsPerOp() != 2 {
t.Errorf("NsPerOp: expected 2, actual %v", r.NsPerOp())
}
- if want, got := " 100\t 2.40 ns/op", r.String(); want != got {
+ if want, got := " 100\t 2.400 ns/op", r.String(); want != got {
t.Errorf("String: expected %q, actual %q", want, got)
}
// Test sub-1 ns/op (issue #31005)
r.T = 40 * time.Nanosecond
- if want, got := " 100\t 0.400 ns/op", r.String(); want != got {
+ if want, got := " 100\t 0.4000 ns/op", r.String(); want != got {
t.Errorf("String: expected %q, actual %q", want, got)
}
@@ -130,7 +131,7 @@ func TestReportMetric(t *testing.T) {
}
// Test stringing.
res.N = 1 // Make the output stable
- want := " 1\t 12345 ns/op\t 0.200 frobs/op"
+ want := " 1\t 12345 ns/op\t 0.2000 frobs/op"
if want != res.String() {
t.Errorf("expected %q, actual %q", want, res.String())
}
diff --git a/src/testing/fstest/mapfs.go b/src/testing/fstest/mapfs.go
new file mode 100644
index 0000000000..a5d4a23fac
--- /dev/null
+++ b/src/testing/fstest/mapfs.go
@@ -0,0 +1,238 @@
+// Copyright 2020 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.
+
+package fstest
+
+import (
+ "io"
+ "io/fs"
+ "path"
+ "sort"
+ "strings"
+ "time"
+)
+
+// A MapFS is a simple in-memory file system for use in tests,
+// represented as a map from path names (arguments to Open)
+// to information about the files or directories they represent.
+//
+// The map need not include parent directories for files contained
+// in the map; those will be synthesized if needed.
+// But a directory can still be included by setting the MapFile.Mode's ModeDir bit;
+// this may be necessary for detailed control over the directory's FileInfo
+// or to create an empty directory.
+//
+// File system operations read directly from the map,
+// so that the file system can be changed by editing the map as needed.
+// An implication is that file system operations must not run concurrently
+// with changes to the map, which would be a race.
+// Another implication is that opening or reading a directory requires
+// iterating over the entire map, so a MapFS should typically be used with not more
+// than a few hundred entries or directory reads.
+type MapFS map[string]*MapFile
+
+// A MapFile describes a single file in a MapFS.
+type MapFile struct {
+ Data []byte // file content
+ Mode fs.FileMode // FileInfo.Mode
+ ModTime time.Time // FileInfo.ModTime
+ Sys interface{} // FileInfo.Sys
+}
+
+var _ fs.FS = MapFS(nil)
+var _ fs.File = (*openMapFile)(nil)
+
+// Open opens the named file.
+func (fsys MapFS) Open(name string) (fs.File, error) {
+ if !fs.ValidPath(name) {
+ return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
+ }
+ file := fsys[name]
+ if file != nil && file.Mode&fs.ModeDir == 0 {
+ // Ordinary file
+ return &openMapFile{name, mapFileInfo{path.Base(name), file}, 0}, nil
+ }
+
+ // Directory, possibly synthesized.
+ // Note that file can be nil here: the map need not contain explicit parent directories for all its files.
+ // But file can also be non-nil, in case the user wants to set metadata for the directory explicitly.
+ // Either way, we need to construct the list of children of this directory.
+ var list []mapFileInfo
+ var elem string
+ var need = make(map[string]bool)
+ if name == "." {
+ elem = "."
+ for fname, f := range fsys {
+ i := strings.Index(fname, "/")
+ if i < 0 {
+ list = append(list, mapFileInfo{fname, f})
+ } else {
+ need[fname[:i]] = true
+ }
+ }
+ } else {
+ elem = name[strings.LastIndex(name, "/")+1:]
+ prefix := name + "/"
+ for fname, f := range fsys {
+ if strings.HasPrefix(fname, prefix) {
+ felem := fname[len(prefix):]
+ i := strings.Index(felem, "/")
+ if i < 0 {
+ list = append(list, mapFileInfo{felem, f})
+ } else {
+ need[fname[len(prefix):len(prefix)+i]] = true
+ }
+ }
+ }
+ // If the directory name is not in the map,
+ // and there are no children of the name in the map,
+ // then the directory is treated as not existing.
+ if file == nil && list == nil && len(need) == 0 {
+ return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
+ }
+ }
+ for _, fi := range list {
+ delete(need, fi.name)
+ }
+ for name := range need {
+ list = append(list, mapFileInfo{name, &MapFile{Mode: fs.ModeDir}})
+ }
+ sort.Slice(list, func(i, j int) bool {
+ return list[i].name < list[j].name
+ })
+
+ if file == nil {
+ file = &MapFile{Mode: fs.ModeDir}
+ }
+ return &mapDir{name, mapFileInfo{elem, file}, list, 0}, nil
+}
+
+// fsOnly is a wrapper that hides all but the fs.FS methods,
+// to avoid an infinite recursion when implementing special
+// methods in terms of helpers that would use them.
+// (In general, implementing these methods using the package fs helpers
+// is redundant and unnecessary, but having the methods may make
+// MapFS exercise more code paths when used in tests.)
+type fsOnly struct{ fs.FS }
+
+func (fsys MapFS) ReadFile(name string) ([]byte, error) {
+ return fs.ReadFile(fsOnly{fsys}, name)
+}
+
+func (fsys MapFS) Stat(name string) (fs.FileInfo, error) {
+ return fs.Stat(fsOnly{fsys}, name)
+}
+
+func (fsys MapFS) ReadDir(name string) ([]fs.DirEntry, error) {
+ return fs.ReadDir(fsOnly{fsys}, name)
+}
+
+func (fsys MapFS) Glob(pattern string) ([]string, error) {
+ return fs.Glob(fsOnly{fsys}, pattern)
+}
+
+type noSub struct {
+ MapFS
+}
+
+func (noSub) Sub() {} // not the fs.SubFS signature
+
+func (fsys MapFS) Sub(dir string) (fs.FS, error) {
+ return fs.Sub(noSub{fsys}, dir)
+}
+
+// A mapFileInfo implements fs.FileInfo and fs.DirEntry for a given map file.
+type mapFileInfo struct {
+ name string
+ f *MapFile
+}
+
+func (i *mapFileInfo) Name() string { return i.name }
+func (i *mapFileInfo) Size() int64 { return int64(len(i.f.Data)) }
+func (i *mapFileInfo) Mode() fs.FileMode { return i.f.Mode }
+func (i *mapFileInfo) Type() fs.FileMode { return i.f.Mode.Type() }
+func (i *mapFileInfo) ModTime() time.Time { return i.f.ModTime }
+func (i *mapFileInfo) IsDir() bool { return i.f.Mode&fs.ModeDir != 0 }
+func (i *mapFileInfo) Sys() interface{} { return i.f.Sys }
+func (i *mapFileInfo) Info() (fs.FileInfo, error) { return i, nil }
+
+// An openMapFile is a regular (non-directory) fs.File open for reading.
+type openMapFile struct {
+ path string
+ mapFileInfo
+ offset int64
+}
+
+func (f *openMapFile) Stat() (fs.FileInfo, error) { return &f.mapFileInfo, nil }
+
+func (f *openMapFile) Close() error { return nil }
+
+func (f *openMapFile) Read(b []byte) (int, error) {
+ if f.offset >= int64(len(f.f.Data)) {
+ return 0, io.EOF
+ }
+ if f.offset < 0 {
+ return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid}
+ }
+ n := copy(b, f.f.Data[f.offset:])
+ f.offset += int64(n)
+ return n, nil
+}
+
+func (f *openMapFile) Seek(offset int64, whence int) (int64, error) {
+ switch whence {
+ case 0:
+ // offset += 0
+ case 1:
+ offset += f.offset
+ case 2:
+ offset += int64(len(f.f.Data))
+ }
+ if offset < 0 || offset > int64(len(f.f.Data)) {
+ return 0, &fs.PathError{Op: "seek", Path: f.path, Err: fs.ErrInvalid}
+ }
+ f.offset = offset
+ return offset, nil
+}
+
+func (f *openMapFile) ReadAt(b []byte, offset int64) (int, error) {
+ if offset < 0 || offset > int64(len(f.f.Data)) {
+ return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid}
+ }
+ n := copy(b, f.f.Data[offset:])
+ if n < len(b) {
+ return n, io.EOF
+ }
+ return n, nil
+}
+
+// A mapDir is a directory fs.File (so also an fs.ReadDirFile) open for reading.
+type mapDir struct {
+ path string
+ mapFileInfo
+ entry []mapFileInfo
+ offset int
+}
+
+func (d *mapDir) Stat() (fs.FileInfo, error) { return &d.mapFileInfo, nil }
+func (d *mapDir) Close() error { return nil }
+func (d *mapDir) Read(b []byte) (int, error) {
+ return 0, &fs.PathError{Op: "read", Path: d.path, Err: fs.ErrInvalid}
+}
+
+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
+ }
+ list := make([]fs.DirEntry, n)
+ for i := range list {
+ list[i] = &d.entry[d.offset+i]
+ }
+ d.offset += n
+ return list, nil
+}
diff --git a/src/testing/fstest/mapfs_test.go b/src/testing/fstest/mapfs_test.go
new file mode 100644
index 0000000000..2abedd6735
--- /dev/null
+++ b/src/testing/fstest/mapfs_test.go
@@ -0,0 +1,19 @@
+// Copyright 2020 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.
+
+package fstest
+
+import (
+ "testing"
+)
+
+func TestMapFS(t *testing.T) {
+ m := MapFS{
+ "hello": {Data: []byte("hello, world\n")},
+ "fortune/k/ken.txt": {Data: []byte("If a program is too slow, it must have a loop.\n")},
+ }
+ if err := TestFS(m, "hello", "fortune/k/ken.txt"); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/src/testing/fstest/testfs.go b/src/testing/fstest/testfs.go
new file mode 100644
index 0000000000..2602bdf0cc
--- /dev/null
+++ b/src/testing/fstest/testfs.go
@@ -0,0 +1,602 @@
+// Copyright 2020 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.
+
+// Package fstest implements support for testing implementations and users of file systems.
+package fstest
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "io/fs"
+ "io/ioutil"
+ "path"
+ "reflect"
+ "sort"
+ "strings"
+ "testing/iotest"
+)
+
+// TestFS tests a file system implementation.
+// It walks the entire tree of files in fsys,
+// 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.
+//
+// If TestFS finds any misbehaviors, it returns an error reporting all of them.
+// The error text spans multiple lines, one per detected misbehavior.
+//
+// Typical usage inside a test is:
+//
+// if err := fstest.TestFS(myFS, "file/that/should/be/present"); err != nil {
+// t.Fatal(err)
+// }
+//
+func TestFS(fsys fs.FS, expected ...string) error {
+ if err := testFS(fsys, expected...); err != nil {
+ return err
+ }
+ for _, name := range expected {
+ if i := strings.Index(name, "/"); i >= 0 {
+ dir, dirSlash := name[:i], name[:i+1]
+ var subExpected []string
+ for _, name := range expected {
+ if strings.HasPrefix(name, dirSlash) {
+ subExpected = append(subExpected, name[len(dirSlash):])
+ }
+ }
+ sub, err := fs.Sub(fsys, dir)
+ if err != nil {
+ return err
+ }
+ if err := testFS(sub, subExpected...); err != nil {
+ return fmt.Errorf("testing fs.Sub(fsys, %s): %v", dir, err)
+ }
+ break // one sub-test is enough
+ }
+ }
+ return nil
+}
+
+func testFS(fsys fs.FS, expected ...string) error {
+ t := fsTester{fsys: fsys}
+ t.checkDir(".")
+ t.checkOpen(".")
+ found := make(map[string]bool)
+ for _, dir := range t.dirs {
+ found[dir] = true
+ }
+ for _, file := range t.files {
+ found[file] = true
+ }
+ delete(found, ".")
+ if len(expected) == 0 && len(found) > 0 {
+ var list []string
+ for k := range found {
+ if k != "." {
+ list = append(list, k)
+ }
+ }
+ sort.Strings(list)
+ if len(list) > 15 {
+ list = append(list[:10], "...")
+ }
+ t.errorf("expected empty file system but found files:\n%s", strings.Join(list, "\n"))
+ }
+ for _, name := range expected {
+ if !found[name] {
+ t.errorf("expected but not found: %s", name)
+ }
+ }
+ if len(t.errText) == 0 {
+ return nil
+ }
+ return errors.New("TestFS found errors:\n" + string(t.errText))
+}
+
+// An fsTester holds state for running the test.
+type fsTester struct {
+ fsys fs.FS
+ errText []byte
+ dirs []string
+ files []string
+}
+
+// errorf adds an error line to errText.
+func (t *fsTester) errorf(format string, args ...interface{}) {
+ if len(t.errText) > 0 {
+ t.errText = append(t.errText, '\n')
+ }
+ t.errText = append(t.errText, fmt.Sprintf(format, args...)...)
+}
+
+func (t *fsTester) openDir(dir string) fs.ReadDirFile {
+ f, err := t.fsys.Open(dir)
+ if err != nil {
+ t.errorf("%s: Open: %v", dir, err)
+ return nil
+ }
+ d, ok := f.(fs.ReadDirFile)
+ if !ok {
+ f.Close()
+ t.errorf("%s: Open returned File type %T, not a io.ReadDirFile", dir, f)
+ return nil
+ }
+ return d
+}
+
+// checkDir checks the directory dir, which is expected to exist
+// (it is either the root or was found in a directory listing with IsDir true).
+func (t *fsTester) checkDir(dir string) {
+ // Read entire directory.
+ t.dirs = append(t.dirs, dir)
+ d := t.openDir(dir)
+ if d == nil {
+ return
+ }
+ list, err := d.ReadDir(-1)
+ if err != nil {
+ d.Close()
+ t.errorf("%s: ReadDir(-1): %v", dir, err)
+ return
+ }
+
+ // Check all children.
+ var prefix string
+ if dir == "." {
+ prefix = ""
+ } else {
+ prefix = dir + "/"
+ }
+ for _, info := range list {
+ name := info.Name()
+ switch {
+ case name == ".", name == "..", name == "":
+ t.errorf("%s: ReadDir: child has invalid name: %#q", dir, name)
+ continue
+ case strings.Contains(name, "/"):
+ t.errorf("%s: ReadDir: child name contains slash: %#q", dir, name)
+ continue
+ case strings.Contains(name, `\`):
+ t.errorf("%s: ReadDir: child name contains backslash: %#q", dir, name)
+ continue
+ }
+ path := prefix + name
+ t.checkStat(path, info)
+ t.checkOpen(path)
+ if info.IsDir() {
+ t.checkDir(path)
+ } else {
+ t.checkFile(path)
+ }
+ }
+
+ // Check ReadDir(-1) at EOF.
+ list2, err := d.ReadDir(-1)
+ if len(list2) > 0 || err != nil {
+ d.Close()
+ t.errorf("%s: ReadDir(-1) at EOF = %d entries, %v, wanted 0 entries, nil", dir, len(list2), err)
+ return
+ }
+
+ // Check ReadDir(1) at EOF (different results).
+ list2, err = d.ReadDir(1)
+ if len(list2) > 0 || err != io.EOF {
+ d.Close()
+ t.errorf("%s: ReadDir(1) at EOF = %d entries, %v, wanted 0 entries, EOF", dir, len(list2), err)
+ return
+ }
+
+ // Check that close does not report an error.
+ if err := d.Close(); err != nil {
+ t.errorf("%s: Close: %v", dir, err)
+ }
+
+ // Check that closing twice doesn't crash.
+ // The return value doesn't matter.
+ d.Close()
+
+ // Reopen directory, read a second time, make sure contents match.
+ if d = t.openDir(dir); d == nil {
+ return
+ }
+ defer d.Close()
+ list2, err = d.ReadDir(-1)
+ if err != nil {
+ t.errorf("%s: second Open+ReadDir(-1): %v", dir, err)
+ return
+ }
+ t.checkDirList(dir, "first Open+ReadDir(-1) vs second Open+ReadDir(-1)", list, list2)
+
+ // Reopen directory, read a third time in pieces, make sure contents match.
+ if d = t.openDir(dir); d == nil {
+ return
+ }
+ defer d.Close()
+ list2 = nil
+ for {
+ n := 1
+ if len(list2) > 0 {
+ n = 2
+ }
+ frag, err := d.ReadDir(n)
+ if len(frag) > n {
+ t.errorf("%s: third Open: ReadDir(%d) after %d: %d entries (too many)", dir, n, len(list2), len(frag))
+ return
+ }
+ list2 = append(list2, frag...)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.errorf("%s: third Open: ReadDir(%d) after %d: %v", dir, n, len(list2), err)
+ return
+ }
+ if n == 0 {
+ t.errorf("%s: third Open: ReadDir(%d) after %d: 0 entries but nil error", dir, n, len(list2))
+ return
+ }
+ }
+ t.checkDirList(dir, "first Open+ReadDir(-1) vs third Open+ReadDir(1,2) loop", list, list2)
+
+ // If fsys has ReadDir, check that it matches and is sorted.
+ if fsys, ok := t.fsys.(fs.ReadDirFS); ok {
+ list2, err := fsys.ReadDir(dir)
+ if err != nil {
+ t.errorf("%s: fsys.ReadDir: %v", dir, err)
+ return
+ }
+ t.checkDirList(dir, "first Open+ReadDir(-1) vs fsys.ReadDir", list, list2)
+
+ for i := 0; i+1 < len(list2); i++ {
+ if list2[i].Name() >= list2[i+1].Name() {
+ t.errorf("%s: fsys.ReadDir: list not sorted: %s before %s", dir, list2[i].Name(), list2[i+1].Name())
+ }
+ }
+ }
+
+ // Check fs.ReadDir as well.
+ list2, err = fs.ReadDir(t.fsys, dir)
+ if err != nil {
+ t.errorf("%s: fs.ReadDir: %v", dir, err)
+ return
+ }
+ t.checkDirList(dir, "first Open+ReadDir(-1) vs fs.ReadDir", list, list2)
+
+ for i := 0; i+1 < len(list2); i++ {
+ if list2[i].Name() >= list2[i+1].Name() {
+ t.errorf("%s: fs.ReadDir: list not sorted: %s before %s", dir, list2[i].Name(), list2[i+1].Name())
+ }
+ }
+
+ t.checkGlob(dir, list)
+}
+
+// formatEntry formats an fs.DirEntry into a string for error messages and comparison.
+func formatEntry(entry fs.DirEntry) string {
+ return fmt.Sprintf("%s IsDir=%v Type=%v", entry.Name(), entry.IsDir(), entry.Type())
+}
+
+// formatInfoEntry formats an fs.FileInfo into a string like the result of formatEntry, for error messages and comparison.
+func formatInfoEntry(info fs.FileInfo) string {
+ return fmt.Sprintf("%s IsDir=%v Type=%v", info.Name(), info.IsDir(), info.Mode().Type())
+}
+
+// formatInfo formats an fs.FileInfo into a string for error messages and comparison.
+func formatInfo(info fs.FileInfo) string {
+ return fmt.Sprintf("%s IsDir=%v Mode=%v Size=%d ModTime=%v", info.Name(), info.IsDir(), info.Mode(), info.Size(), info.ModTime())
+}
+
+// checkGlob checks that various glob patterns work if the file system implements GlobFS.
+func (t *fsTester) checkGlob(dir string, list []fs.DirEntry) {
+ if _, ok := t.fsys.(fs.GlobFS); !ok {
+ return
+ }
+
+ // Make a complex glob pattern prefix that only matches dir.
+ var glob string
+ if dir != "." {
+ elem := strings.Split(dir, "/")
+ for i, e := range elem {
+ var pattern []rune
+ for j, r := range e {
+ if r == '*' || r == '?' || r == '\\' || r == '[' {
+ pattern = append(pattern, '\\', r)
+ continue
+ }
+ switch (i + j) % 5 {
+ case 0:
+ pattern = append(pattern, r)
+ case 1:
+ pattern = append(pattern, '[', r, ']')
+ case 2:
+ pattern = append(pattern, '[', r, '-', r, ']')
+ case 3:
+ pattern = append(pattern, '[', '\\', r, ']')
+ case 4:
+ pattern = append(pattern, '[', '\\', r, '-', '\\', r, ']')
+ }
+ }
+ elem[i] = string(pattern)
+ }
+ glob = strings.Join(elem, "/") + "/"
+ }
+
+ // Test that malformed patterns are detected.
+ // The error is likely path.ErrBadPattern but need not be.
+ if _, err := t.fsys.(fs.GlobFS).Glob(glob + "nonexist/[]"); err == nil {
+ t.errorf("%s: Glob(%#q): bad pattern not detected", dir, glob+"nonexist/[]")
+ }
+
+ // Try to find a letter that appears in only some of the final names.
+ c := rune('a')
+ for ; c <= 'z'; c++ {
+ have, haveNot := false, false
+ for _, d := range list {
+ if strings.ContainsRune(d.Name(), c) {
+ have = true
+ } else {
+ haveNot = true
+ }
+ }
+ if have && haveNot {
+ break
+ }
+ }
+ if c > 'z' {
+ c = 'a'
+ }
+ glob += "*" + string(c) + "*"
+
+ var want []string
+ for _, d := range list {
+ if strings.ContainsRune(d.Name(), c) {
+ want = append(want, path.Join(dir, d.Name()))
+ }
+ }
+
+ names, err := t.fsys.(fs.GlobFS).Glob(glob)
+ if err != nil {
+ t.errorf("%s: Glob(%#q): %v", dir, glob, err)
+ return
+ }
+ if reflect.DeepEqual(want, names) {
+ return
+ }
+
+ if !sort.StringsAreSorted(names) {
+ t.errorf("%s: Glob(%#q): unsorted output:\n%s", dir, glob, strings.Join(names, "\n"))
+ sort.Strings(names)
+ }
+
+ var problems []string
+ for len(want) > 0 || len(names) > 0 {
+ switch {
+ case len(want) > 0 && len(names) > 0 && want[0] == names[0]:
+ want, names = want[1:], names[1:]
+ case len(want) > 0 && (len(names) == 0 || want[0] < names[0]):
+ problems = append(problems, "missing: "+want[0])
+ want = want[1:]
+ default:
+ problems = append(problems, "extra: "+names[0])
+ names = names[1:]
+ }
+ }
+ t.errorf("%s: Glob(%#q): wrong output:\n%s", dir, glob, strings.Join(problems, "\n"))
+}
+
+// checkStat checks that a direct stat of path matches entry,
+// which was found in the parent's directory listing.
+func (t *fsTester) checkStat(path string, entry fs.DirEntry) {
+ file, err := t.fsys.Open(path)
+ if err != nil {
+ t.errorf("%s: Open: %v", path, err)
+ return
+ }
+ info, err := file.Stat()
+ file.Close()
+ if err != nil {
+ t.errorf("%s: Stat: %v", path, err)
+ return
+ }
+ fentry := formatEntry(entry)
+ finfo := formatInfoEntry(info)
+ if fentry != finfo {
+ t.errorf("%s: mismatch:\n\tentry = %s\n\tfile.Stat() = %s", path, fentry, finfo)
+ }
+
+ einfo, err := entry.Info()
+ if err != nil {
+ t.errorf("%s: entry.Info: %v", path, err)
+ return
+ }
+ fentry = formatInfo(einfo)
+ finfo = formatInfo(info)
+ if fentry != finfo {
+ t.errorf("%s: mismatch:\n\tentry.Info() = %s\n\tfile.Stat() = %s\n", path, fentry, finfo)
+ }
+
+ info2, err := fs.Stat(t.fsys, path)
+ if err != nil {
+ t.errorf("%s: fs.Stat: %v", path, err)
+ return
+ }
+ finfo2 := formatInfo(info2)
+ if finfo2 != finfo {
+ t.errorf("%s: fs.Stat(...) = %s\n\twant %s", path, finfo2, finfo)
+ }
+
+ if fsys, ok := t.fsys.(fs.StatFS); ok {
+ info2, err := fsys.Stat(path)
+ if err != nil {
+ t.errorf("%s: fsys.Stat: %v", path, err)
+ return
+ }
+ finfo2 := formatInfo(info2)
+ if finfo2 != finfo {
+ t.errorf("%s: fsys.Stat(...) = %s\n\twant %s", path, finfo2, finfo)
+ }
+ }
+}
+
+// checkDirList checks that two directory lists contain the same files and file info.
+// The order of the lists need not match.
+func (t *fsTester) checkDirList(dir, desc string, list1, list2 []fs.DirEntry) {
+ old := make(map[string]fs.DirEntry)
+ checkMode := func(entry fs.DirEntry) {
+ if entry.IsDir() != (entry.Type()&fs.ModeDir != 0) {
+ if entry.IsDir() {
+ t.errorf("%s: ReadDir returned %s with IsDir() = true, Type() & ModeDir = 0", dir, entry.Name())
+ } else {
+ t.errorf("%s: ReadDir returned %s with IsDir() = false, Type() & ModeDir = ModeDir", dir, entry.Name())
+ }
+ }
+ }
+
+ for _, entry1 := range list1 {
+ old[entry1.Name()] = entry1
+ checkMode(entry1)
+ }
+
+ var diffs []string
+ for _, entry2 := range list2 {
+ entry1 := old[entry2.Name()]
+ if entry1 == nil {
+ checkMode(entry2)
+ diffs = append(diffs, "+ "+formatEntry(entry2))
+ continue
+ }
+ if formatEntry(entry1) != formatEntry(entry2) {
+ diffs = append(diffs, "- "+formatEntry(entry1), "+ "+formatEntry(entry2))
+ }
+ delete(old, entry2.Name())
+ }
+ for _, entry1 := range old {
+ diffs = append(diffs, "- "+formatEntry(entry1))
+ }
+
+ if len(diffs) == 0 {
+ return
+ }
+
+ sort.Slice(diffs, func(i, j int) bool {
+ fi := strings.Fields(diffs[i])
+ fj := strings.Fields(diffs[j])
+ // sort by name (i < j) and then +/- (j < i, because + < -)
+ return fi[1]+" "+fj[0] < fj[1]+" "+fi[0]
+ })
+
+ t.errorf("%s: diff %s:\n\t%s", dir, desc, strings.Join(diffs, "\n\t"))
+}
+
+// checkFile checks that basic file reading works correctly.
+func (t *fsTester) checkFile(file string) {
+ t.files = append(t.files, file)
+
+ // Read entire file.
+ f, err := t.fsys.Open(file)
+ if err != nil {
+ t.errorf("%s: Open: %v", file, err)
+ return
+ }
+
+ data, err := ioutil.ReadAll(f)
+ if err != nil {
+ f.Close()
+ t.errorf("%s: Open+ReadAll: %v", file, err)
+ return
+ }
+
+ if err := f.Close(); err != nil {
+ t.errorf("%s: Close: %v", file, err)
+ }
+
+ // Check that closing twice doesn't crash.
+ // The return value doesn't matter.
+ f.Close()
+
+ // Check that ReadFile works if present.
+ if fsys, ok := t.fsys.(fs.ReadFileFS); ok {
+ data2, err := fsys.ReadFile(file)
+ if err != nil {
+ t.errorf("%s: fsys.ReadFile: %v", file, err)
+ return
+ }
+ t.checkFileRead(file, "ReadAll vs fsys.ReadFile", data, data2)
+
+ t.checkBadPath(file, "ReadFile",
+ func(name string) error { _, err := fsys.ReadFile(name); return err })
+ }
+
+ // Check that fs.ReadFile works with t.fsys.
+ data2, err := fs.ReadFile(t.fsys, file)
+ if err != nil {
+ t.errorf("%s: fs.ReadFile: %v", file, err)
+ return
+ }
+ t.checkFileRead(file, "ReadAll vs fs.ReadFile", data, data2)
+
+ // Use iotest.TestReader to check small reads, Seek, ReadAt.
+ f, err = t.fsys.Open(file)
+ if err != nil {
+ t.errorf("%s: second Open: %v", file, err)
+ return
+ }
+ defer f.Close()
+ if err := iotest.TestReader(f, data); err != nil {
+ t.errorf("%s: failed TestReader:\n\t%s", file, strings.ReplaceAll(err.Error(), "\n", "\n\t"))
+ }
+}
+
+func (t *fsTester) checkFileRead(file, desc string, data1, data2 []byte) {
+ if string(data1) != string(data2) {
+ t.errorf("%s: %s: different data returned\n\t%q\n\t%q", file, desc, data1, data2)
+ return
+ }
+}
+
+// checkBadPath checks that various invalid forms of file's name cannot be opened using t.fsys.Open.
+func (t *fsTester) checkOpen(file string) {
+ t.checkBadPath(file, "Open", func(file string) error {
+ f, err := t.fsys.Open(file)
+ if err == nil {
+ f.Close()
+ }
+ return err
+ })
+}
+
+// checkBadPath checks that various invalid forms of file's name cannot be opened using open.
+func (t *fsTester) checkBadPath(file string, desc string, open func(string) error) {
+ bad := []string{
+ "/" + file,
+ file + "/.",
+ }
+ if file == "." {
+ bad = append(bad, "/")
+ }
+ if i := strings.Index(file, "/"); i >= 0 {
+ bad = append(bad,
+ file[:i]+"//"+file[i+1:],
+ file[:i]+"/./"+file[i+1:],
+ file[:i]+`\`+file[i+1:],
+ file[:i]+"/../"+file,
+ )
+ }
+ if i := strings.LastIndex(file, "/"); i >= 0 {
+ bad = append(bad,
+ file[:i]+"//"+file[i+1:],
+ file[:i]+"/./"+file[i+1:],
+ file[:i]+`\`+file[i+1:],
+ file+"/../"+file[i+1:],
+ )
+ }
+
+ for _, b := range bad {
+ if err := open(b); err == nil {
+ t.errorf("%s: %s(%s) succeeded, want error", file, desc, b)
+ }
+ }
+}
diff --git a/src/testing/helper_test.go b/src/testing/helper_test.go
index 7ce58c67fb..8858196cf0 100644
--- a/src/testing/helper_test.go
+++ b/src/testing/helper_test.go
@@ -70,3 +70,34 @@ func TestTBHelperParallel(t *T) {
t.Errorf("got output line %q; want %q", got, want)
}
}
+
+type noopWriter int
+
+func (nw *noopWriter) Write(b []byte) (int, error) { return len(b), nil }
+
+func BenchmarkTBHelper(b *B) {
+ w := noopWriter(0)
+ ctx := newTestContext(1, newMatcher(regexp.MatchString, "", ""))
+ t1 := &T{
+ common: common{
+ signal: make(chan bool),
+ w: &w,
+ },
+ context: ctx,
+ }
+ f1 := func() {
+ t1.Helper()
+ }
+ f2 := func() {
+ t1.Helper()
+ }
+ b.ResetTimer()
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ if i&1 == 0 {
+ f1()
+ } else {
+ f2()
+ }
+ }
+}
diff --git a/src/testing/iotest/reader.go b/src/testing/iotest/reader.go
index bc2f72a911..770d87f26b 100644
--- a/src/testing/iotest/reader.go
+++ b/src/testing/iotest/reader.go
@@ -6,7 +6,9 @@
package iotest
import (
+ "bytes"
"errors"
+ "fmt"
"io"
)
@@ -90,13 +92,177 @@ func (r *timeoutReader) Read(p []byte) (int, error) {
// ErrReader returns an io.Reader that returns 0, err from all Read calls.
func ErrReader(err error) io.Reader {
- return &alwaysErrReader{err: err}
+ return &errReader{err: err}
}
-type alwaysErrReader struct {
+type errReader struct {
err error
}
-func (aer *alwaysErrReader) Read(p []byte) (int, error) {
- return 0, aer.err
+func (r *errReader) Read(p []byte) (int, error) {
+ return 0, r.err
+}
+
+type smallByteReader struct {
+ r io.Reader
+ off int
+ n int
+}
+
+func (r *smallByteReader) Read(p []byte) (int, error) {
+ if len(p) == 0 {
+ return 0, nil
+ }
+ r.n = r.n%3 + 1
+ n := r.n
+ if n > len(p) {
+ n = len(p)
+ }
+ n, err := r.r.Read(p[0:n])
+ if err != nil && err != io.EOF {
+ err = fmt.Errorf("Read(%d bytes at offset %d): %v", n, r.off, err)
+ }
+ r.off += n
+ return n, err
+}
+
+// TestReader tests that reading from r returns the expected file content.
+// It does reads of different sizes, until EOF.
+// If r implements io.ReaderAt or io.Seeker, TestReader also checks
+// that those operations behave as they should.
+//
+// If TestReader finds any misbehaviors, it returns an error reporting them.
+// The error text may span multiple lines.
+func TestReader(r io.Reader, content []byte) error {
+ if len(content) > 0 {
+ n, err := r.Read(nil)
+ if n != 0 || err != nil {
+ return fmt.Errorf("Read(0) = %d, %v, want 0, nil", n, err)
+ }
+ }
+
+ data, err := io.ReadAll(&smallByteReader{r: r})
+ if err != nil {
+ return err
+ }
+ if !bytes.Equal(data, content) {
+ return fmt.Errorf("ReadAll(small amounts) = %q\n\twant %q", data, content)
+ }
+ n, err := r.Read(make([]byte, 10))
+ if n != 0 || err != io.EOF {
+ return fmt.Errorf("Read(10) at EOF = %v, %v, want 0, EOF", n, err)
+ }
+
+ if r, ok := r.(io.ReadSeeker); ok {
+ // Seek(0, 1) should report the current file position (EOF).
+ if off, err := r.Seek(0, 1); off != int64(len(content)) || err != nil {
+ return fmt.Errorf("Seek(0, 1) from EOF = %d, %v, want %d, nil", off, err, len(content))
+ }
+
+ // Seek backward partway through file, in two steps.
+ // If middle == 0, len(content) == 0, can't use the -1 and +1 seeks.
+ middle := len(content) - len(content)/3
+ if middle > 0 {
+ if off, err := r.Seek(-1, 1); off != int64(len(content)-1) || err != nil {
+ return fmt.Errorf("Seek(-1, 1) from EOF = %d, %v, want %d, nil", -off, err, len(content)-1)
+ }
+ if off, err := r.Seek(int64(-len(content)/3), 1); off != int64(middle-1) || err != nil {
+ return fmt.Errorf("Seek(%d, 1) from %d = %d, %v, want %d, nil", -len(content)/3, len(content)-1, off, err, middle-1)
+ }
+ if off, err := r.Seek(+1, 1); off != int64(middle) || err != nil {
+ return fmt.Errorf("Seek(+1, 1) from %d = %d, %v, want %d, nil", middle-1, off, err, middle)
+ }
+ }
+
+ // Seek(0, 1) should report the current file position (middle).
+ if off, err := r.Seek(0, 1); off != int64(middle) || err != nil {
+ return fmt.Errorf("Seek(0, 1) from %d = %d, %v, want %d, nil", middle, off, err, middle)
+ }
+
+ // Reading forward should return the last part of the file.
+ data, err := io.ReadAll(&smallByteReader{r: r})
+ if err != nil {
+ return fmt.Errorf("ReadAll from offset %d: %v", middle, err)
+ }
+ if !bytes.Equal(data, content[middle:]) {
+ return fmt.Errorf("ReadAll from offset %d = %q\n\twant %q", middle, data, content[middle:])
+ }
+
+ // Seek relative to end of file, but start elsewhere.
+ if off, err := r.Seek(int64(middle/2), 0); off != int64(middle/2) || err != nil {
+ return fmt.Errorf("Seek(%d, 0) from EOF = %d, %v, want %d, nil", middle/2, off, err, middle/2)
+ }
+ if off, err := r.Seek(int64(-len(content)/3), 2); off != int64(middle) || err != nil {
+ return fmt.Errorf("Seek(%d, 2) from %d = %d, %v, want %d, nil", -len(content)/3, middle/2, off, err, middle)
+ }
+
+ // Reading forward should return the last part of the file (again).
+ data, err = io.ReadAll(&smallByteReader{r: r})
+ if err != nil {
+ return fmt.Errorf("ReadAll from offset %d: %v", middle, err)
+ }
+ if !bytes.Equal(data, content[middle:]) {
+ return fmt.Errorf("ReadAll from offset %d = %q\n\twant %q", middle, data, content[middle:])
+ }
+
+ // Absolute seek & read forward.
+ if off, err := r.Seek(int64(middle/2), 0); off != int64(middle/2) || err != nil {
+ return fmt.Errorf("Seek(%d, 0) from EOF = %d, %v, want %d, nil", middle/2, off, err, middle/2)
+ }
+ data, err = io.ReadAll(r)
+ if err != nil {
+ return fmt.Errorf("ReadAll from offset %d: %v", middle/2, err)
+ }
+ if !bytes.Equal(data, content[middle/2:]) {
+ return fmt.Errorf("ReadAll from offset %d = %q\n\twant %q", middle/2, data, content[middle/2:])
+ }
+ }
+
+ if r, ok := r.(io.ReaderAt); ok {
+ data := make([]byte, len(content), len(content)+1)
+ for i := range data {
+ data[i] = 0xfe
+ }
+ n, err := r.ReadAt(data, 0)
+ if n != len(data) || err != nil && err != io.EOF {
+ return fmt.Errorf("ReadAt(%d, 0) = %v, %v, want %d, nil or EOF", len(data), n, err, len(data))
+ }
+ if !bytes.Equal(data, content) {
+ return fmt.Errorf("ReadAt(%d, 0) = %q\n\twant %q", len(data), data, content)
+ }
+
+ n, err = r.ReadAt(data[:1], int64(len(data)))
+ if n != 0 || err != io.EOF {
+ return fmt.Errorf("ReadAt(1, %d) = %v, %v, want 0, EOF", len(data), n, err)
+ }
+
+ for i := range data {
+ data[i] = 0xfe
+ }
+ n, err = r.ReadAt(data[:cap(data)], 0)
+ if n != len(data) || err != io.EOF {
+ return fmt.Errorf("ReadAt(%d, 0) = %v, %v, want %d, EOF", cap(data), n, err, len(data))
+ }
+ if !bytes.Equal(data, content) {
+ return fmt.Errorf("ReadAt(%d, 0) = %q\n\twant %q", len(data), data, content)
+ }
+
+ for i := range data {
+ data[i] = 0xfe
+ }
+ for i := range data {
+ n, err = r.ReadAt(data[i:i+1], int64(i))
+ if n != 1 || err != nil && (i != len(data)-1 || err != io.EOF) {
+ want := "nil"
+ if i == len(data)-1 {
+ want = "nil or EOF"
+ }
+ return fmt.Errorf("ReadAt(1, %d) = %v, %v, want 1, %s", i, n, err, want)
+ }
+ if data[i] != content[i] {
+ return fmt.Errorf("ReadAt(1, %d) = %q want %q", i, data[i:i+1], content[i:i+1])
+ }
+ }
+ }
+ return nil
}
diff --git a/src/testing/iotest/reader_test.go b/src/testing/iotest/reader_test.go
index 6004e841e5..f149e74c74 100644
--- a/src/testing/iotest/reader_test.go
+++ b/src/testing/iotest/reader_test.go
@@ -8,6 +8,7 @@ import (
"bytes"
"errors"
"io"
+ "strings"
"testing"
)
@@ -249,3 +250,12 @@ func TestErrReader(t *testing.T) {
})
}
}
+
+func TestStringsReader(t *testing.T) {
+ const msg = "Now is the time for all good gophers."
+
+ r := strings.NewReader(msg)
+ if err := TestReader(r, []byte(msg)); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/src/testing/testing.go b/src/testing/testing.go
index 6267abfcdf..c87e0a5b9a 100644
--- a/src/testing/testing.go
+++ b/src/testing/testing.go
@@ -242,7 +242,6 @@ import (
"fmt"
"internal/race"
"io"
- "io/ioutil"
"os"
"runtime"
"runtime/debug"
@@ -385,17 +384,18 @@ const maxStackLen = 50
// common holds the elements common between T and B and
// captures common methods such as Errorf.
type common struct {
- mu sync.RWMutex // guards this group of fields
- output []byte // Output generated by test or benchmark.
- 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.
- done bool // Test is finished and all subtests have completed.
- helpers map[string]struct{} // functions to be skipped when writing file/line info
- 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.
+ mu sync.RWMutex // guards this group of fields
+ output []byte // Output generated by test or benchmark.
+ 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.
+ 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.
chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
bench bool // Whether the current test is a benchmark.
@@ -511,7 +511,7 @@ func (c *common) frameSkip(skip int) runtime.Frame {
}
return prevFrame
}
- if _, ok := c.helpers[frame.Function]; !ok {
+ if _, ok := c.helperNames[frame.Function]; !ok {
// Found a frame that wasn't inside a helper function.
return frame
}
@@ -523,6 +523,14 @@ func (c *common) frameSkip(skip int) runtime.Frame {
// and inserts the final newline if needed and indentation spaces for formatting.
// This function must be called with c.mu held.
func (c *common) decorate(s string, skip int) string {
+ // If more helper PCs have been added since we last did the conversion
+ if c.helperNames == nil {
+ c.helperNames = make(map[string]struct{})
+ for pc := range c.helperPCs {
+ c.helperNames[pcToName(pc)] = struct{}{}
+ }
+ }
+
frame := c.frameSkip(skip)
file := frame.File
line := frame.Line
@@ -885,10 +893,19 @@ func (c *common) Helper() {
}
c.mu.Lock()
defer c.mu.Unlock()
- if c.helpers == nil {
- c.helpers = make(map[string]struct{})
+ if c.helperPCs == nil {
+ c.helperPCs = make(map[uintptr]struct{})
+ }
+ // repeating code from callerName here to save walking a stack frame
+ var pc [1]uintptr
+ n := runtime.Callers(2, pc[:]) // skip runtime.Callers + Helper
+ if n == 0 {
+ panic("testing: zero callers found")
+ }
+ if _, found := c.helperPCs[pc[0]]; !found {
+ c.helperPCs[pc[0]] = struct{}{}
+ c.helperNames = nil // map will be recreated next time it is needed
}
- c.helpers[callerName(1)] = struct{}{}
}
// Cleanup registers a function to be called when the test and all its
@@ -956,14 +973,14 @@ func (c *common) TempDir() string {
if nonExistent {
c.Helper()
- // ioutil.TempDir doesn't like path separators in its pattern,
+ // 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())
- c.tempDir, c.tempDirErr = ioutil.TempDir("", pattern)
+ c.tempDir, c.tempDirErr = os.MkdirTemp("", pattern)
if c.tempDirErr == nil {
c.Cleanup(func() {
if err := os.RemoveAll(c.tempDir); err != nil {
@@ -1033,13 +1050,17 @@ func (c *common) runCleanup(ph panicHandling) (panicVal interface{}) {
// callerName gives the function name (qualified with a package path)
// for the caller after skip frames (where 0 means the current function).
func callerName(skip int) string {
- // Make room for the skip PC.
var pc [1]uintptr
n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName
if n == 0 {
panic("testing: zero callers found")
}
- frames := runtime.CallersFrames(pc[:n])
+ return pcToName(pc[0])
+}
+
+func pcToName(pc uintptr) string {
+ pcs := []uintptr{pc}
+ frames := runtime.CallersFrames(pcs)
frame, _ := frames.Next()
return frame.Function
}
@@ -1113,6 +1134,7 @@ func tRunner(t *T, fn func(t *T)) {
// If the test panicked, print any test output before dying.
err := recover()
signal := true
+
if !t.finished && err == nil {
err = errNilPanicOrGoexit
for p := t.parent; p != nil; p = p.parent {
@@ -1124,6 +1146,21 @@ func tRunner(t *T, fn func(t *T)) {
}
}
}
+ // Use a deferred call to ensure that we report that the test is
+ // complete even if a cleanup function calls t.FailNow. See issue 41355.
+ didPanic := false
+ defer func() {
+ if didPanic {
+ return
+ }
+ if err != nil {
+ panic(err)
+ }
+ // Only report that the test is complete if it doesn't panic,
+ // as otherwise the test binary can exit before the panic is
+ // reported to the user. See issue 41479.
+ t.signal <- signal
+ }()
doPanic := func(err interface{}) {
t.Fail()
@@ -1141,6 +1178,7 @@ func tRunner(t *T, fn func(t *T)) {
fmt.Fprintf(root.parent.w, "cleanup panicked with %v", r)
}
}
+ didPanic = true
panic(err)
}
if err != nil {
@@ -1182,7 +1220,6 @@ func tRunner(t *T, fn func(t *T)) {
if t.parent != nil && atomic.LoadInt32(&t.hasSub) == 0 {
t.setRan()
}
- t.signal <- signal
}()
defer func() {
if len(t.sub) == 0 {
diff --git a/src/testing/testing_test.go b/src/testing/testing_test.go
index d665a334e4..0f096980ca 100644
--- a/src/testing/testing_test.go
+++ b/src/testing/testing_test.go
@@ -5,7 +5,6 @@
package testing_test
import (
- "io/ioutil"
"os"
"path/filepath"
"testing"
@@ -102,11 +101,11 @@ func testTempDir(t *testing.T) {
if !fi.IsDir() {
t.Errorf("dir %q is not a dir", dir)
}
- fis, err := ioutil.ReadDir(dir)
+ files, err := os.ReadDir(dir)
if err != nil {
t.Fatal(err)
}
- if len(fis) > 0 {
- t.Errorf("unexpected %d files in TempDir: %v", len(fis), fis)
+ if len(files) > 0 {
+ t.Errorf("unexpected %d files in TempDir: %v", len(files), files)
}
}