Skip to content

Commit

Permalink
Merge pull request #17 from tjamet/feat/walk
Browse files Browse the repository at this point in the history
utils: add Walk function to walk over a filesystem
  • Loading branch information
mcuadros authored Aug 4, 2021
2 parents 3bf3fe5 + 213e20d commit 7ab80d7
Show file tree
Hide file tree
Showing 2 changed files with 266 additions and 0 deletions.
72 changes: 72 additions & 0 deletions util/walk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package util

import (
"os"
"path/filepath"

"github.com/go-git/go-billy/v5"
)

// walk recursively descends path, calling walkFn
// adapted from https://golang.org/src/path/filepath/path.go
func walk(fs billy.Filesystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
if !info.IsDir() {
return walkFn(path, info, nil)
}

names, err := readdirnames(fs, path)
err1 := walkFn(path, info, err)
// If err != nil, walk can't walk into this directory.
// err1 != nil means walkFn want walk to skip this directory or stop walking.
// Therefore, if one of err and err1 isn't nil, walk will return.
if err != nil || err1 != nil {
// The caller's behavior is controlled by the return value, which is decided
// by walkFn. walkFn may ignore err and return nil.
// If walkFn returns SkipDir, it will be handled by the caller.
// So walk should return whatever walkFn returns.
return err1
}

for _, name := range names {
filename := filepath.Join(path, name)
fileInfo, err := fs.Lstat(filename)
if err != nil {
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
return err
}
} else {
err = walk(fs, filename, fileInfo, walkFn)
if err != nil {
if !fileInfo.IsDir() || err != filepath.SkipDir {
return err
}
}
}
}
return nil
}

// Walk walks the file tree rooted at root, calling fn for each file or
// directory in the tree, including root. All errors that arise visiting files
// and directories are filtered by fn: see the WalkFunc documentation for
// details.
//
// The files are walked in lexical order, which makes the output deterministic
// but requires Walk to read an entire directory into memory before proceeding
// to walk that directory. Walk does not follow symbolic links.
//
// Function adapted from https://github.com/golang/go/blob/3b770f2ccb1fa6fecc22ea822a19447b10b70c5c/src/path/filepath/path.go#L500
func Walk(fs billy.Filesystem, root string, walkFn filepath.WalkFunc) error {
info, err := fs.Lstat(root)
if err != nil {
err = walkFn(root, nil, err)
} else {
err = walk(fs, root, info, walkFn)
}

if err == filepath.SkipDir {
return nil
}

return err
}
194 changes: 194 additions & 0 deletions util/walk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package util_test

import (
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"testing"

"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-billy/v5/util"

. "gopkg.in/check.v1"
)

type WalkSuite struct{}

func TestWalk(t *testing.T) { TestingT(t) }

var _ = Suite(&WalkSuite{})

func (s *WalkSuite) TestWalkCanSkipTopDirectory(c *C) {
filesystem := memfs.New()
c.Assert(util.Walk(filesystem, "/root/that/does/not/exist", func(path string, info os.FileInfo, err error) error { return filepath.SkipDir }), IsNil)
}

func (s *WalkSuite) TestWalkReturnsAnErrorWhenRootDoesNotExist(c *C) {
filesystem := memfs.New()
c.Assert(util.Walk(filesystem, "/root/that/does/not/exist", func(path string, info os.FileInfo, err error) error { return err }), NotNil)
}

func (s *WalkSuite) TestWalkOnPlainFile(c *C) {
filesystem := memfs.New()
createFile(c, filesystem, "./README.md")
discoveredPaths := []string{}
c.Assert(util.Walk(filesystem, "./README.md", func(path string, info os.FileInfo, err error) error {
discoveredPaths = append(discoveredPaths, path)
return nil
}), IsNil)
c.Assert(discoveredPaths, DeepEquals, []string{"./README.md"})
}

func (s *WalkSuite) TestWalkOnExistingFolder(c *C) {
filesystem := memfs.New()
createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
createFile(c, filesystem, "path/to/some/file")
discoveredPaths := []string{}
c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error {
discoveredPaths = append(discoveredPaths, path)
return nil
}), IsNil)
c.Assert(discoveredPaths, Contains, "path")
c.Assert(discoveredPaths, Contains, "path/to")
c.Assert(discoveredPaths, Contains, "path/to/some")
c.Assert(discoveredPaths, Contains, "path/to/some/file")
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder/that")
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder/that/contain")
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder/that/contain/file")
}

