Merge pull request #174 from colincross/glob

Fix recursive glob on MockFs
This commit is contained in:
colincross 2017-10-05 16:09:08 -07:00 committed by GitHub
commit afa12b4744
3 changed files with 123 additions and 53 deletions

View file

@ -22,6 +22,8 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strings"
) )
// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go" // Based on Andrew Gerrand's "10 things you (probably) dont' know about Go"
@ -53,6 +55,8 @@ func MockFs(files map[string][]byte) FileSystem {
fs.all = append(fs.all, d) fs.all = append(fs.all, d)
} }
sort.Strings(fs.all)
return fs return fs
} }
@ -63,6 +67,7 @@ type FileSystem interface {
glob(pattern string) (matches []string, err error) glob(pattern string) (matches []string, err error)
IsDir(name string) (bool, error) IsDir(name string) (bool, error)
Lstat(name string) (os.FileInfo, error) Lstat(name string) (os.FileInfo, error)
ListDirsRecursive(name string) (dirs []string, err error)
} }
// osFs implements FileSystem using the local disk. // osFs implements FileSystem using the local disk.
@ -100,6 +105,27 @@ func (osFs) Lstat(path string) (stats os.FileInfo, err error) {
return os.Lstat(path) return os.Lstat(path)
} }
// Returns a list of all directories under dir
func (osFs) ListDirsRecursive(name string) (dirs []string, err error) {
err = filepath.Walk(name, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Mode().IsDir() {
name := info.Name()
if name[0] == '.' && name != "." {
return filepath.SkipDir
}
dirs = append(dirs, path)
}
return nil
})
return dirs, err
}
type mockFs struct { type mockFs struct {
files map[string][]byte files map[string][]byte
dirs map[string]bool dirs map[string]bool
@ -150,6 +176,10 @@ func (m *mockFs) glob(pattern string) ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if f == "." && f != pattern {
// filepath.Glob won't return "." unless the pattern was "."
match = false
}
if match { if match {
matches = append(matches, f) matches = append(matches, f)
} }
@ -160,3 +190,23 @@ func (m *mockFs) glob(pattern string) ([]string, error) {
func (m *mockFs) Lstat(path string) (stats os.FileInfo, err error) { func (m *mockFs) Lstat(path string) (stats os.FileInfo, err error) {
return nil, errors.New("Lstat is not yet implemented in MockFs") return nil, errors.New("Lstat is not yet implemented in MockFs")
} }
func (m *mockFs) ListDirsRecursive(name string) (dirs []string, err error) {
name = filepath.Clean(name)
dirs = append(dirs, name)
if name == "." {
name = ""
} else if name != "/" {
name = name + "/"
}
for _, f := range m.all {
if _, isDir := m.dirs[f]; isDir && filepath.Base(f)[0] != '.' {
if strings.HasPrefix(f, name) &&
strings.HasPrefix(f, "/") == strings.HasPrefix(name, "/") {
dirs = append(dirs, f)
}
}
}
return dirs, nil
}

View file

@ -121,7 +121,7 @@ func glob(fs FileSystem, pattern string, hasRecursive bool) (matches, dirs []str
return nil, nil, fmt.Errorf("unexpected error after glob: %s", err) return nil, nil, fmt.Errorf("unexpected error after glob: %s", err)
} else if isDir { } else if isDir {
if file == "**" { if file == "**" {
recurseDirs, err := walkAllDirs(m) recurseDirs, err := fs.ListDirsRecursive(m)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -166,27 +166,6 @@ func isWild(pattern string) bool {
return strings.ContainsAny(pattern, "*?[") return strings.ContainsAny(pattern, "*?[")
} }
// Returns a list of all directories under dir
func walkAllDirs(dir string) (dirs []string, err error) {
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Mode().IsDir() {
name := info.Name()
if name[0] == '.' && name != "." {
return filepath.SkipDir
}
dirs = append(dirs, path)
}
return nil
})
return dirs, err
}
// Filters the strings in matches based on the glob patterns in excludes. Hierarchical (a/*) and // Filters the strings in matches based on the glob patterns in excludes. Hierarchical (a/*) and
// recursive (**) glob patterns are supported. // recursive (**) glob patterns are supported.
func filterExcludes(matches []string, excludes []string) ([]string, error) { func filterExcludes(matches []string, excludes []string) ([]string, error) {

View file

@ -18,18 +18,21 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strconv"
"testing" "testing"
) )
var pwd, _ = os.Getwd() var pwd, _ = os.Getwd()
var globTestCases = []struct { type globTestCase struct {
pattern string pattern string
matches []string matches []string
excludes []string excludes []string
deps []string deps []string
err error err error
}{ }
var globTestCases = []globTestCase{
// Current directory tests // Current directory tests
{ {
pattern: "*", pattern: "*",
@ -441,37 +444,75 @@ var globTestCases = []struct {
}, },
} }
func TestMockGlob(t *testing.T) {
files := []string{
"a/a/a",
"a/b/b",
"b/a",
"c/c",
"c/f/f.ext",
"c/g/g.ext",
"c/h/h",
"d.ext",
"e.ext",
".test/a",
".testing",
".test/.ing",
}
mockFiles := make(map[string][]byte)
for _, f := range files {
mockFiles[f] = nil
mockFiles[filepath.Join(pwd, "testdata", f)] = nil
}
mock := MockFs(mockFiles)
for i, testCase := range globTestCases {
t.Run(strconv.Itoa(i), func(t *testing.T) {
testGlob(t, mock, testCase)
})
}
}
func TestGlob(t *testing.T) { func TestGlob(t *testing.T) {
os.Chdir("testdata") os.Chdir("testdata")
defer os.Chdir("..") defer os.Chdir("..")
for _, testCase := range globTestCases { for i, testCase := range globTestCases {
matches, deps, err := Glob(testCase.pattern, testCase.excludes) t.Run(strconv.Itoa(i), func(t *testing.T) {
if err != testCase.err { testGlob(t, OsFs, testCase)
t.Errorf(" pattern: %q", testCase.pattern) })
if testCase.excludes != nil { }
t.Errorf("excludes: %q", testCase.excludes) }
}
t.Errorf(" error: %s", err) func testGlob(t *testing.T, fs FileSystem, testCase globTestCase) {
continue matches, deps, err := fs.Glob(testCase.pattern, testCase.excludes)
} if err != testCase.err {
t.Errorf(" pattern: %q", testCase.pattern)
if !reflect.DeepEqual(matches, testCase.matches) { if testCase.excludes != nil {
t.Errorf("incorrect matches list:") t.Errorf("excludes: %q", testCase.excludes)
t.Errorf(" pattern: %q", testCase.pattern) }
if testCase.excludes != nil { t.Errorf(" error: %s", err)
t.Errorf("excludes: %q", testCase.excludes) return
} }
t.Errorf(" got: %#v", matches)
t.Errorf("expected: %#v", testCase.matches) if !reflect.DeepEqual(matches, testCase.matches) {
} t.Errorf("incorrect matches list:")
if !reflect.DeepEqual(deps, testCase.deps) { t.Errorf(" pattern: %q", testCase.pattern)
t.Errorf("incorrect deps list:") if testCase.excludes != nil {
t.Errorf(" pattern: %q", testCase.pattern) t.Errorf("excludes: %q", testCase.excludes)
if testCase.excludes != nil { }
t.Errorf("excludes: %q", testCase.excludes) t.Errorf(" got: %#v", matches)
} t.Errorf("expected: %#v", testCase.matches)
t.Errorf(" got: %#v", deps) }
t.Errorf("expected: %#v", testCase.deps) if !reflect.DeepEqual(deps, testCase.deps) {
} t.Errorf("incorrect deps list:")
t.Errorf(" pattern: %q", testCase.pattern)
if testCase.excludes != nil {
t.Errorf("excludes: %q", testCase.excludes)
}
t.Errorf(" got: %#v", deps)
t.Errorf("expected: %#v", testCase.deps)
} }
} }