diff options
author | Russ Cox <rsc@golang.org> | 2020-07-06 11:27:12 -0400 |
---|---|---|
committer | Russ Cox <rsc@golang.org> | 2020-10-20 17:53:14 +0000 |
commit | b64202bc29b9c1cf0118878d1c0acc9cdb2308f6 (patch) | |
tree | 6c632cefefa5dbb996d04f57e7a6e2282ffae4bb /src/testing | |
parent | 7a131acfd142f0fc7612365078b9f00e371fc0e2 (diff) | |
download | go-b64202bc29b9c1cf0118878d1c0acc9cdb2308f6.tar.gz go-b64202bc29b9c1cf0118878d1c0acc9cdb2308f6.zip |
io/fs: add Glob and GlobFS
Add Glob helper function, GlobFS interface, and test.
Add Glob method to fstest.MapFS.
Add testing of Glob method to fstest.TestFS.
For #41190.
Change-Id: If89dd7f63e310ba5ca2651340267a9ff39fcc0c7
Reviewed-on: https://go-review.googlesource.com/c/go/+/243915
Trust: Russ Cox <rsc@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
Diffstat (limited to 'src/testing')
-rw-r--r-- | src/testing/fstest/mapfs.go | 4 | ||||
-rw-r--r-- | src/testing/fstest/testfs.go | 96 |
2 files changed, 100 insertions, 0 deletions
diff --git a/src/testing/fstest/mapfs.go b/src/testing/fstest/mapfs.go index 1eaf8f0040..10e56f5b3c 100644 --- a/src/testing/fstest/mapfs.go +++ b/src/testing/fstest/mapfs.go @@ -128,6 +128,10 @@ 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) +} + // A mapFileInfo implements fs.FileInfo and fs.DirEntry for a given map file. type mapFileInfo struct { name string diff --git a/src/testing/fstest/testfs.go b/src/testing/fstest/testfs.go index 4ea6ed6095..21cd00e5b6 100644 --- a/src/testing/fstest/testfs.go +++ b/src/testing/fstest/testfs.go @@ -11,6 +11,8 @@ import ( "io" "io/fs" "io/ioutil" + "path" + "reflect" "sort" "strings" "testing/iotest" @@ -226,6 +228,8 @@ func (t *fsTester) checkDir(dir string) { 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. @@ -243,6 +247,98 @@ 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, "/") + "/" + } + + // 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) { |