func (s *WalkSuite) TestWalkCanSkipFolder(c *C) {
filesystem := memfs.New()
createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
createFile(c, filesystem, "path/to/some/file")
discoveredPaths := []string{}
c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error {
discoveredPaths = append(discoveredPaths, path)
if path == "path/to/some/subfolder" {
return filepath.SkipDir
}
return nil
}), IsNil)
c.Assert(discoveredPaths, Contains, "path")
c.Assert(discoveredPaths, Contains, "path/to")
c.Assert(discoveredPaths, Contains, "path/to/some")
c.Assert(discoveredPaths, Contains, "path/to/some/file")
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that")
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain")
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain/file")
}

func (s *WalkSuite) TestWalkStopsOnError(c *C) {
filesystem := memfs.New()
createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
createFile(c, filesystem, "path/to/some/file")
discoveredPaths := []string{}
c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error {
discoveredPaths = append(discoveredPaths, path)
if path == "path/to/some/subfolder" {
return errors.New("uncaught error")
}
return nil
}), NotNil)
c.Assert(discoveredPaths, Contains, "path")
c.Assert(discoveredPaths, Contains, "path/to")
c.Assert(discoveredPaths, Contains, "path/to/some")
c.Assert(discoveredPaths, Contains, "path/to/some/file")
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that")
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain")
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain/file")
}

func (s *WalkSuite) TestWalkForwardsStatErrors(c *C) {
memFilesystem := memfs.New()
filesystem := &fnFs{
Filesystem: memFilesystem,
lstat: func(path string) (os.FileInfo, error) {
if path == "path/to/some/subfolder" {
return nil, errors.New("uncaught error")
}
return memFilesystem.Lstat(path)
},
}

createFile(c, filesystem, "path/to/some/subfolder/that/contain/file")
createFile(c, filesystem, "path/to/some/file")
discoveredPaths := []string{}
c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error {
discoveredPaths = append(discoveredPaths, path)
if path == "path/to/some/subfolder" {
c.Assert(err, NotNil)
}
return err
}), NotNil)
c.Assert(discoveredPaths, Contains, "path")
c.Assert(discoveredPaths, Contains, "path/to")
c.Assert(discoveredPaths, Contains, "path/to/some")
c.Assert(discoveredPaths, Contains, "path/to/some/file")
c.Assert(discoveredPaths, Contains, "path/to/some/subfolder")
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that")
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain")
c.Assert(discoveredPaths, NotContain, "path/to/some/subfolder/that/contain/file")
}

func createFile(c *C, filesystem billy.Filesystem, path string) {
fd, err := filesystem.Create(path)
c.Assert(err, IsNil)
if err != nil {
fd.Close()
}
}

type fnFs struct {
billy.Filesystem
lstat func(path string) (os.FileInfo, error)
}

func (f *fnFs) Lstat(path string) (os.FileInfo, error) {
if f.lstat != nil {
return f.lstat(path)
}
return nil, errors.New("not implemented")
}

type containsChecker struct {
*CheckerInfo
}

func (checker *containsChecker) Check(params []interface{}, names []string) (result bool, err string) {
defer func() {
if v := recover(); v != nil {
result = false
err = fmt.Sprint(v)
}
}()

value := reflect.ValueOf(params[0])
result = false
err = fmt.Sprintf("%v does not contain %v", params[0], params[1])
switch value.Kind() {
case reflect.Array, reflect.Slice:
for i := 0; i < value.Len(); i++ {
r := reflect.DeepEqual(value.Index(i).Interface(), params[1])
if r {
result = true
err = ""
}
}
default:
return false, "obtained value type is not iterable"
}
return
}

var Contains Checker = &containsChecker{
&CheckerInfo{Name: "Contains", Params: []string{"obtained", "expected"}},
}

var NotContain Checker = Not(Contains)

0 comments on commit 7ab80d7

Please sign in to comment.