Merge pull request #34 from mcuadros/v3
V3 reorganization
diff --git a/.travis.yml b/.travis.yml
index 01b51bb..5839b61 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,7 @@
- 1.8
- tip
-go_import_path: gopkg.in/src-d/go-billy.v2
+go_import_path: gopkg.in/src-d/go-billy.v3
matrix:
allow_failures:
diff --git a/README.md b/README.md
index f389b53..d7409a8 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# go-billy [](https://godoc.org/gopkg.in/src-d/go-billy.v2) [](https://travis-ci.org/src-d/go-billy) [](https://ci.appveyor.com/project/mcuadros/go-billy) [](https://codecov.io/gh/src-d/go-billy) [](https://codebeat.co/projects/github-com-src-d-go-billy)
+# go-billy [](https://godoc.org/gopkg.in/src-d/go-billy.v3) [](https://travis-ci.org/src-d/go-billy) [](https://ci.appveyor.com/project/mcuadros/go-billy) [](https://codecov.io/gh/src-d/go-billy) [](https://codebeat.co/projects/github-com-src-d-go-billy)
The missing interface filesystem abstraction for Go.
Billy implements an interface based on the `os` standard library, allowing to develop applications without dependency on the underlying storage. Make virtually free implement an mocks and testing over filesystem operations.
@@ -8,7 +8,7 @@
## Installation
```go
-go get -u gopkg.in/src-d/go-billy.v2/...
+go get -u gopkg.in/src-d/go-billy.v3/...
```
## Why billy?
diff --git a/appveyor.yml b/appveyor.yml
index 845cfad..4814a61 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,7 +1,7 @@
version: "{build}"
platform: x64
-clone_folder: c:\gopath\src\gopkg.in\src-d\go-billy.v2
+clone_folder: c:\gopath\src\gopkg.in\src-d\go-billy.v3
environment:
GOPATH: c:\gopath
diff --git a/fs.go b/fs.go
index 3433eaa..eb5f7ae 100644
--- a/fs.go
+++ b/fs.go
@@ -4,82 +4,135 @@
"errors"
"io"
"os"
+ "time"
)
var (
- ErrClosed = errors.New("file: Writing on closed file.")
- ErrReadOnly = errors.New("this is a read-only filesystem")
- ErrNotSupported = errors.New("feature not supported")
+ ErrReadOnly = errors.New("read-only filesystem")
+ ErrNotSupported = errors.New("feature not supported")
+ ErrCrossedBoundary = errors.New("chroot boundary crossed")
)
// Filesystem abstract the operations in a storage-agnostic interface.
-// It allows you to:
-// * Create files.
-// * Open existing files.
-// * Get info about files.
-// * List files in a directory.
-// * Get a temporal file.
-// * Rename files.
-// * Remove files.
-// * Create directories.
-// * Join parts of path.
-// * Obtain a filesystem starting on a subdirectory in the current filesystem.
-// * Get the base path for the filesystem.
-// Each method implementation varies from implementation to implementation. Refer to
-// the specific documentation for more info.
+// Each method implementation mimics the behavior of the equivalent functions
+// at the os package from the standard library.
type Filesystem interface {
+ Basic
+ TempFile
+ Dir
+ Symlink
+ Chroot
+}
+
+// Basic abstract the basic operations in a storage-agnostic interface as
+// an extension to the Basic interface.
+type Basic interface {
+ // Create creates the named file with mode 0666 (before umask), truncating
+ // it if it already exists. If successful, methods on the returned File can
+ // be used for I/O; the associated file descriptor has mode O_RDWR.
Create(filename string) (File, error)
+ // Open opens the named file for reading. If successful, methods on the
+ // returned file can be used for reading; the associated file descriptor has
+ // mode O_RDONLY.
Open(filename string) (File, error)
+ // OpenFile is the generalized open call; most users will use Open or Create
+ // instead. It opens the named file with specified flag (O_RDONLY etc.) and
+ // perm, (0666 etc.) if applicable. If successful, methods on the returned
+ // File can be used for I/O.
OpenFile(filename string, flag int, perm os.FileMode) (File, error)
// Stat returns a FileInfo describing the named file.
- Stat(filename string) (FileInfo, error)
+ Stat(filename string) (os.FileInfo, error)
+ // Rename renames (moves) oldpath to newpath. If newpath already exists and
+ // is not a directory, Rename replaces it. OS-specific restrictions may
+ // apply when oldpath and newpath are in different directories.
+ Rename(oldpath, newpath string) error
+ // Remove removes the named file or directory.
+ Remove(filename string) error
+ // Join joins any number of path elements into a single path, adding a
+ // Separator if necessary. Join calls filepath.Clean on the result; in
+ // particular, all empty strings are ignored. On Windows, the result is a
+ // UNC path if and only if the first path element is a UNC path.
+ Join(elem ...string) string
+}
+
+type TempFile interface {
+ // TempFile creates a new temporary file in the directory dir with a name
+ // beginning with prefix, opens the file for reading and writing, and
+ // returns the resulting *os.File. If dir is the empty string, TempFile
+ // uses the default directory for temporary files (see os.TempDir).
+ // Multiple programs calling TempFile simultaneously will not choose the
+ // same file. The caller can use f.Name() to find the pathname of the file.
+ // It is the caller's responsibility to remove the file when no longer
+ // needed.
+ TempFile(dir, prefix string) (File, error)
+}
+
+// Dir abstract the dir related operations in a storage-agnostic interface as
+// an extension to the Basic interface.
+type Dir interface {
+ // ReadDir reads the directory named by dirname and returns a list of
+ // directory entries sorted by filename.
+ ReadDir(path string) ([]os.FileInfo, error)
+ // MkdirAll creates a directory named path, along with any necessary
+ // parents, and returns nil, or else returns an error. The permission bits
+ // perm are used for all directories that MkdirAll creates. If path is/
+ // already a directory, MkdirAll does nothing and returns nil.
+ MkdirAll(filename string, perm os.FileMode) error
+}
+
+// Symlink abstract the symlink related operations in a storage-agnostic
+// interface as an extension to the Basic interface.
+type Symlink interface {
// Lstat returns a FileInfo describing the named file. If the file is a
// symbolic link, the returned FileInfo describes the symbolic link. Lstat
// makes no attempt to follow the link.
- Lstat(filename string) (FileInfo, error)
- ReadDir(path string) ([]FileInfo, error)
- TempFile(dir, prefix string) (File, error)
- Rename(from, to string) error
- Remove(filename string) error
- MkdirAll(filename string, perm os.FileMode) error
- Join(elem ...string) string
- Dir(path string) Filesystem
+ Lstat(filename string) (os.FileInfo, error)
// Symlink creates a symbolic-link from link to target. target may be an
// absolute or relative path, and need not refer to an existing node.
// Parent directories of link are created as necessary.
Symlink(target, link string) error
- // Readlink returns the target path of link. An error is returned if link is
- // not a symbolic-link.
+ // Readlink returns the target path of link.
Readlink(link string) (string, error)
- Base() string
}
-// File implements io.Closer, io.Reader, io.Seeker, and io.Writer>
-// Provides method to obtain the file name and the state of the file (open or closed).
+// Change abstract the FileInfo change related operations in a storage-agnostic
+// interface as an extension to the Basic interface
+type Change interface {
+ // Chmod changes the mode of the named file to mode. If the file is a
+ // symbolic link, it changes the mode of the link's target.
+ Chmod(name string, mode os.FileMode) error
+ // Lchown changes the numeric uid and gid of the named file. If the file is
+ // a symbolic link, it changes the uid and gid of the link itself.
+ Lchown(name string, uid, gid int) error
+ // Chown changes the numeric uid and gid of the named file. If the file is a
+ // symbolic link, it changes the uid and gid of the link's target.
+ Chown(name string, uid, gid int) error
+ // Chtimes changes the access and modification times of the named file,
+ // similar to the Unix utime() or utimes() functions.
+ //
+ // The underlying filesystem may truncate or round the values to a less
+ // precise time unit.
+ Chtimes(name string, atime time.Time, mtime time.Time) error
+}
+
+// Chroot abstract the chroot related operations in a storage-agnostic interface
+// as an extension to the Basic interface.
+type Chroot interface {
+ // Chroot returns a new filesystem from the same type where the new root is
+ // the given path. Files outside of the designated directory tree cannot be
+ // accessed.
+ Chroot(path string) (Basic, error)
+ // Root returns the root path of the filesystem.
+ Root() string
+}
+
+// File represent a file, being a subset of the os.File
type File interface {
- Filename() string
- IsClosed() bool
+ // Name returns the name of the file as presented to Open.
+ Name() string
io.Writer
io.Reader
+ io.ReaderAt
io.Seeker
io.Closer
}
-
-type FileInfo os.FileInfo
-
-type BaseFile struct {
- BaseFilename string
- Closed bool
-}
-
-func (f *BaseFile) Filename() string {
- return f.BaseFilename
-}
-
-func (f *BaseFile) IsClosed() bool {
- return f.Closed
-}
-
-type removerAll interface {
- RemoveAll(string) error
-}
diff --git a/helper/chroot/chroot.go b/helper/chroot/chroot.go
new file mode 100644
index 0000000..e3e8bb7
--- /dev/null
+++ b/helper/chroot/chroot.go
@@ -0,0 +1,281 @@
+package chroot
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+
+ "gopkg.in/src-d/go-billy.v3"
+)
+
+// ChrootHelper is a helper to implement billy.Chroot.
+type ChrootHelper struct {
+ underlying billy.Basic
+ base string
+
+ dirSupport bool
+ symlinkSupport bool
+ tempFileSupport bool
+}
+
+// New creates a new filesystem wrapping up the given 'fs'.
+// The created filesystem has its base in the given ChrootHelperectory of the
+// underlying filesystem.
+func New(fs billy.Basic, base string) billy.Filesystem {
+ helper := &ChrootHelper{underlying: fs, base: base}
+ _, helper.dirSupport = fs.(billy.Dir)
+ _, helper.symlinkSupport = fs.(billy.Symlink)
+ _, helper.tempFileSupport = fs.(billy.TempFile)
+
+ return helper
+}
+
+func (fs *ChrootHelper) underlyingPath(filename string) (string, error) {
+ if isCrossBoundaries(filename) {
+ return "", billy.ErrCrossedBoundary
+ }
+
+ return fs.Join(fs.Root(), filename), nil
+}
+
+func isCrossBoundaries(path string) bool {
+ path = filepath.ToSlash(path)
+ path = filepath.Clean(path)
+
+ return strings.HasPrefix(path, "..")
+}
+
+func (fs *ChrootHelper) Create(filename string) (billy.File, error) {
+ fullpath, err := fs.underlyingPath(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ f, err := fs.underlying.Create(fullpath)
+ if err != nil {
+ return nil, err
+ }
+
+ return newFile(fs, f, filename), nil
+}
+
+func (fs *ChrootHelper) Open(filename string) (billy.File, error) {
+ fullpath, err := fs.underlyingPath(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ f, err := fs.underlying.Open(fullpath)
+ if err != nil {
+ return nil, err
+ }
+
+ return newFile(fs, f, filename), nil
+}
+
+func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) {
+ fullpath, err := fs.underlyingPath(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ f, err := fs.underlying.OpenFile(fullpath, flag, mode)
+ if err != nil {
+ return nil, err
+ }
+
+ return newFile(fs, f, filename), nil
+}
+
+func (fs *ChrootHelper) Stat(filename string) (os.FileInfo, error) {
+ fullpath, err := fs.underlyingPath(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ return fs.underlying.Stat(fullpath)
+}
+
+func (fs *ChrootHelper) Rename(from, to string) error {
+ var err error
+ from, err = fs.underlyingPath(from)
+ if err != nil {
+ return err
+ }
+
+ to, err = fs.underlyingPath(to)
+ if err != nil {
+ return err
+ }
+
+ return fs.underlying.Rename(from, to)
+}
+
+func (fs *ChrootHelper) Remove(path string) error {
+ fullpath, err := fs.underlyingPath(path)
+ if err != nil {
+ return err
+ }
+
+ return fs.underlying.Remove(fullpath)
+}
+
+func (fs *ChrootHelper) Join(elem ...string) string {
+ return fs.underlying.Join(elem...)
+}
+
+func (fs *ChrootHelper) TempFile(dir, prefix string) (billy.File, error) {
+ if !fs.tempFileSupport {
+ return nil, billy.ErrNotSupported
+ }
+
+ fullpath, err := fs.underlyingPath(dir)
+ if err != nil {
+ return nil, err
+ }
+
+ f, err := fs.underlying.(billy.TempFile).TempFile(fullpath, prefix)
+ if err != nil {
+ return nil, err
+ }
+
+ return newFile(fs, f, fs.Join(dir, filepath.Base(f.Name()))), nil
+}
+
+func (fs *ChrootHelper) ReadDir(path string) ([]os.FileInfo, error) {
+ if !fs.dirSupport {
+ return nil, billy.ErrNotSupported
+ }
+
+ fullpath, err := fs.underlyingPath(path)
+ if err != nil {
+ return nil, err
+ }
+
+ return fs.underlying.(billy.Dir).ReadDir(fullpath)
+}
+
+func (fs *ChrootHelper) MkdirAll(filename string, perm os.FileMode) error {
+ if !fs.dirSupport {
+ return billy.ErrNotSupported
+ }
+
+ fullpath, err := fs.underlyingPath(filename)
+ if err != nil {
+ return err
+ }
+
+ return fs.underlying.(billy.Dir).MkdirAll(fullpath, perm)
+}
+
+func (fs *ChrootHelper) Lstat(filename string) (os.FileInfo, error) {
+ if !fs.symlinkSupport {
+ return nil, billy.ErrNotSupported
+ }
+
+ fullpath, err := fs.underlyingPath(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ return fs.underlying.(billy.Symlink).Lstat(fullpath)
+}
+
+func (fs *ChrootHelper) Symlink(target, link string) error {
+ if !fs.symlinkSupport {
+ return billy.ErrNotSupported
+ }
+
+ target = filepath.FromSlash(target)
+
+ // only rewrite target if it's already absolute
+ if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) {
+ target = fs.Join(fs.Root(), target)
+ target = filepath.Clean(filepath.FromSlash(target))
+ }
+
+ if fs.isTargetOutBounders(link, target) {
+ return billy.ErrCrossedBoundary
+ }
+
+ link, err := fs.underlyingPath(link)
+ if err != nil {
+ return err
+ }
+
+ return fs.underlying.(billy.Symlink).Symlink(target, link)
+}
+
+func (fs *ChrootHelper) isTargetOutBounders(link, target string) bool {
+ fulllink := fs.Join(fs.base, link)
+ fullpath := fs.Join(filepath.Dir(fulllink), target)
+ target, err := filepath.Rel(fs.base, fullpath)
+ if err != nil {
+ return true
+ }
+
+ return isCrossBoundaries(target)
+}
+
+func (fs *ChrootHelper) Readlink(link string) (string, error) {
+ if !fs.symlinkSupport {
+ return "", billy.ErrNotSupported
+ }
+
+ fullpath, err := fs.underlyingPath(link)
+ if err != nil {
+ return "", err
+ }
+
+ target, err := fs.underlying.(billy.Symlink).Readlink(fullpath)
+ if err != nil {
+ return "", err
+ }
+
+ if !filepath.IsAbs(target) && !strings.HasPrefix(target, string(filepath.Separator)) {
+ return target, nil
+ }
+
+ target, err = filepath.Rel(fs.base, target)
+ if err != nil {
+ return "", err
+ }
+
+ return string(os.PathSeparator) + target, nil
+}
+
+func (fs *ChrootHelper) Chroot(path string) (billy.Basic, error) {
+ fullpath, err := fs.underlyingPath(path)
+ if err != nil {
+ return nil, err
+ }
+
+ return New(fs.underlying, fullpath), nil
+}
+
+func (fs *ChrootHelper) Root() string {
+ return fs.base
+}
+
+func (fs *ChrootHelper) Underlying() billy.Basic {
+ return fs.underlying
+}
+
+type file struct {
+ billy.File
+ name string
+}
+
+func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File {
+ filename = fs.Join(fs.Root(), filename)
+ filename, _ = filepath.Rel(fs.Root(), filename)
+
+ return &file{
+ File: f,
+ name: filename,
+ }
+}
+
+func (f *file) Name() string {
+ return f.name
+}
diff --git a/helper/chroot/chroot_test.go b/helper/chroot/chroot_test.go
new file mode 100644
index 0000000..27edd78
--- /dev/null
+++ b/helper/chroot/chroot_test.go
@@ -0,0 +1,344 @@
+package chroot
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v3/test"
+
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+var _ = Suite(&ChrootSuite{})
+
+type ChrootSuite struct{}
+
+func (s *ChrootSuite) TestCreate(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ f, err := fs.Create("bar/qux")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, filepath.Join("bar", "qux"))
+
+ c.Assert(m.CreateArgs, HasLen, 1)
+ c.Assert(m.CreateArgs[0], Equals, "/foo/bar/qux")
+}
+
+func (s *ChrootSuite) TestCreateErrCrossedBoundary(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.Create("../foo")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestOpen(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ f, err := fs.Open("bar/qux")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, filepath.Join("bar", "qux"))
+
+ c.Assert(m.OpenArgs, HasLen, 1)
+ c.Assert(m.OpenArgs[0], Equals, "/foo/bar/qux")
+}
+
+func (s *ChrootSuite) TestChroot(c *C) {
+ m := &test.BasicMock{}
+
+ fs, _ := New(m, "/foo").Chroot("baz")
+ f, err := fs.Open("bar/qux")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, filepath.Join("bar", "qux"))
+
+ c.Assert(m.OpenArgs, HasLen, 1)
+ c.Assert(m.OpenArgs[0], Equals, "/foo/baz/bar/qux")
+}
+
+func (s *ChrootSuite) TestChrootErrCrossedBoundary(c *C) {
+ m := &test.BasicMock{}
+
+ fs, err := New(m, "/foo").Chroot("../qux")
+ c.Assert(fs, IsNil)
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestOpenErrCrossedBoundary(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.Open("../foo")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestOpenFile(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ f, err := fs.OpenFile("bar/qux", 42, 0777)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, filepath.Join("bar", "qux"))
+
+ c.Assert(m.OpenFileArgs, HasLen, 1)
+ c.Assert(m.OpenFileArgs[0], Equals, [3]interface{}{"/foo/bar/qux", 42, os.FileMode(0777)})
+}
+
+func (s *ChrootSuite) TestOpenFileErrCrossedBoundary(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.OpenFile("../foo", 42, 0777)
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestStat(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.Stat("bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(m.StatArgs, HasLen, 1)
+ c.Assert(m.StatArgs[0], Equals, "/foo/bar/qux")
+}
+
+func (s *ChrootSuite) TestStatErrCrossedBoundary(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.Stat("../foo")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestRename(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ err := fs.Rename("bar/qux", "qux/bar")
+ c.Assert(err, IsNil)
+
+ c.Assert(m.RenameArgs, HasLen, 1)
+ c.Assert(m.RenameArgs[0], Equals, [2]string{"/foo/bar/qux", "/foo/qux/bar"})
+}
+
+func (s *ChrootSuite) TestRenameErrCrossedBoundary(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ err := fs.Rename("../foo", "bar")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+
+ err = fs.Rename("foo", "../bar")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestRemove(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ err := fs.Remove("bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(m.RemoveArgs, HasLen, 1)
+ c.Assert(m.RemoveArgs[0], Equals, "/foo/bar/qux")
+}
+
+func (s *ChrootSuite) TestRemoveErrCrossedBoundary(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ err := fs.Remove("../foo")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestTempFile(c *C) {
+ m := &test.TempFileMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.TempFile("bar", "qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(m.TempFileArgs, HasLen, 1)
+ c.Assert(m.TempFileArgs[0], Equals, [2]string{"/foo/bar", "qux"})
+}
+
+func (s *ChrootSuite) TestTempFileErrCrossedBoundary(c *C) {
+ m := &test.TempFileMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.TempFile("../foo", "qux")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestTempFileWithBasic(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.TempFile("", "")
+ c.Assert(err, Equals, billy.ErrNotSupported)
+}
+
+func (s *ChrootSuite) TestReadDir(c *C) {
+ m := &test.DirMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.ReadDir("bar")
+ c.Assert(err, IsNil)
+
+ c.Assert(m.ReadDirArgs, HasLen, 1)
+ c.Assert(m.ReadDirArgs[0], Equals, "/foo/bar")
+}
+
+func (s *ChrootSuite) TestReadDirErrCrossedBoundary(c *C) {
+ m := &test.DirMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.ReadDir("../foo")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestReadDirWithBasic(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.ReadDir("")
+ c.Assert(err, Equals, billy.ErrNotSupported)
+}
+
+func (s *ChrootSuite) TestMkDirAll(c *C) {
+ m := &test.DirMock{}
+
+ fs := New(m, "/foo")
+ err := fs.MkdirAll("bar", 0777)
+ c.Assert(err, IsNil)
+
+ c.Assert(m.MkdirAllArgs, HasLen, 1)
+ c.Assert(m.MkdirAllArgs[0], Equals, [2]interface{}{"/foo/bar", os.FileMode(0777)})
+}
+
+func (s *ChrootSuite) TestMkdirAllErrCrossedBoundary(c *C) {
+ m := &test.DirMock{}
+
+ fs := New(m, "/foo")
+ err := fs.MkdirAll("../foo", 0777)
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestMkdirAllWithBasic(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ err := fs.MkdirAll("", 0)
+ c.Assert(err, Equals, billy.ErrNotSupported)
+}
+
+func (s *ChrootSuite) TestLstat(c *C) {
+ m := &test.SymlinkMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.Lstat("qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(m.LstatArgs, HasLen, 1)
+ c.Assert(m.LstatArgs[0], Equals, "/foo/qux")
+}
+
+func (s *ChrootSuite) TestLstatErrCrossedBoundary(c *C) {
+ m := &test.SymlinkMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.Lstat("../qux")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestLstatWithBasic(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.Lstat("")
+ c.Assert(err, Equals, billy.ErrNotSupported)
+}
+
+func (s *ChrootSuite) TestSymlink(c *C) {
+ m := &test.SymlinkMock{}
+
+ fs := New(m, "/foo")
+ err := fs.Symlink("../baz", "qux/bar")
+ c.Assert(err, IsNil)
+
+ c.Assert(m.SymlinkArgs, HasLen, 1)
+ c.Assert(m.SymlinkArgs[0], Equals, [2]string{filepath.FromSlash("../baz"), "/foo/qux/bar"})
+}
+
+func (s *ChrootSuite) TestSymlinkWithAbsoluteTarget(c *C) {
+ m := &test.SymlinkMock{}
+
+ fs := New(m, "/foo")
+ err := fs.Symlink("/bar", "qux/baz")
+ c.Assert(err, IsNil)
+
+ c.Assert(m.SymlinkArgs, HasLen, 1)
+ c.Assert(m.SymlinkArgs[0], Equals, [2]string{filepath.FromSlash("/foo/bar"), "/foo/qux/baz"})
+}
+
+func (s *ChrootSuite) TestSymlinkErrCrossedBoundary(c *C) {
+ m := &test.SymlinkMock{}
+
+ fs := New(m, "/foo")
+ err := fs.Symlink("qux", "../foo")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestSymlinkWithBasic(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ err := fs.Symlink("", "")
+ c.Assert(err, Equals, billy.ErrNotSupported)
+}
+
+func (s *ChrootSuite) TestReadlink(c *C) {
+ m := &test.SymlinkMock{}
+
+ fs := New(m, "/foo")
+ link, err := fs.Readlink("/qux")
+ c.Assert(err, IsNil)
+ c.Assert(link, Equals, filepath.FromSlash("/qux"))
+
+ c.Assert(m.ReadlinkArgs, HasLen, 1)
+ c.Assert(m.ReadlinkArgs[0], Equals, "/foo/qux")
+}
+
+func (s *ChrootSuite) TestReadlinkWithRelative(c *C) {
+ m := &test.SymlinkMock{}
+
+ fs := New(m, "/foo")
+ link, err := fs.Readlink("qux/bar")
+ c.Assert(err, IsNil)
+ c.Assert(link, Equals, filepath.FromSlash("/qux/bar"))
+
+ c.Assert(m.ReadlinkArgs, HasLen, 1)
+ c.Assert(m.ReadlinkArgs[0], Equals, "/foo/qux/bar")
+}
+
+func (s *ChrootSuite) TestReadlinkErrCrossedBoundary(c *C) {
+ m := &test.SymlinkMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.Readlink("../qux")
+ c.Assert(err, Equals, billy.ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestReadlinkWithBasic(c *C) {
+ m := &test.BasicMock{}
+
+ fs := New(m, "/foo")
+ _, err := fs.Readlink("")
+ c.Assert(err, Equals, billy.ErrNotSupported)
+}
diff --git a/helper/mount/mount.go b/helper/mount/mount.go
new file mode 100644
index 0000000..74c1e88
--- /dev/null
+++ b/helper/mount/mount.go
@@ -0,0 +1,284 @@
+package mount
+
+import (
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "fmt"
+
+ "gopkg.in/src-d/go-billy.v3"
+)
+
+var separator = string(filepath.Separator)
+
+// Mount is a helper that allows to emulate the behavior of mount in memory.
+// Very usufull to create a temporal dir, on filesystem where is a performance
+// penalty in doing so.
+type Mount struct {
+ underlying billy.Basic
+ source billy.Basic
+ mountpoint string
+
+ uc capabilities // underlying
+ sc capabilities // source
+}
+
+type capabilities struct{ dir, symlink bool }
+
+// New creates a new filesystem wrapping up 'fs' the intercepts all the calls
+// made to `mountpoint` path and redirecting it to `source` filesystem.
+func New(fs billy.Basic, mountpoint string, source billy.Basic) *Mount {
+ h := &Mount{
+ underlying: fs,
+ source: source,
+ mountpoint: cleanPath(mountpoint),
+ }
+
+ _, h.sc.dir = h.source.(billy.Dir)
+ _, h.sc.symlink = h.source.(billy.Symlink)
+ _, h.uc.dir = h.underlying.(billy.Dir)
+ _, h.uc.symlink = h.underlying.(billy.Symlink)
+ return h
+}
+
+func (h *Mount) Create(path string) (billy.File, error) {
+ fs, fullpath := h.getBasicAndPath(path)
+ if fullpath == "." {
+ return nil, os.ErrInvalid
+ }
+
+ f, err := fs.Create(fullpath)
+ return wrapFile(f, path), err
+}
+
+func (h *Mount) Open(path string) (billy.File, error) {
+ fs, fullpath := h.getBasicAndPath(path)
+ if fullpath == "." {
+ return nil, os.ErrInvalid
+ }
+
+ f, err := fs.Open(fullpath)
+ return wrapFile(f, path), err
+}
+
+func (h *Mount) OpenFile(path string, flag int, mode os.FileMode) (billy.File, error) {
+ fs, fullpath := h.getBasicAndPath(path)
+ if fullpath == "." {
+ return nil, os.ErrInvalid
+ }
+
+ f, err := fs.OpenFile(fullpath, flag, mode)
+ return wrapFile(f, path), err
+}
+
+func (h *Mount) Rename(from, to string) error {
+ fromInSource := h.isMountpoint(from)
+ toInSource := h.isMountpoint(to)
+
+ var fromFS, toFS billy.Basic
+
+ switch {
+ case fromInSource && toInSource:
+ from = h.mustRelToMountpoint(from)
+ to = h.mustRelToMountpoint(to)
+ return h.source.Rename(from, to)
+ case !fromInSource && !toInSource:
+ return h.underlying.Rename(from, to)
+ case fromInSource && !toInSource:
+ fromFS = h.source
+ from = h.mustRelToMountpoint(from)
+ toFS = h.underlying
+ to = cleanPath(to)
+ case !fromInSource && toInSource:
+ fromFS = h.underlying
+ from = cleanPath(from)
+ toFS = h.source
+ to = h.mustRelToMountpoint(to)
+ }
+
+ if err := copyPath(fromFS, toFS, from, to); err != nil {
+ return err
+ }
+
+ return fromFS.Remove(from)
+}
+
+func (h *Mount) Stat(path string) (os.FileInfo, error) {
+ fs, fullpath := h.getBasicAndPath(path)
+ return fs.Stat(fullpath)
+}
+
+func (h *Mount) Remove(path string) error {
+ fs, fullpath := h.getBasicAndPath(path)
+ if fullpath == "." {
+ return os.ErrInvalid
+ }
+
+ return fs.Remove(fullpath)
+}
+
+func (h *Mount) ReadDir(path string) ([]os.FileInfo, error) {
+ fs, fullpath, err := h.getDirAndPath(path)
+ if err != nil {
+ return nil, err
+ }
+
+ return fs.ReadDir(fullpath)
+}
+
+func (h *Mount) MkdirAll(filename string, perm os.FileMode) error {
+ fs, fullpath, err := h.getDirAndPath(filename)
+ if err != nil {
+ return err
+ }
+
+ return fs.MkdirAll(fullpath, perm)
+}
+
+func (h *Mount) Symlink(target, link string) error {
+ fs, fullpath, err := h.getSymlinkAndPath(link)
+ if err != nil {
+ return err
+ }
+
+ resolved := filepath.Join(filepath.Dir(link), target)
+ if h.isMountpoint(resolved) != h.isMountpoint(link) {
+ return fmt.Errorf("invalid symlink, target is crossing filesystems")
+ }
+
+ return fs.Symlink(target, fullpath)
+}
+
+func (h *Mount) Readlink(link string) (string, error) {
+ fs, fullpath, err := h.getSymlinkAndPath(link)
+ if err != nil {
+ return "", err
+ }
+
+ return fs.Readlink(fullpath)
+}
+
+func (h *Mount) Lstat(path string) (os.FileInfo, error) {
+ fs, fullpath, err := h.getSymlinkAndPath(path)
+ if err != nil {
+ return nil, err
+ }
+
+ return fs.Lstat(fullpath)
+}
+
+func (fs *Mount) getBasicAndPath(path string) (billy.Basic, string) {
+ path = cleanPath(path)
+ if !fs.isMountpoint(path) {
+ return fs.underlying, path
+ }
+
+ return fs.source, fs.mustRelToMountpoint(path)
+}
+
+func (fs *Mount) getDirAndPath(path string) (billy.Dir, string, error) {
+ path = cleanPath(path)
+ if !fs.isMountpoint(path) {
+ if !fs.uc.dir {
+ return nil, "", billy.ErrNotSupported
+ }
+
+ return fs.underlying.(billy.Dir), path, nil
+ }
+
+ if !fs.sc.dir {
+ return nil, "", billy.ErrNotSupported
+ }
+
+ return fs.source.(billy.Dir), fs.mustRelToMountpoint(path), nil
+}
+
+func (fs *Mount) getSymlinkAndPath(path string) (billy.Symlink, string, error) {
+ path = cleanPath(path)
+ if !fs.isMountpoint(path) {
+ if !fs.uc.symlink {
+ return nil, "", billy.ErrNotSupported
+ }
+
+ return fs.underlying.(billy.Symlink), path, nil
+ }
+
+ if !fs.sc.symlink {
+ return nil, "", billy.ErrNotSupported
+ }
+
+ return fs.source.(billy.Symlink), fs.mustRelToMountpoint(path), nil
+}
+
+func (fs *Mount) mustRelToMountpoint(path string) string {
+ path = cleanPath(path)
+ fullpath, err := filepath.Rel(fs.mountpoint, path)
+ if err != nil {
+ panic(err)
+ }
+
+ return fullpath
+}
+
+func (fs *Mount) isMountpoint(path string) bool {
+ path = cleanPath(path)
+ return strings.HasPrefix(path, fs.mountpoint)
+}
+
+func cleanPath(path string) string {
+ path = filepath.FromSlash(path)
+ rel, err := filepath.Rel(separator, path)
+ if err == nil {
+ path = rel
+ }
+
+ return filepath.Clean(path)
+}
+
+// copyPath copies a file across filesystems.
+func copyPath(src, dst billy.Basic, srcPath, dstPath string) error {
+ dstFile, err := dst.Create(dstPath)
+ if err != nil {
+ return err
+ }
+
+ srcFile, err := src.Open(srcPath)
+ if err != nil {
+ return nil
+ }
+
+ _, err = io.Copy(dstFile, srcFile)
+ if err != nil {
+ return nil
+ }
+
+ err = dstFile.Close()
+ if err != nil {
+ _ = srcFile.Close()
+ return err
+ }
+
+ return srcFile.Close()
+}
+
+type file struct {
+ billy.File
+ name string
+}
+
+func wrapFile(f billy.File, filename string) billy.File {
+ if f == nil {
+ return nil
+ }
+
+ return &file{
+ File: f,
+ name: cleanPath(filename),
+ }
+}
+
+func (f *file) Name() string {
+ return f.name
+}
diff --git a/helper/mount/mount_test.go b/helper/mount/mount_test.go
new file mode 100644
index 0000000..20af8d0
--- /dev/null
+++ b/helper/mount/mount_test.go
@@ -0,0 +1,331 @@
+package mount
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v3/memfs"
+ "gopkg.in/src-d/go-billy.v3/test"
+ "gopkg.in/src-d/go-billy.v3/util"
+
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+var _ = Suite(&MountSuite{})
+
+type MountSuite struct {
+ Helper *Mount
+ Underlying mock
+ Source mock
+}
+
+type mock struct {
+ test.BasicMock
+ test.DirMock
+ test.SymlinkMock
+}
+
+func (s *MountSuite) SetUpTest(c *C) {
+ s.Underlying.BasicMock = test.BasicMock{}
+ s.Underlying.DirMock = test.DirMock{}
+ s.Underlying.SymlinkMock = test.SymlinkMock{}
+ s.Source.BasicMock = test.BasicMock{}
+ s.Source.DirMock = test.DirMock{}
+ s.Source.SymlinkMock = test.SymlinkMock{}
+
+ s.Helper = New(&s.Underlying, "/foo", &s.Source)
+}
+
+func (s *MountSuite) TestCreate(c *C) {
+ f, err := s.Helper.Create("bar/qux")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, filepath.Join("bar", "qux"))
+
+ c.Assert(s.Underlying.CreateArgs, HasLen, 1)
+ c.Assert(s.Underlying.CreateArgs[0], Equals, filepath.Join("bar", "qux"))
+ c.Assert(s.Source.CreateArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestCreateMountPoint(c *C) {
+ f, err := s.Helper.Create("foo")
+ c.Assert(f, IsNil)
+ c.Assert(err, Equals, os.ErrInvalid)
+}
+
+func (s *MountSuite) TestCreateInMount(c *C) {
+ f, err := s.Helper.Create("foo/bar/qux")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, filepath.Join("foo", "bar", "qux"))
+
+ c.Assert(s.Underlying.CreateArgs, HasLen, 0)
+ c.Assert(s.Source.CreateArgs, HasLen, 1)
+ c.Assert(s.Source.CreateArgs[0], Equals, filepath.Join("bar", "qux"))
+}
+
+func (s *MountSuite) TestOpen(c *C) {
+ f, err := s.Helper.Open("bar/qux")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, filepath.Join("bar", "qux"))
+
+ c.Assert(s.Underlying.OpenArgs, HasLen, 1)
+ c.Assert(s.Underlying.OpenArgs[0], Equals, filepath.Join("bar", "qux"))
+ c.Assert(s.Source.OpenArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestOpenMountPoint(c *C) {
+ f, err := s.Helper.Open("foo")
+ c.Assert(f, IsNil)
+ c.Assert(err, Equals, os.ErrInvalid)
+}
+
+func (s *MountSuite) TestOpenInMount(c *C) {
+ f, err := s.Helper.Open("foo/bar/qux")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, filepath.Join("foo", "bar", "qux"))
+
+ c.Assert(s.Underlying.OpenArgs, HasLen, 0)
+ c.Assert(s.Source.OpenArgs, HasLen, 1)
+ c.Assert(s.Source.OpenArgs[0], Equals, filepath.Join("bar", "qux"))
+}
+
+func (s *MountSuite) TestOpenFile(c *C) {
+ f, err := s.Helper.OpenFile("bar/qux", 42, 0777)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, filepath.Join("bar", "qux"))
+
+ c.Assert(s.Underlying.OpenFileArgs, HasLen, 1)
+ c.Assert(s.Underlying.OpenFileArgs[0], Equals,
+ [3]interface{}{filepath.Join("bar", "qux"), 42, os.FileMode(0777)})
+ c.Assert(s.Source.OpenFileArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestOpenFileMountPoint(c *C) {
+ f, err := s.Helper.OpenFile("foo", 42, 0777)
+ c.Assert(f, IsNil)
+ c.Assert(err, Equals, os.ErrInvalid)
+}
+
+func (s *MountSuite) TestOpenFileInMount(c *C) {
+ f, err := s.Helper.OpenFile("foo/bar/qux", 42, 0777)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, filepath.Join("foo", "bar", "qux"))
+
+ c.Assert(s.Underlying.OpenFileArgs, HasLen, 0)
+ c.Assert(s.Source.OpenFileArgs, HasLen, 1)
+ c.Assert(s.Source.OpenFileArgs[0], Equals,
+ [3]interface{}{filepath.Join("bar", "qux"), 42, os.FileMode(0777)})
+}
+
+func (s *MountSuite) TestStat(c *C) {
+ _, err := s.Helper.Stat("bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.StatArgs, HasLen, 1)
+ c.Assert(s.Underlying.StatArgs[0], Equals, filepath.Join("bar", "qux"))
+ c.Assert(s.Source.StatArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestStatInMount(c *C) {
+ _, err := s.Helper.Stat("foo/bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.StatArgs, HasLen, 0)
+ c.Assert(s.Source.StatArgs, HasLen, 1)
+ c.Assert(s.Source.StatArgs[0], Equals, filepath.Join("bar", "qux"))
+}
+
+func (s *MountSuite) TestRename(c *C) {
+ err := s.Helper.Rename("bar/qux", "qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.RenameArgs, HasLen, 1)
+ c.Assert(s.Underlying.RenameArgs[0], Equals, [2]string{"bar/qux", "qux"})
+ c.Assert(s.Source.RenameArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestRenameInMount(c *C) {
+ err := s.Helper.Rename("foo/bar/qux", "foo/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.RenameArgs, HasLen, 0)
+ c.Assert(s.Source.RenameArgs, HasLen, 1)
+ c.Assert(s.Source.RenameArgs[0], Equals,
+ [2]string{filepath.Join("bar", "qux"), "qux"})
+}
+
+func (s *MountSuite) TestRenameCross(c *C) {
+ underlying := memfs.New()
+ source := memfs.New()
+
+ util.WriteFile(underlying, "file", []byte("foo"), 0777)
+
+ fs := New(underlying, "/foo", source)
+ err := fs.Rename("file", "foo/file")
+ c.Assert(err, IsNil)
+
+ _, err = underlying.Stat("file")
+ c.Assert(err, Equals, os.ErrNotExist)
+
+ _, err = source.Stat("file")
+ c.Assert(err, IsNil)
+
+ err = fs.Rename("foo/file", "file")
+ c.Assert(err, IsNil)
+
+ _, err = underlying.Stat("file")
+ c.Assert(err, IsNil)
+
+ _, err = source.Stat("file")
+ c.Assert(err, Equals, os.ErrNotExist)
+}
+
+func (s *MountSuite) TestRemove(c *C) {
+ err := s.Helper.Remove("bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.RemoveArgs, HasLen, 1)
+ c.Assert(s.Underlying.RemoveArgs[0], Equals, filepath.Join("bar", "qux"))
+ c.Assert(s.Source.RemoveArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestRemoveMountPoint(c *C) {
+ err := s.Helper.Remove("foo")
+ c.Assert(err, Equals, os.ErrInvalid)
+}
+
+func (s *MountSuite) TestRemoveInMount(c *C) {
+ err := s.Helper.Remove("foo/bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.RemoveArgs, HasLen, 0)
+ c.Assert(s.Source.RemoveArgs, HasLen, 1)
+ c.Assert(s.Source.RemoveArgs[0], Equals, filepath.Join("bar", "qux"))
+}
+
+func (s *MountSuite) TestReadDir(c *C) {
+ _, err := s.Helper.ReadDir("bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.ReadDirArgs, HasLen, 1)
+ c.Assert(s.Underlying.ReadDirArgs[0], Equals, filepath.Join("bar", "qux"))
+ c.Assert(s.Source.ReadDirArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestReadDirInMount(c *C) {
+ _, err := s.Helper.ReadDir("foo/bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.ReadDirArgs, HasLen, 0)
+ c.Assert(s.Source.ReadDirArgs, HasLen, 1)
+ c.Assert(s.Source.ReadDirArgs[0], Equals, filepath.Join("bar", "qux"))
+}
+
+func (s *MountSuite) TestMkdirAll(c *C) {
+ err := s.Helper.MkdirAll("bar/qux", 0777)
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.MkdirAllArgs, HasLen, 1)
+ c.Assert(s.Underlying.MkdirAllArgs[0], Equals,
+ [2]interface{}{filepath.Join("bar", "qux"), os.FileMode(0777)})
+ c.Assert(s.Source.MkdirAllArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestMkdirAllInMount(c *C) {
+ err := s.Helper.MkdirAll("foo/bar/qux", 0777)
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.MkdirAllArgs, HasLen, 0)
+ c.Assert(s.Source.MkdirAllArgs, HasLen, 1)
+ c.Assert(s.Source.MkdirAllArgs[0], Equals,
+ [2]interface{}{filepath.Join("bar", "qux"), os.FileMode(0777)})
+}
+
+func (s *MountSuite) TestLstat(c *C) {
+ _, err := s.Helper.Lstat("bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.LstatArgs, HasLen, 1)
+ c.Assert(s.Underlying.LstatArgs[0], Equals, filepath.Join("bar", "qux"))
+ c.Assert(s.Source.LstatArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestLstatInMount(c *C) {
+ _, err := s.Helper.Lstat("foo/bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.LstatArgs, HasLen, 0)
+ c.Assert(s.Source.LstatArgs, HasLen, 1)
+ c.Assert(s.Source.LstatArgs[0], Equals, filepath.Join("bar", "qux"))
+}
+
+func (s *MountSuite) TestSymlink(c *C) {
+ err := s.Helper.Symlink("../baz", "bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.SymlinkArgs, HasLen, 1)
+ c.Assert(s.Underlying.SymlinkArgs[0], Equals,
+ [2]string{"../baz", filepath.Join("bar", "qux")})
+ c.Assert(s.Source.SymlinkArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestSymlinkCrossMount(c *C) {
+ err := s.Helper.Symlink("../foo", "bar/qux")
+ c.Assert(err, NotNil)
+
+ err = s.Helper.Symlink("../foo/qux", "bar/qux")
+ c.Assert(err, NotNil)
+
+ err = s.Helper.Symlink("../baz", "foo")
+ c.Assert(err, NotNil)
+
+ err = s.Helper.Symlink("../../../foo", "foo/bar/qux")
+ c.Assert(err, NotNil)
+}
+
+func (s *MountSuite) TestSymlinkInMount(c *C) {
+ err := s.Helper.Symlink("../baz", "foo/bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.SymlinkArgs, HasLen, 0)
+ c.Assert(s.Source.SymlinkArgs, HasLen, 1)
+ c.Assert(s.Source.SymlinkArgs[0], Equals,
+ [2]string{"../baz", filepath.Join("bar", "qux")})
+}
+
+func (s *MountSuite) TestRadlink(c *C) {
+ _, err := s.Helper.Readlink("bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.ReadlinkArgs, HasLen, 1)
+ c.Assert(s.Underlying.ReadlinkArgs[0], Equals, filepath.Join("bar", "qux"))
+ c.Assert(s.Source.ReadlinkArgs, HasLen, 0)
+}
+
+func (s *MountSuite) TestReadlinkInMount(c *C) {
+ _, err := s.Helper.Readlink("foo/bar/qux")
+ c.Assert(err, IsNil)
+
+ c.Assert(s.Underlying.ReadlinkArgs, HasLen, 0)
+ c.Assert(s.Source.ReadlinkArgs, HasLen, 1)
+ c.Assert(s.Source.ReadlinkArgs[0], Equals, filepath.Join("bar", "qux"))
+}
+
+func (s *MountSuite) TestUnderlyingNotSupported(c *C) {
+ h := New(&test.BasicMock{}, "/foo", &test.BasicMock{})
+ _, err := h.ReadDir("qux")
+ c.Assert(err, Equals, billy.ErrNotSupported)
+ _, err = h.Readlink("qux")
+ c.Assert(err, Equals, billy.ErrNotSupported)
+}
+
+func (s *MountSuite) TestSourceNotSupported(c *C) {
+ h := New(&s.Underlying, "/foo", &test.BasicMock{})
+ _, err := h.ReadDir("foo")
+ c.Assert(err, Equals, billy.ErrNotSupported)
+ _, err = h.Readlink("foo")
+ c.Assert(err, Equals, billy.ErrNotSupported)
+}
diff --git a/memfs/memory.go b/memfs/memory.go
index e31159c..60316dd 100644
--- a/memfs/memory.go
+++ b/memfs/memory.go
@@ -1,5 +1,5 @@
// Package memfs provides a billy filesystem base on memory.
-package memfs // import "gopkg.in/src-d/go-billy.v2/memfs"
+package memfs // import "gopkg.in/src-d/go-billy.v3/memfs"
import (
"errors"
@@ -10,53 +10,48 @@
"strings"
"time"
- "gopkg.in/src-d/go-billy.v2"
+ "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v3/helper/chroot"
+ "gopkg.in/src-d/go-billy.v3/util"
)
const separator = filepath.Separator
// Memory a very convenient filesystem based on memory files
type Memory struct {
- base string
- s *storage
+ s *storage
tempCount int
}
-//New returns a new Memory filesystem
-func New() *Memory {
- return &Memory{
- base: string(separator),
- s: newStorage(),
- }
+//New returns a new Memory filesystem.
+func New() billy.Filesystem {
+ fs := &Memory{s: newStorage()}
+ return chroot.New(fs, string(separator))
}
-// Create returns a new file in memory from a given filename.
func (fs *Memory) Create(filename string) (billy.File, error) {
return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
}
-// Open returns a readonly file from a given name.
func (fs *Memory) Open(filename string) (billy.File, error) {
return fs.OpenFile(filename, os.O_RDONLY, 0)
}
-// OpenFile returns the file from a given name with given flag and permits.
func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
- fullpath := fs.fullpath(filename)
- f, has := fs.s.Get(fullpath)
+ f, has := fs.s.Get(filename)
if !has {
if !isCreate(flag) {
return nil, os.ErrNotExist
}
var err error
- f, err = fs.s.New(fullpath, perm, flag)
+ f, err = fs.s.New(filename, perm, flag)
if err != nil {
return nil, err
}
} else {
- if target, isLink := fs.resolveIfLink(fullpath, f); isLink {
+ if target, isLink := fs.resolveLink(filename, f); isLink {
return fs.OpenFile(target, flag, perm)
}
}
@@ -65,19 +60,12 @@
return nil, fmt.Errorf("cannot open directory: %s", filename)
}
- filename, err := filepath.Rel(fs.base, fullpath)
- if err != nil {
- return nil, err
- }
-
return f.Duplicate(filename, perm, flag), nil
}
-func (fs *Memory) fullpath(path string) string {
- return clean(fs.Join(fs.base, path))
-}
+var errNotLink = errors.New("not a link")
-func (fs *Memory) resolveIfLink(fullpath string, f *file) (target string, isLink bool) {
+func (fs *Memory) resolveLink(fullpath string, f *file) (target string, isLink bool) {
if !isSymlink(f.mode) {
return fullpath, false
}
@@ -87,7 +75,7 @@
target = fs.Join(filepath.Dir(fullpath), target)
}
- return clean(target), true
+ return target, true
}
// On Windows OS, IsAbs validates if a path is valid based on if stars with a
@@ -97,18 +85,16 @@
return filepath.IsAbs(path) || strings.HasPrefix(path, string(separator))
}
-// Stat returns a billy.FileInfo with the information of the requested file.
-func (fs *Memory) Stat(filename string) (billy.FileInfo, error) {
- fullpath := fs.fullpath(filename)
- f, has := fs.s.Get(fullpath)
+func (fs *Memory) Stat(filename string) (os.FileInfo, error) {
+ f, has := fs.s.Get(filename)
if !has {
return nil, os.ErrNotExist
}
- fi := f.Stat()
+ fi, _ := f.Stat()
var err error
- if target, isLink := fs.resolveIfLink(fullpath, f); isLink {
+ if target, isLink := fs.resolveLink(filename, f); isLink {
fi, err = fs.Stat(target)
if err != nil {
return nil, err
@@ -122,98 +108,60 @@
return fi, nil
}
-func (fs *Memory) Lstat(filename string) (billy.FileInfo, error) {
- fullpath := fs.fullpath(filename)
- f, has := fs.s.Get(fullpath)
+func (fs *Memory) Lstat(filename string) (os.FileInfo, error) {
+ f, has := fs.s.Get(filename)
if !has {
return nil, os.ErrNotExist
}
- return f.Stat(), nil
+ return f.Stat()
}
-// ReadDir returns a list of billy.FileInfo in the given directory.
-func (fs *Memory) ReadDir(path string) ([]billy.FileInfo, error) {
- fullpath := fs.fullpath(path)
- if f, has := fs.s.Get(fullpath); has {
- if target, isLink := fs.resolveIfLink(fullpath, f); isLink {
+func (fs *Memory) ReadDir(path string) ([]os.FileInfo, error) {
+ if f, has := fs.s.Get(path); has {
+ if target, isLink := fs.resolveLink(path, f); isLink {
return fs.ReadDir(target)
}
}
- var entries []billy.FileInfo
- for _, f := range fs.s.Children(fullpath) {
- entries = append(entries, f.Stat())
+ var entries []os.FileInfo
+ for _, f := range fs.s.Children(path) {
+ fi, _ := f.Stat()
+ entries = append(entries, fi)
}
return entries, nil
}
-// MkdirAll creates a directory.
func (fs *Memory) MkdirAll(path string, perm os.FileMode) error {
- fullpath := fs.Join(fs.base, path)
-
- _, err := fs.s.New(fullpath, perm|os.ModeDir, 0)
+ _, err := fs.s.New(path, perm|os.ModeDir, 0)
return err
}
-var maxTempFiles = 1024 * 4
-
-// TempFile creates a new temporary file.
func (fs *Memory) TempFile(dir, prefix string) (billy.File, error) {
- var fullpath string
- for {
- if fs.tempCount >= maxTempFiles {
- return nil, errors.New("max. number of tempfiles reached")
- }
-
- fullpath = fs.getTempFilename(dir, prefix)
- if _, ok := fs.s.files[fullpath]; !ok {
- break
- }
- }
-
- return fs.Create(fullpath)
+ return util.TempFile(fs, dir, prefix)
}
func (fs *Memory) getTempFilename(dir, prefix string) string {
fs.tempCount++
filename := fmt.Sprintf("%s_%d_%d", prefix, fs.tempCount, time.Now().UnixNano())
- return fs.Join(fs.base, dir, filename)
+ return fs.Join(dir, filename)
}
-// Rename moves a the `from` file to the `to` file.
func (fs *Memory) Rename(from, to string) error {
- from = fs.Join(fs.base, from)
- to = fs.Join(fs.base, to)
-
return fs.s.Rename(from, to)
}
-// Remove deletes a given file from storage.
func (fs *Memory) Remove(filename string) error {
- fullpath := fs.Join(fs.base, filename)
- return fs.s.Remove(fullpath)
+ return fs.s.Remove(filename)
}
-// Join joins any number of path elements into a single path, adding a Separator if necessary.
func (fs *Memory) Join(elem ...string) string {
return filepath.Join(elem...)
}
-// Dir creates a new memory filesystem whose root is the given path inside the current
-// filesystem.
-func (fs *Memory) Dir(path string) billy.Filesystem {
- return &Memory{
- base: fs.Join(fs.base, path),
- s: fs.s,
- }
-}
-
func (fs *Memory) Symlink(target, link string) error {
- fullpath := clean(fs.Join(fs.base, link))
-
- _, err := fs.Stat(fullpath)
+ _, err := fs.Stat(link)
if err == nil {
return os.ErrExist
}
@@ -222,13 +170,11 @@
return err
}
- target = clean(target)
- return billy.WriteFile(fs, fullpath, []byte(target), 0777|os.ModeSymlink)
+ return util.WriteFile(fs, link, []byte(target), 0777|os.ModeSymlink)
}
func (fs *Memory) Readlink(link string) (string, error) {
- fullpath := fs.fullpath(link)
- f, has := fs.s.Get(fullpath)
+ f, has := fs.s.Get(link)
if !has {
return "", os.ErrNotExist
}
@@ -236,7 +182,7 @@
if !isSymlink(f.mode) {
return "", &os.PathError{
Op: "readlink",
- Path: fullpath,
+ Path: link,
Err: fmt.Errorf("not a symlink"),
}
}
@@ -244,18 +190,18 @@
return string(f.content.bytes), nil
}
-// Base returns the base path for the filesystem.
-func (fs *Memory) Base() string {
- return fs.base
-}
-
type file struct {
- billy.BaseFile
-
+ name string
content *content
position int64
flag int
mode os.FileMode
+
+ isClosed bool
+}
+
+func (f *file) Name() string {
+ return f.name
}
func (f *file) Read(b []byte) (int, error) {
@@ -270,8 +216,8 @@
}
func (f *file) ReadAt(b []byte, off int64) (int, error) {
- if f.IsClosed() {
- return 0, billy.ErrClosed
+ if f.isClosed {
+ return 0, os.ErrClosed
}
if !isReadAndWrite(f.flag) && !isReadOnly(f.flag) {
@@ -284,8 +230,8 @@
}
func (f *file) Seek(offset int64, whence int) (int64, error) {
- if f.IsClosed() {
- return 0, billy.ErrClosed
+ if f.isClosed {
+ return 0, os.ErrClosed
}
switch whence {
@@ -301,8 +247,8 @@
}
func (f *file) Write(p []byte) (int, error) {
- if f.IsClosed() {
- return 0, billy.ErrClosed
+ if f.isClosed {
+ return 0, os.ErrClosed
}
if !isReadAndWrite(f.flag) && !isWriteOnly(f.flag) {
@@ -316,20 +262,20 @@
}
func (f *file) Close() error {
- if f.IsClosed() {
- return errors.New("file already closed")
+ if f.isClosed {
+ return os.ErrClosed
}
- f.Closed = true
+ f.isClosed = true
return nil
}
func (f *file) Duplicate(filename string, mode os.FileMode, flag int) billy.File {
new := &file{
- BaseFile: billy.BaseFile{BaseFilename: filename},
- content: f.content,
- mode: mode,
- flag: flag,
+ name: filename,
+ content: f.content,
+ mode: mode,
+ flag: flag,
}
if isAppend(flag) {
@@ -343,12 +289,12 @@
return new
}
-func (f *file) Stat() billy.FileInfo {
+func (f *file) Stat() (os.FileInfo, error) {
return &fileInfo{
- name: f.Filename(),
+ name: f.Name(),
mode: f.mode,
size: f.content.Len(),
- }
+ }, nil
}
type fileInfo struct {
diff --git a/memfs/memory_test.go b/memfs/memory_test.go
index d527a41..6687459 100644
--- a/memfs/memory_test.go
+++ b/memfs/memory_test.go
@@ -3,8 +3,9 @@
import (
"testing"
+ "gopkg.in/src-d/go-billy.v3/test"
+
. "gopkg.in/check.v1"
- "gopkg.in/src-d/go-billy.v2/test"
)
func Test(t *testing.T) { TestingT(t) }
@@ -17,17 +18,5 @@
var _ = Suite(&MemorySuite{})
func (s *MemorySuite) SetUpTest(c *C) {
- s.FilesystemSuite.FS = New()
-}
-
-func (s *MemorySuite) TestTempFileMaxTempFiles(c *C) {
- for i := 0; i < maxTempFiles; i++ {
- f, err := s.FilesystemSuite.FS.TempFile("", "")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
- }
-
- f, err := s.FilesystemSuite.FS.TempFile("", "")
- c.Assert(err, NotNil)
- c.Assert(f, IsNil)
+ s.FilesystemSuite = test.NewFilesystemSuite(New())
}
diff --git a/memfs/storage.go b/memfs/storage.go
index 916be11..354c512 100644
--- a/memfs/storage.go
+++ b/memfs/storage.go
@@ -5,8 +5,6 @@
"io"
"os"
"path/filepath"
-
- "gopkg.in/src-d/go-billy.v2"
)
type storage struct {
@@ -39,9 +37,7 @@
}
f := &file{
- BaseFile: billy.BaseFile{
- BaseFilename: filepath.Base(path),
- },
+ name: filepath.Base(path),
content: &content{},
mode: mode,
flag: flag,
@@ -55,7 +51,7 @@
func (s *storage) createParent(path string, mode os.FileMode, f *file) error {
base := filepath.Dir(path)
base = clean(base)
- if f.Filename() == string(separator) {
+ if f.Name() == string(separator) {
return nil
}
@@ -67,7 +63,7 @@
s.children[base] = make(map[string]*file, 0)
}
- s.children[base][f.Filename()] = f
+ s.children[base][f.Name()] = f
return nil
}
@@ -136,7 +132,7 @@
func (s *storage) move(from, to string) error {
s.files[to] = s.files[from]
- s.files[to].BaseFilename = filepath.Base(to)
+ s.files[to].name = filepath.Base(to)
s.children[to] = s.children[from]
defer func() {
diff --git a/osfs/os.go b/osfs/os.go
index 9bc9656..62d90a5 100644
--- a/osfs/os.go
+++ b/osfs/os.go
@@ -1,14 +1,13 @@
// Package osfs provides a billy filesystem for the OS.
-package osfs // import "gopkg.in/src-d/go-billy.v2/osfs"
+package osfs // import "gopkg.in/src-d/go-billy.v3/osfs"
import (
"io/ioutil"
"os"
"path/filepath"
- "strings"
-
- "gopkg.in/src-d/go-billy.v2"
+ "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v3/helper/chroot"
)
const (
@@ -16,46 +15,26 @@
defaultCreateMode = 0666
)
-// OS is a filesystem based on the os filesystem
-type OS struct {
- base string
+// OS is a filesystem based on the os filesystem.
+type OS struct{}
+
+// New returns a new OS filesystem.
+func New(baseDir string) billy.Filesystem {
+ return chroot.New(&OS{}, baseDir)
}
-// New returns a new OS filesystem
-func New(baseDir string) *OS {
- return &OS{
- base: baseDir,
- }
-}
-
-// Create creates a file and opens it with standard permissions
-// and modes O_RDWR, O_CREATE and O_TRUNC.
func (fs *OS) Create(filename string) (billy.File, error) {
return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode)
}
-// OpenFile is equivalent to standard os.OpenFile.
-// If flag os.O_CREATE is set, all parent directories will be created.
func (fs *OS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
- fullpath := fs.absolutize(filename)
-
if flag&os.O_CREATE != 0 {
- if err := fs.createDir(fullpath); err != nil {
+ if err := fs.createDir(filename); err != nil {
return nil, err
}
}
- f, err := os.OpenFile(fullpath, flag, perm)
- if err != nil {
- return nil, err
- }
-
- filename, err = filepath.Rel(fs.base, fullpath)
- if err != nil {
- return nil, err
- }
-
- return newOSFile(filename, f), nil
+ return os.OpenFile(filename, flag, perm)
}
func (fs *OS) createDir(fullpath string) error {
@@ -69,17 +48,13 @@
return nil
}
-// ReadDir returns the filesystem info for all the archives under the specified
-// path.
-func (fs *OS) ReadDir(path string) ([]billy.FileInfo, error) {
- fullpath := fs.absolutize(path)
-
- l, err := ioutil.ReadDir(fullpath)
+func (fs *OS) ReadDir(path string) ([]os.FileInfo, error) {
+ l, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
- var s = make([]billy.FileInfo, len(l))
+ var s = make([]os.FileInfo, len(l))
for i, f := range l {
s[i] = f
}
@@ -87,11 +62,7 @@
return s, nil
}
-// Rename moves a file in disk from _from_ to _to_.
func (fs *OS) Rename(from, to string) error {
- from = fs.absolutize(from)
- to = fs.absolutize(to)
-
if err := fs.createDir(to); err != nil {
return err
}
@@ -99,86 +70,39 @@
return os.Rename(from, to)
}
-// MkdirAll creates a directory.
func (fs *OS) MkdirAll(path string, perm os.FileMode) error {
- fullpath := fs.absolutize(path)
- return os.MkdirAll(fullpath, defaultDirectoryMode)
+ return os.MkdirAll(path, defaultDirectoryMode)
}
-// Open opens a file in read-only mode.
func (fs *OS) Open(filename string) (billy.File, error) {
return fs.OpenFile(filename, os.O_RDONLY, 0)
}
-// Remove deletes a file in disk.
func (fs *OS) Remove(filename string) error {
- fullpath := fs.absolutize(filename)
- return os.Remove(fullpath)
+ return os.Remove(filename)
}
-// TempFile creates a new temporal file.
func (fs *OS) TempFile(dir, prefix string) (billy.File, error) {
- fullpath := fs.absolutize(dir)
- if err := fs.createDir(fullpath + string(os.PathSeparator)); err != nil {
+ if err := fs.createDir(dir + string(os.PathSeparator)); err != nil {
return nil, err
}
- f, err := ioutil.TempFile(fullpath, prefix)
- if err != nil {
- return nil, err
- }
-
- s, err := f.Stat()
- if err != nil {
- return nil, err
- }
-
- filename, err := filepath.Rel(fs.base, fs.Join(fullpath, s.Name()))
- if err != nil {
- return nil, err
- }
-
- return newOSFile(filename, f), nil
+ return ioutil.TempFile(dir, prefix)
}
-// Join joins the specified elements using the filesystem separator.
func (fs *OS) Join(elem ...string) string {
return filepath.Join(elem...)
}
-// Dir returns a new Filesystem from the same type of fs using as baseDir the
-// given path
-func (fs *OS) Dir(path string) billy.Filesystem {
- return New(fs.absolutize(path))
-}
-
-// Base returns the base path of the filesytem
-func (fs *OS) Base() string {
- return fs.base
-}
-
-// RemoveAll removes a file or directory recursively. Removes everything it can,
-// but returns the first error.
func (fs *OS) RemoveAll(path string) error {
- fullpath := fs.Join(fs.base, path)
- return os.RemoveAll(fullpath)
+ return os.RemoveAll(filepath.Clean(path))
}
-func (fs *OS) Lstat(filename string) (billy.FileInfo, error) {
- fullpath := fs.Join(fs.base, filename)
- return os.Lstat(fullpath)
+func (fs *OS) Lstat(filename string) (os.FileInfo, error) {
+ return os.Lstat(filepath.Clean(filename))
}
-// Symlink imlements billy.Symlinker.Symlink.
func (fs *OS) Symlink(target, link string) error {
- target = filepath.FromSlash(target)
-
- // only rewrite target if it's already absolute
- if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) {
- target = fs.absolutize(target)
- }
-
- link = fs.absolutize(link)
if err := fs.createDir(link); err != nil {
return err
}
@@ -186,64 +110,6 @@
return os.Symlink(target, link)
}
-// Readlink implements billy.Symlinker.Readlink.
func (fs *OS) Readlink(link string) (string, error) {
- fullpath := fs.Join(fs.base, link)
- target, err := os.Readlink(fullpath)
- if err != nil {
- return "", err
- }
-
- if !filepath.IsAbs(target) && !strings.HasPrefix(target, string(filepath.Separator)) {
- return target, nil
- }
-
- target, err = filepath.Rel(fs.base, target)
- if err != nil {
- return "", err
- }
-
- return string(os.PathSeparator) + target, nil
-}
-
-// osFile represents a file in the os filesystem
-type osFile struct {
- billy.BaseFile
- file *os.File
-}
-
-func newOSFile(filename string, file *os.File) billy.File {
- return &osFile{
- BaseFile: billy.BaseFile{BaseFilename: filename},
- file: file,
- }
-}
-
-func (f *osFile) Read(p []byte) (int, error) {
- return f.file.Read(p)
-}
-
-func (f *osFile) Seek(offset int64, whence int) (int64, error) {
- return f.file.Seek(offset, whence)
-}
-
-func (f *osFile) Write(p []byte) (int, error) {
- return f.file.Write(p)
-}
-
-func (f *osFile) Close() error {
- f.BaseFile.Closed = true
-
- return f.file.Close()
-}
-
-func (f *osFile) ReadAt(p []byte, off int64) (int, error) {
- return f.file.ReadAt(p, off)
-}
-
-func (fs *OS) absolutize(relpath string) string {
- fullpath := filepath.FromSlash(filepath.ToSlash(relpath))
-
- fullpath = fs.Join(fs.base, fullpath)
- return filepath.Clean(fullpath)
+ return os.Readlink(link)
}
diff --git a/osfs/os_posix.go b/osfs/os_posix.go
index 9f23b02..2cee95e 100644
--- a/osfs/os_posix.go
+++ b/osfs/os_posix.go
@@ -4,12 +4,9 @@
import (
"os"
-
- "gopkg.in/src-d/go-billy.v2"
)
// Stat returns the FileInfo structure describing file.
-func (fs *OS) Stat(filename string) (billy.FileInfo, error) {
- fullpath := fs.absolutize(filename)
- return os.Stat(fullpath)
+func (fs *OS) Stat(filename string) (os.FileInfo, error) {
+ return os.Stat(filename)
}
diff --git a/osfs/os_test.go b/osfs/os_test.go
index 767dc74..ad3d445 100644
--- a/osfs/os_test.go
+++ b/osfs/os_test.go
@@ -7,7 +7,7 @@
"testing"
. "gopkg.in/check.v1"
- "gopkg.in/src-d/go-billy.v2/test"
+ "gopkg.in/src-d/go-billy.v3/test"
)
func Test(t *testing.T) { TestingT(t) }
@@ -21,7 +21,7 @@
func (s *OSSuite) SetUpTest(c *C) {
s.path, _ = ioutil.TempDir(os.TempDir(), "go-billy-osfs-test")
- s.FilesystemSuite.FS = New(s.path)
+ s.FilesystemSuite = test.NewFilesystemSuite(New(s.path))
}
func (s *OSSuite) TearDownTest(c *C) {
diff --git a/osfs/os_windows.go b/osfs/os_windows.go
index 18beabe..333f72e 100644
--- a/osfs/os_windows.go
+++ b/osfs/os_windows.go
@@ -6,23 +6,18 @@
"os"
"path/filepath"
"strings"
-
- "gopkg.in/src-d/go-billy.v2"
)
// Stat returns the FileInfo structure describing file.
-func (fs *OS) Stat(filename string) (billy.FileInfo, error) {
+func (fs *OS) Stat(filename string) (os.FileInfo, error) {
// TODO: remove this in Go 1.9
-
- fullpath := fs.absolutize(filename)
-
target, err := fs.Readlink(filename)
if err != nil {
- return os.Stat(fullpath)
+ return os.Stat(filename)
}
if !filepath.IsAbs(target) && !strings.HasPrefix(target, string(filepath.Separator)) {
- target, _ = filepath.Rel(fs.base, fs.Join(filepath.Dir(fullpath), target))
+ target = fs.Join(filepath.Dir(filename), target)
}
fi, err := fs.Stat(target)
@@ -32,7 +27,7 @@
return &fileInfo{
FileInfo: fi,
- name: filepath.Base(fullpath),
+ name: filepath.Base(filename),
}, nil
}
diff --git a/subdirfs/file.go b/subdirfs/file.go
deleted file mode 100644
index cd6f38d..0000000
--- a/subdirfs/file.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package subdirfs
-
-import (
- "io"
- "path/filepath"
-
- "gopkg.in/src-d/go-billy.v2"
-)
-
-type file struct {
- billy.BaseFile
-
- f billy.File
-}
-
-func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File {
- filename = fs.Join(fs.Base(), filename)
- filename, _ = filepath.Rel(fs.Base(), filename)
-
- return &file{
- BaseFile: billy.BaseFile{BaseFilename: filename},
- f: f,
- }
-}
-
-func (f *file) Read(p []byte) (int, error) {
- return f.f.Read(p)
-}
-
-func (f *file) ReadAt(b []byte, off int64) (int, error) {
- rf, ok := f.f.(io.ReaderAt)
- if !ok {
- return 0, billy.ErrNotSupported
- }
-
- return rf.ReadAt(b, off)
-}
-
-func (f *file) Seek(offset int64, whence int) (int64, error) {
- return f.f.Seek(offset, whence)
-}
-
-func (f *file) Write(p []byte) (int, error) {
- return f.f.Write(p)
-}
-
-func (f *file) Close() error {
- defer func() { f.Closed = true }()
- return f.f.Close()
-}
diff --git a/subdirfs/fileinfo.go b/subdirfs/fileinfo.go
deleted file mode 100644
index 4ede637..0000000
--- a/subdirfs/fileinfo.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package subdirfs
-
-import (
- "os"
- "time"
-
- "gopkg.in/src-d/go-billy.v2"
-)
-
-type fileInfo struct {
- filename string
- fi billy.FileInfo
-}
-
-func newFileInfo(filename string, fi billy.FileInfo) billy.FileInfo {
- return &fileInfo{filename, fi}
-}
-
-func (fi *fileInfo) Name() string {
- return fi.filename
-}
-func (fi *fileInfo) Size() int64 {
- return fi.fi.Size()
-}
-
-func (fi *fileInfo) Mode() os.FileMode {
- return fi.fi.Mode()
-}
-
-func (fi *fileInfo) ModTime() time.Time {
- return fi.fi.ModTime()
-}
-
-func (fi *fileInfo) IsDir() bool {
- return fi.fi.IsDir()
-}
-
-func (fi *fileInfo) Sys() interface{} {
- return fi.fi.Sys()
-}
diff --git a/subdirfs/subdir.go b/subdirfs/subdir.go
deleted file mode 100644
index 9bcdfcb..0000000
--- a/subdirfs/subdir.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package subdirfs
-
-import (
- "errors"
- "os"
- "path/filepath"
- "strings"
-
- "gopkg.in/src-d/go-billy.v2"
-)
-
-// ErrSymlinkNotSupported is returned by Symlink() and Readfile() if the
-// underlying filesystem does not support symlinking.
-var ErrSymlinkNotSupported = errors.New("symlink not supported")
-
-type subdirFs struct {
- underlying billy.Filesystem
- base string
-}
-
-// New creates a new filesystem wrapping up the given 'fs'.
-// The created filesystem has its base in the given subdirectory
-// of the underlying filesystem.
-//
-// This is particularly useful to implement the Dir method for
-// other filesystems.
-func New(fs billy.Filesystem, base string) billy.Filesystem {
- return &subdirFs{fs, base}
-}
-
-func (s *subdirFs) underlyingPath(filename string) string {
- return s.Join(s.Base(), filename)
-}
-
-func (s *subdirFs) Create(filename string) (billy.File, error) {
- f, err := s.underlying.Create(s.underlyingPath(filename))
- if err != nil {
- return nil, err
- }
-
- return newFile(s, f, filename), nil
-}
-
-func (s *subdirFs) Open(filename string) (billy.File, error) {
- f, err := s.underlying.Open(s.underlyingPath(filename))
- if err != nil {
- return nil, err
- }
-
- return newFile(s, f, filename), nil
-}
-
-func (s *subdirFs) OpenFile(filename string, flag int, mode os.FileMode) (
- billy.File, error) {
-
- f, err := s.underlying.OpenFile(s.underlyingPath(filename), flag, mode)
- if err != nil {
- return nil, err
- }
-
- return newFile(s, f, filename), nil
-}
-
-func (s *subdirFs) TempFile(dir, prefix string) (billy.File, error) {
- f, err := s.underlying.TempFile(s.underlyingPath(dir), prefix)
- if err != nil {
- return nil, err
- }
-
- return newFile(s, f, s.Join(dir, filepath.Base(f.Filename()))), nil
-}
-
-func (s *subdirFs) Rename(from, to string) error {
- return s.underlying.Rename(s.underlyingPath(from), s.underlyingPath(to))
-}
-
-func (s *subdirFs) Remove(path string) error {
- return s.underlying.Remove(s.underlyingPath(path))
-}
-
-func (s *subdirFs) MkdirAll(filename string, perm os.FileMode) error {
- fullpath := s.Join(s.base, filename)
- return s.underlying.MkdirAll(fullpath, perm)
-}
-
-func (s *subdirFs) Stat(filename string) (billy.FileInfo, error) {
- fullpath := s.underlyingPath(filename)
- fi, err := s.underlying.Stat(fullpath)
- if err != nil {
- return nil, err
- }
-
- return newFileInfo(filepath.Base(fullpath), fi), nil
-}
-
-func (s *subdirFs) Lstat(filename string) (billy.FileInfo, error) {
- fullpath := s.underlyingPath(filename)
- fi, err := s.underlying.Lstat(fullpath)
- if err != nil {
- return nil, err
- }
-
- return newFileInfo(filepath.Base(fullpath), fi), nil
-}
-
-func (s *subdirFs) ReadDir(path string) ([]billy.FileInfo, error) {
- prefix := s.underlyingPath(path)
- fis, err := s.underlying.ReadDir(prefix)
- if err != nil {
- return nil, err
- }
- for i := 0; i < len(fis); i++ {
- rn := strings.Replace(fis[i].Name(), prefix, "", 1)
- fis[i] = newFileInfo(rn, fis[i])
- }
-
- return fis, nil
-}
-
-func (s *subdirFs) Join(elem ...string) string {
- return s.underlying.Join(elem...)
-}
-
-func (s *subdirFs) Dir(path string) billy.Filesystem {
- return New(s.underlying, s.underlyingPath(path))
-}
-
-func (s *subdirFs) Base() string {
- return s.base
-}
-
-// Symlink implements billy.Symlinker.Symlink.
-func (s *subdirFs) Symlink(target, link string) error {
- target = filepath.FromSlash(target)
-
- // only rewrite target if it's already absolute
- if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) {
- target = string(os.PathSeparator) + s.underlyingPath(target)
- }
-
- link = s.underlyingPath(link)
- return s.underlying.Symlink(target, link)
-}
-
-// Readlink implements billy.Symlinker.Readlink.
-func (s *subdirFs) Readlink(link string) (string, error) {
- fullpath := s.underlyingPath(link)
- target, err := s.underlying.Readlink(fullpath)
- if err != nil {
- return "", err
- }
-
- if !filepath.IsAbs(target) && !strings.HasPrefix(target, string(filepath.Separator)) {
- return target, nil
- }
-
- base := string(os.PathSeparator) + s.base
- target, err = filepath.Rel(base, target)
- if err != nil {
- return "", err
- }
-
- return string(os.PathSeparator) + target, nil
-}
diff --git a/subdirfs/subdir_test.go b/subdirfs/subdir_test.go
deleted file mode 100644
index 6158973..0000000
--- a/subdirfs/subdir_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package subdirfs
-
-import (
- "io/ioutil"
- stdos "os"
- "testing"
-
- "gopkg.in/src-d/go-billy.v2"
- "gopkg.in/src-d/go-billy.v2/osfs"
- "gopkg.in/src-d/go-billy.v2/test"
-
- . "gopkg.in/check.v1"
-)
-
-func Test(t *testing.T) { TestingT(t) }
-
-type FilesystemSuite struct {
- test.FilesystemSuite
- cfs billy.Filesystem
- path string
-}
-
-var _ = Suite(&FilesystemSuite{})
-
-func (s *FilesystemSuite) SetUpTest(c *C) {
- s.path, _ = ioutil.TempDir(stdos.TempDir(), "go-billy-subdirfs-test")
- fs := osfs.New(s.path)
-
- s.cfs = New(fs, "test-subdir")
- s.FilesystemSuite.FS = s.cfs
-}
-
-func (s *FilesystemSuite) TearDownTest(c *C) {
- fi, err := ioutil.ReadDir(s.path)
- c.Assert(err, IsNil)
- c.Assert(len(fi) <= 1, Equals, true)
-
- err = stdos.RemoveAll(s.path)
- c.Assert(err, IsNil)
-}
diff --git a/test/basic.go b/test/basic.go
new file mode 100644
index 0000000..1a0b264
--- /dev/null
+++ b/test/basic.go
@@ -0,0 +1,564 @@
+package test
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+ . "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v3/util"
+)
+
+// BasicSuite is a convenient test suite to validate any implementation of
+// billy.Basic
+type BasicSuite struct {
+ FS Basic
+}
+
+func (s *BasicSuite) TestCreate(c *C) {
+ f, err := s.FS.Create("foo")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestCreateDepth(c *C) {
+ f, err := s.FS.Create("bar/foo")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, s.FS.Join("bar", "foo"))
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestCreateDepthAbsolute(c *C) {
+ f, err := s.FS.Create("/bar/foo")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, s.FS.Join("bar", "foo"))
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestCreateOverwrite(c *C) {
+ for i := 0; i < 3; i++ {
+ f, err := s.FS.Create("foo")
+ c.Assert(err, IsNil)
+
+ l, err := f.Write([]byte(fmt.Sprintf("foo%d", i)))
+ c.Assert(err, IsNil)
+ c.Assert(l, Equals, 4)
+
+ err = f.Close()
+ c.Assert(err, IsNil)
+ }
+
+ f, err := s.FS.Open("foo")
+ c.Assert(err, IsNil)
+
+ wrote, err := ioutil.ReadAll(f)
+ c.Assert(err, IsNil)
+ c.Assert(string(wrote), DeepEquals, "foo2")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestCreateAndClose(c *C) {
+ f, err := s.FS.Create("foo")
+ c.Assert(err, IsNil)
+
+ _, err = f.Write([]byte("foo"))
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+
+ f, err = s.FS.Open(f.Name())
+ c.Assert(err, IsNil)
+
+ wrote, err := ioutil.ReadAll(f)
+ c.Assert(err, IsNil)
+ c.Assert(string(wrote), DeepEquals, "foo")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestOpen(c *C) {
+ f, err := s.FS.Create("foo")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo")
+ c.Assert(f.Close(), IsNil)
+
+ f, err = s.FS.Open("foo")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestOpenNotExists(c *C) {
+ f, err := s.FS.Open("not-exists")
+ c.Assert(err, NotNil)
+ c.Assert(f, IsNil)
+}
+
+func (s *BasicSuite) TestOpenFile(c *C) {
+ defaultMode := os.FileMode(0666)
+
+ f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode)
+ c.Assert(err, IsNil)
+ s.testWriteClose(c, f, "foo1")
+
+ // Truncate if it exists
+ f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo1")
+ s.testWriteClose(c, f, "foo1overwritten")
+
+ // Read-only if it exists
+ f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo1")
+ s.testReadClose(c, f, "foo1overwritten")
+
+ // Create when it does exist
+ f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo1")
+ s.testWriteClose(c, f, "bar")
+
+ f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode)
+ c.Assert(err, IsNil)
+ s.testReadClose(c, f, "bar")
+}
+
+func (s *BasicSuite) TestOpenFileNoTruncate(c *C) {
+ defaultMode := os.FileMode(0666)
+
+ // Create when it does not exist
+ f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY, defaultMode)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo1")
+ s.testWriteClose(c, f, "foo1")
+
+ f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode)
+ c.Assert(err, IsNil)
+ s.testReadClose(c, f, "foo1")
+
+ // Create when it does exist
+ f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY, defaultMode)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo1")
+ s.testWriteClose(c, f, "bar")
+
+ f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode)
+ c.Assert(err, IsNil)
+ s.testReadClose(c, f, "bar1")
+}
+
+func (s *BasicSuite) TestOpenFileAppend(c *C) {
+ defaultMode := os.FileMode(0666)
+
+ f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_APPEND, defaultMode)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo1")
+ s.testWriteClose(c, f, "foo1")
+
+ f, err = s.FS.OpenFile("foo1", os.O_WRONLY|os.O_APPEND, defaultMode)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo1")
+ s.testWriteClose(c, f, "bar1")
+
+ f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode)
+ c.Assert(err, IsNil)
+ s.testReadClose(c, f, "foo1bar1")
+}
+
+func (s *BasicSuite) TestOpenFileReadWrite(c *C) {
+ defaultMode := os.FileMode(0666)
+
+ f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_TRUNC|os.O_RDWR, defaultMode)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo1")
+
+ written, err := f.Write([]byte("foobar"))
+ c.Assert(written, Equals, 6)
+ c.Assert(err, IsNil)
+
+ _, err = f.Seek(0, os.SEEK_SET)
+ c.Assert(err, IsNil)
+
+ written, err = f.Write([]byte("qux"))
+ c.Assert(written, Equals, 3)
+ c.Assert(err, IsNil)
+
+ _, err = f.Seek(0, os.SEEK_SET)
+ c.Assert(err, IsNil)
+
+ s.testReadClose(c, f, "quxbar")
+}
+
+func (s *BasicSuite) TestOpenFileWithModes(c *C) {
+ f, err := s.FS.OpenFile("foo", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, customMode)
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+
+ fi, err := s.FS.Stat("foo")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Mode(), Equals, os.FileMode(customMode))
+}
+
+func (s *BasicSuite) testWriteClose(c *C, f File, content string) {
+ written, err := f.Write([]byte(content))
+ c.Assert(written, Equals, len(content))
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) testReadClose(c *C, f File, content string) {
+ read, err := ioutil.ReadAll(f)
+ c.Assert(err, IsNil)
+ c.Assert(string(read), Equals, content)
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestFileWrite(c *C) {
+ f, err := s.FS.Create("foo")
+ c.Assert(err, IsNil)
+
+ n, err := f.Write([]byte("foo"))
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, 3)
+
+ f.Seek(0, io.SeekStart)
+ all, err := ioutil.ReadAll(f)
+ c.Assert(err, IsNil)
+ c.Assert(string(all), Equals, "foo")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestFileWriteClose(c *C) {
+ f, err := s.FS.Create("foo")
+ c.Assert(err, IsNil)
+
+ c.Assert(f.Close(), IsNil)
+
+ _, err = f.Write([]byte("foo"))
+ c.Assert(err, NotNil)
+}
+
+func (s *BasicSuite) TestFileRead(c *C) {
+ err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644)
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Open("foo")
+ c.Assert(err, IsNil)
+
+ all, err := ioutil.ReadAll(f)
+ c.Assert(err, IsNil)
+ c.Assert(string(all), Equals, "foo")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestFileClosed(c *C) {
+ err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644)
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Open("foo")
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+
+ _, err = ioutil.ReadAll(f)
+ c.Assert(err, NotNil)
+}
+
+func (s *BasicSuite) TestFileNonRead(c *C) {
+ err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644)
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.OpenFile("foo", os.O_WRONLY, 0)
+ c.Assert(err, IsNil)
+
+ _, err = ioutil.ReadAll(f)
+ c.Assert(err, NotNil)
+
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestFileSeekstart(c *C) {
+ s.testFileSeek(c, 10, io.SeekStart)
+}
+
+func (s *BasicSuite) TestFileSeekCurrent(c *C) {
+ s.testFileSeek(c, 5, io.SeekCurrent)
+}
+
+func (s *BasicSuite) TestFileSeekEnd(c *C) {
+ s.testFileSeek(c, -26, io.SeekEnd)
+}
+
+func (s *BasicSuite) testFileSeek(c *C, offset int64, whence int) {
+ err := util.WriteFile(s.FS, "foo", []byte("0123456789abcdefghijklmnopqrstuvwxyz"), 0644)
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Open("foo")
+ c.Assert(err, IsNil)
+
+ some := make([]byte, 5)
+ _, err = f.Read(some)
+ c.Assert(err, IsNil)
+ c.Assert(string(some), Equals, "01234")
+
+ p, err := f.Seek(offset, whence)
+ c.Assert(err, IsNil)
+ c.Assert(int(p), Equals, 10)
+
+ all, err := ioutil.ReadAll(f)
+ c.Assert(err, IsNil)
+ c.Assert(all, HasLen, 26)
+ c.Assert(string(all), Equals, "abcdefghijklmnopqrstuvwxyz")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestSeekToEndAndWrite(c *C) {
+ defaultMode := os.FileMode(0666)
+
+ f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_TRUNC|os.O_RDWR, defaultMode)
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "foo1")
+
+ _, err = f.Seek(10, io.SeekEnd)
+ c.Assert(err, IsNil)
+
+ n, err := f.Write([]byte(`TEST`))
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, 4)
+
+ _, err = f.Seek(0, io.SeekStart)
+ c.Assert(err, IsNil)
+
+ s.testReadClose(c, f, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TEST")
+}
+
+func (s *BasicSuite) TestFileSeekClosed(c *C) {
+ err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644)
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Open("foo")
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+
+ _, err = f.Seek(0, 0)
+ c.Assert(err, NotNil)
+}
+
+func (s *BasicSuite) TestFileCloseTwice(c *C) {
+ f, err := s.FS.Create("foo")
+ c.Assert(err, IsNil)
+
+ c.Assert(f.Close(), IsNil)
+ c.Assert(f.Close(), NotNil)
+}
+
+func (s *BasicSuite) TestStat(c *C) {
+ util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode)
+
+ fi, err := s.FS.Stat("foo/bar")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "bar")
+ c.Assert(fi.Size(), Equals, int64(3))
+ c.Assert(fi.Mode(), Equals, customMode)
+ c.Assert(fi.ModTime().IsZero(), Equals, false)
+ c.Assert(fi.IsDir(), Equals, false)
+}
+
+func (s *BasicSuite) TestStatNonExistent(c *C) {
+ fi, err := s.FS.Stat("non-existent")
+ comment := Commentf("error: %s", err)
+ c.Assert(os.IsNotExist(err), Equals, true, comment)
+ c.Assert(fi, IsNil)
+}
+
+func (s *BasicSuite) TestRename(c *C) {
+ err := util.WriteFile(s.FS, "foo", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Rename("foo", "bar")
+ c.Assert(err, IsNil)
+
+ foo, err := s.FS.Stat("foo")
+ c.Assert(foo, IsNil)
+ c.Assert(os.IsNotExist(err), Equals, true)
+
+ bar, err := s.FS.Stat("bar")
+ c.Assert(err, IsNil)
+ c.Assert(bar, NotNil)
+}
+
+func (s *BasicSuite) TestOpenAndWrite(c *C) {
+ err := util.WriteFile(s.FS, "foo", nil, 0644)
+ c.Assert(err, IsNil)
+
+ foo, err := s.FS.Open("foo")
+ c.Assert(foo, NotNil)
+ c.Assert(err, IsNil)
+
+ n, err := foo.Write([]byte("foo"))
+ c.Assert(err, NotNil)
+ c.Assert(n, Equals, 0)
+
+ c.Assert(foo.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestOpenAndStat(c *C) {
+ err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644)
+ c.Assert(err, IsNil)
+
+ foo, err := s.FS.Open("foo")
+ c.Assert(foo, NotNil)
+ c.Assert(foo.Name(), Equals, "foo")
+ c.Assert(err, IsNil)
+ c.Assert(foo.Close(), IsNil)
+
+ stat, err := s.FS.Stat("foo")
+ c.Assert(stat, NotNil)
+ c.Assert(err, IsNil)
+ c.Assert(stat.Name(), Equals, "foo")
+ c.Assert(stat.Size(), Equals, int64(3))
+}
+
+func (s *BasicSuite) TestRemove(c *C) {
+ f, err := s.FS.Create("foo")
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+
+ err = s.FS.Remove("foo")
+ c.Assert(err, IsNil)
+}
+
+func (s *BasicSuite) TestRemoveNonExisting(c *C) {
+ err := s.FS.Remove("NON-EXISTING")
+ c.Assert(err, NotNil)
+ c.Assert(os.IsNotExist(err), Equals, true)
+}
+
+func (s *BasicSuite) TestRemoveNotEmptyDir(c *C) {
+ err := util.WriteFile(s.FS, "foo", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Remove("no-exists")
+ c.Assert(err, NotNil)
+}
+
+func (s *BasicSuite) TestJoin(c *C) {
+ c.Assert(s.FS.Join("foo", "bar"), Equals, fmt.Sprintf("foo%cbar", filepath.Separator))
+}
+
+func (s *BasicSuite) TestReadAtOnReadWrite(c *C) {
+ f, err := s.FS.Create("foo")
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("abcdefg"))
+ c.Assert(err, IsNil)
+
+ rf, ok := f.(io.ReaderAt)
+ c.Assert(ok, Equals, true)
+
+ b := make([]byte, 3)
+ n, err := rf.ReadAt(b, 2)
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, 3)
+ c.Assert(string(b), Equals, "cde")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestReadAtOnReadOnly(c *C) {
+ err := util.WriteFile(s.FS, "foo", []byte("abcdefg"), 0644)
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Open("foo")
+ c.Assert(err, IsNil)
+
+ rf, ok := f.(io.ReaderAt)
+ c.Assert(ok, Equals, true)
+
+ b := make([]byte, 3)
+ n, err := rf.ReadAt(b, 2)
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, 3)
+ c.Assert(string(b), Equals, "cde")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestReadAtEOF(c *C) {
+ err := util.WriteFile(s.FS, "foo", []byte("TEST"), 0644)
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Open("foo")
+ c.Assert(err, IsNil)
+
+ b := make([]byte, 5)
+ n, err := f.ReadAt(b, 0)
+ c.Assert(err, Equals, io.EOF)
+ c.Assert(n, Equals, 4)
+ c.Assert(string(b), Equals, "TEST\x00")
+
+ err = f.Close()
+ c.Assert(err, IsNil)
+}
+
+func (s *BasicSuite) TestReadAtOffset(c *C) {
+ err := util.WriteFile(s.FS, "foo", []byte("TEST"), 0644)
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Open("foo")
+ c.Assert(err, IsNil)
+
+ rf, ok := f.(io.ReaderAt)
+ c.Assert(ok, Equals, true)
+
+ o, err := f.Seek(0, io.SeekCurrent)
+ c.Assert(err, IsNil)
+ c.Assert(o, Equals, int64(0))
+
+ b := make([]byte, 4)
+ n, err := rf.ReadAt(b, 0)
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, 4)
+ c.Assert(string(b), Equals, "TEST")
+
+ o, err = f.Seek(0, io.SeekCurrent)
+ c.Assert(err, IsNil)
+ c.Assert(o, Equals, int64(0))
+
+ err = f.Close()
+ c.Assert(err, IsNil)
+}
+
+func (s *BasicSuite) TestReadWriteLargeFile(c *C) {
+ f, err := s.FS.Create("foo")
+ c.Assert(err, IsNil)
+
+ size := 1 << 20
+
+ n, err := f.Write(bytes.Repeat([]byte("F"), size))
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, size)
+
+ c.Assert(f.Close(), IsNil)
+
+ f, err = s.FS.Open("foo")
+ c.Assert(err, IsNil)
+ b, err := ioutil.ReadAll(f)
+ c.Assert(err, IsNil)
+ c.Assert(len(b), Equals, size)
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *BasicSuite) TestWriteFile(c *C) {
+ err := util.WriteFile(s.FS, "foo", []byte("bar"), 0777)
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Open("foo")
+ c.Assert(err, IsNil)
+
+ wrote, err := ioutil.ReadAll(f)
+ c.Assert(err, IsNil)
+ c.Assert(string(wrote), DeepEquals, "bar")
+
+ c.Assert(f.Close(), IsNil)
+}
diff --git a/test/chroot.go b/test/chroot.go
new file mode 100644
index 0000000..70bfac6
--- /dev/null
+++ b/test/chroot.go
@@ -0,0 +1,123 @@
+package test
+
+import (
+ "os"
+
+ . "gopkg.in/check.v1"
+ . "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v3/util"
+)
+
+// ChrootSuite is a convenient test suite to validate any implementation of
+// billy.Chroot
+type ChrootSuite struct {
+ FS interface {
+ Basic
+ Chroot
+ }
+}
+
+func (s *ChrootSuite) TestCreateWithChroot(c *C) {
+ fs, _ := s.FS.Chroot("foo")
+ f, err := fs.Create("bar")
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+ c.Assert(f.Name(), Equals, "bar")
+
+ f, err = s.FS.Open("foo/bar")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, s.FS.Join("foo", "bar"))
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *ChrootSuite) TestOpenWithChroot(c *C) {
+ fs, _ := s.FS.Chroot("foo")
+ f, err := fs.Create("bar")
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+ c.Assert(f.Name(), Equals, "bar")
+
+ f, err = fs.Open("bar")
+ c.Assert(err, IsNil)
+ c.Assert(f.Name(), Equals, "bar")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *ChrootSuite) TestOpenOutOffBoundary(c *C) {
+ err := util.WriteFile(s.FS, "bar", nil, 0644)
+ c.Assert(err, IsNil)
+
+ fs, _ := s.FS.Chroot("foo")
+ f, err := fs.Open("../bar")
+ c.Assert(err, Equals, ErrCrossedBoundary)
+ c.Assert(f, IsNil)
+}
+
+func (s *ChrootSuite) TestStatOutOffBoundary(c *C) {
+ err := util.WriteFile(s.FS, "bar", nil, 0644)
+ c.Assert(err, IsNil)
+
+ fs, _ := s.FS.Chroot("foo")
+ f, err := fs.Stat("../bar")
+ c.Assert(err, Equals, ErrCrossedBoundary)
+ c.Assert(f, IsNil)
+}
+
+func (s *ChrootSuite) TestStatWithChroot(c *C) {
+ files := []string{"foo", "bar", "qux/baz", "qux/qux"}
+ for _, name := range files {
+ err := util.WriteFile(s.FS, name, nil, 0644)
+ c.Assert(err, IsNil)
+ }
+
+ // Some implementations detect directories based on a prefix
+ // for all files; it's easy to miss path separator handling there.
+ fi, err := s.FS.Stat("qu")
+ c.Assert(os.IsNotExist(err), Equals, true, Commentf("error: %s", err))
+ c.Assert(fi, IsNil)
+
+ fi, err = s.FS.Stat("qux")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "qux")
+ c.Assert(fi.IsDir(), Equals, true)
+
+ qux, _ := s.FS.Chroot("qux")
+
+ fi, err = qux.Stat("baz")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "baz")
+ c.Assert(fi.IsDir(), Equals, false)
+
+ fi, err = qux.Stat("/baz")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "baz")
+ c.Assert(fi.IsDir(), Equals, false)
+}
+
+func (s *ChrootSuite) TestRenameOutOffBoundary(c *C) {
+ err := util.WriteFile(s.FS, "foo/foo", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = util.WriteFile(s.FS, "bar", nil, 0644)
+ c.Assert(err, IsNil)
+
+ fs, _ := s.FS.Chroot("foo")
+ err = fs.Rename("../bar", "foo")
+ c.Assert(err, Equals, ErrCrossedBoundary)
+
+ err = fs.Rename("foo", "../bar")
+ c.Assert(err, Equals, ErrCrossedBoundary)
+}
+
+func (s *ChrootSuite) TestRemoveOutOffBoundary(c *C) {
+ err := util.WriteFile(s.FS, "bar", nil, 0644)
+ c.Assert(err, IsNil)
+
+ fs, _ := s.FS.Chroot("foo")
+ err = fs.Remove("../bar")
+ c.Assert(err, Equals, ErrCrossedBoundary)
+}
+
+func (s *FilesystemSuite) TestRoot(c *C) {
+ c.Assert(s.FS.Root(), Not(Equals), "")
+}
diff --git a/test/fs_suite_linux.go b/test/common_posix.go
similarity index 100%
rename from test/fs_suite_linux.go
rename to test/common_posix.go
diff --git a/test/fs_suite_windows.go b/test/common_windows.go
similarity index 100%
rename from test/fs_suite_windows.go
rename to test/common_windows.go
diff --git a/test/dir.go b/test/dir.go
new file mode 100644
index 0000000..c7ca1ad
--- /dev/null
+++ b/test/dir.go
@@ -0,0 +1,238 @@
+package test
+
+import (
+ "os"
+
+ . "gopkg.in/check.v1"
+ . "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v3/util"
+)
+
+// DirSuite is a convenient test suite to validate any implementation of
+// billy.Dir
+type DirSuite struct {
+ FS interface {
+ Basic
+ Dir
+ }
+}
+
+func (s *DirSuite) TestMkdirAll(c *C) {
+ err := s.FS.MkdirAll("empty", os.FileMode(0755))
+ c.Assert(err, IsNil)
+
+ fi, err := s.FS.Stat("empty")
+ c.Assert(err, IsNil)
+ c.Assert(fi.IsDir(), Equals, true)
+}
+
+func (s *DirSuite) TestMkdirAllNested(c *C) {
+ err := s.FS.MkdirAll("foo/bar/baz", os.FileMode(0755))
+ c.Assert(err, IsNil)
+
+ fi, err := s.FS.Stat("foo/bar/baz")
+ c.Assert(err, IsNil)
+ c.Assert(fi.IsDir(), Equals, true)
+
+ fi, err = s.FS.Stat("foo/bar")
+ c.Assert(err, IsNil)
+ c.Assert(fi.IsDir(), Equals, true)
+
+ fi, err = s.FS.Stat("foo")
+ c.Assert(err, IsNil)
+ c.Assert(fi.IsDir(), Equals, true)
+}
+
+func (s *DirSuite) TestMkdirAllIdempotent(c *C) {
+ err := s.FS.MkdirAll("empty", 0755)
+ c.Assert(err, IsNil)
+ fi, err := s.FS.Stat("empty")
+ c.Assert(err, IsNil)
+ c.Assert(fi.IsDir(), Equals, true)
+
+ // idempotent
+ err = s.FS.MkdirAll("empty", 0755)
+ c.Assert(err, IsNil)
+ fi, err = s.FS.Stat("empty")
+ c.Assert(err, IsNil)
+ c.Assert(fi.IsDir(), Equals, true)
+}
+
+func (s *DirSuite) TestMkdirAllAndCreate(c *C) {
+ err := s.FS.MkdirAll("dir", os.FileMode(0755))
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Create("dir/bar/foo")
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+
+ fi, err := s.FS.Stat("dir/bar/foo")
+ c.Assert(err, IsNil)
+ c.Assert(fi.IsDir(), Equals, false)
+}
+
+func (s *DirSuite) TestMkdirAllWithExistingFile(c *C) {
+ f, err := s.FS.Create("dir/foo")
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+
+ err = s.FS.MkdirAll("dir/foo", os.FileMode(0755))
+ c.Assert(err, NotNil)
+
+ fi, err := s.FS.Stat("dir/foo")
+ c.Assert(err, IsNil)
+ c.Assert(fi.IsDir(), Equals, false)
+}
+
+func (s *DirSuite) TestStatDir(c *C) {
+ s.FS.MkdirAll("foo/bar", 0644)
+
+ fi, err := s.FS.Stat("foo/bar")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "bar")
+ c.Assert(fi.Mode().IsDir(), Equals, true)
+ c.Assert(fi.ModTime().IsZero(), Equals, false)
+ c.Assert(fi.IsDir(), Equals, true)
+}
+
+func (s *BasicSuite) TestStatDeep(c *C) {
+ files := []string{"foo", "bar", "qux/baz", "qux/qux"}
+ for _, name := range files {
+ err := util.WriteFile(s.FS, name, nil, 0644)
+ c.Assert(err, IsNil)
+ }
+
+ // Some implementations detect directories based on a prefix
+ // for all files; it's easy to miss path separator handling there.
+ fi, err := s.FS.Stat("qu")
+ c.Assert(os.IsNotExist(err), Equals, true, Commentf("error: %s", err))
+ c.Assert(fi, IsNil)
+
+ fi, err = s.FS.Stat("qux")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "qux")
+ c.Assert(fi.IsDir(), Equals, true)
+
+ fi, err = s.FS.Stat("qux/baz")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "baz")
+ c.Assert(fi.IsDir(), Equals, false)
+}
+
+func (s *DirSuite) TestReadDir(c *C) {
+ files := []string{"foo", "bar", "qux/baz", "qux/qux"}
+ for _, name := range files {
+ err := util.WriteFile(s.FS, name, nil, 0644)
+ c.Assert(err, IsNil)
+ }
+
+ info, err := s.FS.ReadDir("/")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 3)
+
+ info, err = s.FS.ReadDir("/qux")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 2)
+}
+
+func (s *DirSuite) TestReadDirWithMkDirAll(c *C) {
+ err := s.FS.MkdirAll("qux", 0644)
+ c.Assert(err, IsNil)
+
+ files := []string{"qux/baz", "qux/qux"}
+ for _, name := range files {
+ err := util.WriteFile(s.FS, name, nil, 0644)
+ c.Assert(err, IsNil)
+ }
+
+ info, err := s.FS.ReadDir("/")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 1)
+ c.Assert(info[0].IsDir(), Equals, true)
+
+ info, err = s.FS.ReadDir("/qux")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 2)
+}
+
+func (s *DirSuite) TestReadDirFileInfo(c *C) {
+ err := util.WriteFile(s.FS, "foo", []byte{'F', 'O', 'O'}, 0644)
+ c.Assert(err, IsNil)
+
+ info, err := s.FS.ReadDir("/")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 1)
+
+ c.Assert(info[0].Size(), Equals, int64(3))
+ c.Assert(info[0].IsDir(), Equals, false)
+ c.Assert(info[0].Name(), Equals, "foo")
+}
+
+func (s *DirSuite) TestReadDirFileInfoDirs(c *C) {
+ files := []string{"qux/baz/foo"}
+ for _, name := range files {
+ err := util.WriteFile(s.FS, name, []byte{'F', 'O', 'O'}, 0644)
+ c.Assert(err, IsNil)
+ }
+
+ info, err := s.FS.ReadDir("qux")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 1)
+ c.Assert(info[0].IsDir(), Equals, true)
+ c.Assert(info[0].Name(), Equals, "baz")
+
+ info, err = s.FS.ReadDir("qux/baz")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 1)
+ c.Assert(info[0].Size(), Equals, int64(3))
+ c.Assert(info[0].IsDir(), Equals, false)
+ c.Assert(info[0].Name(), Equals, "foo")
+ c.Assert(info[0].Mode(), Not(Equals), 0)
+}
+
+func (s *DirSuite) TestRenameToDir(c *C) {
+ err := util.WriteFile(s.FS, "foo", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Rename("foo", "bar/qux")
+ c.Assert(err, IsNil)
+
+ old, err := s.FS.Stat("foo")
+ c.Assert(old, IsNil)
+ c.Assert(os.IsNotExist(err), Equals, true)
+
+ dir, err := s.FS.Stat("bar")
+ c.Assert(dir, NotNil)
+ c.Assert(err, IsNil)
+
+ file, err := s.FS.Stat("bar/qux")
+ c.Assert(file.Name(), Equals, "qux")
+ c.Assert(err, IsNil)
+}
+
+func (s *DirSuite) TestRenameDir(c *C) {
+ err := s.FS.MkdirAll("foo", 0644)
+ c.Assert(err, IsNil)
+
+ err = util.WriteFile(s.FS, "foo/bar", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Rename("foo", "bar")
+ c.Assert(err, IsNil)
+
+ dirfoo, err := s.FS.Stat("foo")
+ c.Assert(dirfoo, IsNil)
+ c.Assert(os.IsNotExist(err), Equals, true)
+
+ dirbar, err := s.FS.Stat("bar")
+ c.Assert(err, IsNil)
+ c.Assert(dirbar, NotNil)
+
+ foo, err := s.FS.Stat("foo/bar")
+ c.Assert(os.IsNotExist(err), Equals, true)
+ c.Assert(foo, IsNil)
+
+ bar, err := s.FS.Stat("bar/bar")
+ c.Assert(err, IsNil)
+ c.Assert(bar, NotNil)
+}
diff --git a/test/fs.go b/test/fs.go
new file mode 100644
index 0000000..e135e1c
--- /dev/null
+++ b/test/fs.go
@@ -0,0 +1,198 @@
+package test
+
+import (
+ "os"
+
+ . "gopkg.in/check.v1"
+ . "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v3/util"
+)
+
+// FilesystemSuite is a convenient test suite to validate any implementation of
+// billy.Filesystem
+type FilesystemSuite struct {
+ FS Filesystem
+
+ BasicSuite
+ DirSuite
+ SymlinkSuite
+ TempFileSuite
+ ChrootSuite
+}
+
+// NewFilesystemSuite returns a new FilesystemSuite based on the given fs.
+func NewFilesystemSuite(fs Filesystem) FilesystemSuite {
+ s := FilesystemSuite{FS: fs}
+ s.BasicSuite.FS = s.FS
+ s.DirSuite.FS = s.FS
+ s.SymlinkSuite.FS = s.FS
+ s.TempFileSuite.FS = s.FS
+ s.ChrootSuite.FS = s.FS
+
+ return s
+}
+
+func (s *FilesystemSuite) TestSymlinkToDir(c *C) {
+ err := s.FS.MkdirAll("dir", 0755)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("dir", "link")
+ c.Assert(err, IsNil)
+
+ fi, err := s.FS.Stat("link")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "link")
+ c.Assert(fi.IsDir(), Equals, true)
+}
+
+func (s *FilesystemSuite) TestSymlinkReadDir(c *C) {
+ err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("dir", "link")
+ c.Assert(err, IsNil)
+
+ info, err := s.FS.ReadDir("link")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 1)
+
+ c.Assert(info[0].Size(), Equals, int64(3))
+ c.Assert(info[0].IsDir(), Equals, false)
+ c.Assert(info[0].Name(), Equals, "file")
+}
+
+func (s *FilesystemSuite) TestCreateWithExistantDir(c *C) {
+ err := s.FS.MkdirAll("foo", 0644)
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Create("foo")
+ c.Assert(err, NotNil)
+ c.Assert(f, IsNil)
+}
+
+func (s *ChrootSuite) TestReadDirWithChroot(c *C) {
+ files := []string{"foo", "bar", "qux/baz", "qux/qux"}
+ for _, name := range files {
+ err := util.WriteFile(s.FS, name, nil, 0644)
+ c.Assert(err, IsNil)
+ }
+
+ qux, _ := s.FS.Chroot("/qux")
+
+ info, err := qux.(Filesystem).ReadDir("/")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 2)
+}
+
+func (s *FilesystemSuite) TestSymlinkWithChrootBasic(c *C) {
+ qux, _ := s.FS.Chroot("/qux")
+
+ err := util.WriteFile(qux, "file", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = qux.(Filesystem).Symlink("file", "link")
+ c.Assert(err, IsNil)
+
+ fi, err := qux.Stat("link")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "link")
+
+ fi, err = s.FS.Stat("qux/link")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "link")
+}
+
+func (s *FilesystemSuite) TestSymlinkWithChrootCrossBounders(c *C) {
+ qux, _ := s.FS.Chroot("/qux")
+ err := qux.(Filesystem).Symlink("../../file", "qux/link")
+ c.Assert(err, Equals, ErrCrossedBoundary)
+}
+
+func (s *FilesystemSuite) TestReadDirWithLink(c *C) {
+ util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode)
+ s.FS.Symlink("bar", "foo/qux")
+
+ info, err := s.FS.ReadDir("/foo")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 2)
+}
+
+func (s *FilesystemSuite) TestRemoveAllNonExistent(c *C) {
+ c.Assert(util.RemoveAll(s.FS, "non-existent"), IsNil)
+}
+
+func (s *FilesystemSuite) TestRemoveAllEmptyDir(c *C) {
+ c.Assert(s.FS.MkdirAll("empty", os.FileMode(0755)), IsNil)
+ c.Assert(util.RemoveAll(s.FS, "empty"), IsNil)
+ _, err := s.FS.Stat("empty")
+ c.Assert(err, NotNil)
+ c.Assert(os.IsNotExist(err), Equals, true)
+}
+
+func (s *FilesystemSuite) TestRemoveAll(c *C) {
+ fnames := []string{
+ "foo/1",
+ "foo/2",
+ "foo/bar/1",
+ "foo/bar/2",
+ "foo/bar/baz/1",
+ "foo/bar/baz/qux/1",
+ "foo/bar/baz/qux/2",
+ "foo/bar/baz/qux/3",
+ }
+
+ for _, fname := range fnames {
+ err := util.WriteFile(s.FS, fname, nil, 0644)
+ c.Assert(err, IsNil)
+ }
+
+ c.Assert(util.RemoveAll(s.FS, "foo"), IsNil)
+
+ for _, fname := range fnames {
+ _, err := s.FS.Stat(fname)
+ comment := Commentf("not removed: %s %s", fname, err)
+ c.Assert(os.IsNotExist(err), Equals, true, comment)
+ }
+}
+
+func (s *FilesystemSuite) TestRemoveAllRelative(c *C) {
+ fnames := []string{
+ "foo/1",
+ "foo/2",
+ "foo/bar/1",
+ "foo/bar/2",
+ "foo/bar/baz/1",
+ "foo/bar/baz/qux/1",
+ "foo/bar/baz/qux/2",
+ "foo/bar/baz/qux/3",
+ }
+
+ for _, fname := range fnames {
+ err := util.WriteFile(s.FS, fname, nil, 0644)
+ c.Assert(err, IsNil)
+ }
+
+ c.Assert(util.RemoveAll(s.FS, "foo/bar/.."), IsNil)
+
+ for _, fname := range fnames {
+ _, err := s.FS.Stat(fname)
+ comment := Commentf("not removed: %s %s", fname, err)
+ c.Assert(os.IsNotExist(err), Equals, true, comment)
+ }
+}
+
+func (s *FilesystemSuite) TestReadDir(c *C) {
+ files := []string{"foo", "bar", "qux/baz", "qux/qux"}
+ for _, name := range files {
+ err := util.WriteFile(s.FS, name, nil, 0644)
+ c.Assert(err, IsNil)
+ }
+
+ info, err := s.FS.ReadDir("/")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 3)
+
+ info, err = s.FS.ReadDir("/qux")
+ c.Assert(err, IsNil)
+ c.Assert(info, HasLen, 2)
+}
diff --git a/test/fs_suite.go b/test/fs_suite.go
deleted file mode 100644
index 9983f78..0000000
--- a/test/fs_suite.go
+++ /dev/null
@@ -1,1150 +0,0 @@
-package test
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "testing"
-
- . "gopkg.in/check.v1"
- . "gopkg.in/src-d/go-billy.v2"
-)
-
-func Test(t *testing.T) { TestingT(t) }
-
-type FilesystemSuite struct {
- FS Filesystem
-}
-
-func (s *FilesystemSuite) TestCreate(c *C) {
- f, err := s.FS.Create("foo")
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestOpen(c *C) {
- f, err := s.FS.Create("foo")
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo")
- c.Assert(f.Close(), IsNil)
-
- f, err = s.FS.Open("foo")
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestOpenNotExists(c *C) {
- f, err := s.FS.Open("not-exists")
- c.Assert(err, NotNil)
- c.Assert(f, IsNil)
-}
-
-func (s *FilesystemSuite) TestCreateDir(c *C) {
- err := s.FS.MkdirAll("foo", 0644)
- c.Assert(err, IsNil)
-
- f, err := s.FS.Create("foo")
- c.Assert(err, NotNil)
- c.Assert(f, IsNil)
-}
-
-func (s *FilesystemSuite) TestCreateDepth(c *C) {
- f, err := s.FS.Create("bar/foo")
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, s.FS.Join("bar", "foo"))
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestCreateDepthAbsolute(c *C) {
- f, err := s.FS.Create("/bar/foo")
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, s.FS.Join("bar", "foo"))
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestCreateOverwrite(c *C) {
- for i := 0; i < 3; i++ {
- f, err := s.FS.Create("foo")
- c.Assert(err, IsNil)
-
- l, err := f.Write([]byte(fmt.Sprintf("foo%d", i)))
- c.Assert(err, IsNil)
- c.Assert(l, Equals, 4)
-
- err = f.Close()
- c.Assert(err, IsNil)
- }
-
- f, err := s.FS.Open("foo")
- c.Assert(err, IsNil)
-
- wrote, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(string(wrote), DeepEquals, "foo2")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestCreateClose(c *C) {
- f, err := s.FS.Create("foo")
- c.Assert(err, IsNil)
- c.Assert(f.IsClosed(), Equals, false)
-
- _, err = f.Write([]byte("foo"))
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- f, err = s.FS.Open(f.Filename())
- c.Assert(err, IsNil)
-
- wrote, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(string(wrote), DeepEquals, "foo")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestOpenFile(c *C) {
- defaultMode := os.FileMode(0666)
-
- f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode)
- c.Assert(err, IsNil)
- s.testWriteClose(c, f, "foo1")
-
- // Truncate if it exists
- f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo1")
- s.testWriteClose(c, f, "foo1overwritten")
-
- // Read-only if it exists
- f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo1")
- s.testReadClose(c, f, "foo1overwritten")
-
- // Create when it does exist
- f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo1")
- s.testWriteClose(c, f, "bar")
-
- f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode)
- c.Assert(err, IsNil)
- s.testReadClose(c, f, "bar")
-}
-
-func (s *FilesystemSuite) TestOpenFileNoTruncate(c *C) {
- defaultMode := os.FileMode(0666)
-
- // Create when it does not exist
- f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY, defaultMode)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo1")
- s.testWriteClose(c, f, "foo1")
-
- f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode)
- c.Assert(err, IsNil)
- s.testReadClose(c, f, "foo1")
-
- // Create when it does exist
- f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY, defaultMode)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo1")
- s.testWriteClose(c, f, "bar")
-
- f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode)
- c.Assert(err, IsNil)
- s.testReadClose(c, f, "bar1")
-}
-
-func (s *FilesystemSuite) TestOpenFileAppend(c *C) {
- defaultMode := os.FileMode(0666)
-
- f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_APPEND, defaultMode)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo1")
- s.testWriteClose(c, f, "foo1")
-
- f, err = s.FS.OpenFile("foo1", os.O_WRONLY|os.O_APPEND, defaultMode)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo1")
- s.testWriteClose(c, f, "bar1")
-
- f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode)
- c.Assert(err, IsNil)
- s.testReadClose(c, f, "foo1bar1")
-}
-
-func (s *FilesystemSuite) TestOpenFileReadWrite(c *C) {
- defaultMode := os.FileMode(0666)
-
- f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_TRUNC|os.O_RDWR, defaultMode)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo1")
-
- written, err := f.Write([]byte("foobar"))
- c.Assert(written, Equals, 6)
- c.Assert(err, IsNil)
-
- _, err = f.Seek(0, os.SEEK_SET)
- c.Assert(err, IsNil)
-
- written, err = f.Write([]byte("qux"))
- c.Assert(written, Equals, 3)
- c.Assert(err, IsNil)
-
- _, err = f.Seek(0, os.SEEK_SET)
- c.Assert(err, IsNil)
-
- s.testReadClose(c, f, "quxbar")
-}
-
-func (s *FilesystemSuite) TestOpenFileWithModes(c *C) {
- f, err := s.FS.OpenFile("foo", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, customMode)
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- fi, err := s.FS.Stat("foo")
- c.Assert(err, IsNil)
- c.Assert(fi.Mode(), Equals, os.FileMode(customMode))
-}
-
-func (s *FilesystemSuite) testWriteClose(c *C, f File, content string) {
- written, err := f.Write([]byte(content))
- c.Assert(written, Equals, len(content))
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) testReadClose(c *C, f File, content string) {
- read, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(string(read), Equals, content)
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestFileWrite(c *C) {
- f, err := s.FS.Create("foo")
- c.Assert(err, IsNil)
-
- n, err := f.Write([]byte("foo"))
- c.Assert(err, IsNil)
- c.Assert(n, Equals, 3)
-
- f.Seek(0, io.SeekStart)
- all, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(string(all), Equals, "foo")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestFileWriteClose(c *C) {
- f, err := s.FS.Create("foo")
- c.Assert(err, IsNil)
-
- c.Assert(f.Close(), IsNil)
-
- _, err = f.Write([]byte("foo"))
- c.Assert(err, NotNil)
-}
-
-func (s *FilesystemSuite) TestFileRead(c *C) {
- err := WriteFile(s.FS, "foo", []byte("foo"), 0644)
- c.Assert(err, IsNil)
-
- f, err := s.FS.Open("foo")
- c.Assert(err, IsNil)
-
- all, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(string(all), Equals, "foo")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestFileClosed(c *C) {
- err := WriteFile(s.FS, "foo", []byte("foo"), 0644)
- c.Assert(err, IsNil)
-
- f, err := s.FS.Open("foo")
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- _, err = ioutil.ReadAll(f)
- c.Assert(err, NotNil)
-}
-
-func (s *FilesystemSuite) TestFileNonRead(c *C) {
- err := WriteFile(s.FS, "foo", []byte("foo"), 0644)
- c.Assert(err, IsNil)
-
- f, err := s.FS.OpenFile("foo", os.O_WRONLY, 0)
- c.Assert(err, IsNil)
-
- _, err = ioutil.ReadAll(f)
- c.Assert(err, NotNil)
-
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestFileSeekstart(c *C) {
- s.testFileSeek(c, 10, io.SeekStart)
-}
-
-func (s *FilesystemSuite) TestFileSeekCurrent(c *C) {
- s.testFileSeek(c, 5, io.SeekCurrent)
-}
-
-func (s *FilesystemSuite) TestFileSeekEnd(c *C) {
- s.testFileSeek(c, -26, io.SeekEnd)
-}
-
-func (s *FilesystemSuite) testFileSeek(c *C, offset int64, whence int) {
- err := WriteFile(s.FS, "foo", []byte("0123456789abcdefghijklmnopqrstuvwxyz"), 0644)
- c.Assert(err, IsNil)
-
- f, err := s.FS.Open("foo")
- c.Assert(err, IsNil)
-
- some := make([]byte, 5)
- _, err = f.Read(some)
- c.Assert(err, IsNil)
- c.Assert(string(some), Equals, "01234")
-
- p, err := f.Seek(offset, whence)
- c.Assert(err, IsNil)
- c.Assert(int(p), Equals, 10)
-
- all, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(all, HasLen, 26)
- c.Assert(string(all), Equals, "abcdefghijklmnopqrstuvwxyz")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestSeekToEndAndWrite(c *C) {
- defaultMode := os.FileMode(0666)
-
- f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_TRUNC|os.O_RDWR, defaultMode)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, "foo1")
-
- _, err = f.Seek(10, io.SeekEnd)
- c.Assert(err, IsNil)
-
- n, err := f.Write([]byte(`TEST`))
- c.Assert(err, IsNil)
- c.Assert(n, Equals, 4)
-
- _, err = f.Seek(0, io.SeekStart)
- c.Assert(err, IsNil)
-
- s.testReadClose(c, f, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TEST")
-}
-
-func (s *FilesystemSuite) TestFileSeekClosed(c *C) {
- err := WriteFile(s.FS, "foo", []byte("foo"), 0644)
- c.Assert(err, IsNil)
-
- f, err := s.FS.Open("foo")
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- _, err = f.Seek(0, 0)
- c.Assert(err, NotNil)
-}
-
-func (s *FilesystemSuite) TestFileCloseTwice(c *C) {
- f, err := s.FS.Create("foo")
- c.Assert(err, IsNil)
-
- c.Assert(f.Close(), IsNil)
- c.Assert(f.Close(), NotNil)
-}
-
-func (s *FilesystemSuite) TestReadDirAndDir(c *C) {
- files := []string{"foo", "bar", "qux/baz", "qux/qux"}
- for _, name := range files {
- err := WriteFile(s.FS, name, nil, 0644)
- c.Assert(err, IsNil)
- }
-
- info, err := s.FS.ReadDir("/")
- c.Assert(err, IsNil)
- c.Assert(info, HasLen, 3)
-
- info, err = s.FS.ReadDir("/qux")
- c.Assert(err, IsNil)
- c.Assert(info, HasLen, 2)
-
- qux := s.FS.Dir("/qux")
- info, err = qux.ReadDir("/")
- c.Assert(err, IsNil)
- c.Assert(info, HasLen, 2)
-}
-
-func (s *FilesystemSuite) TestReadDirAndWithMkDirAll(c *C) {
- err := s.FS.MkdirAll("qux", 0644)
- c.Assert(err, IsNil)
-
- files := []string{"qux/baz", "qux/qux"}
- for _, name := range files {
- err := WriteFile(s.FS, name, nil, 0644)
- c.Assert(err, IsNil)
- }
-
- info, err := s.FS.ReadDir("/")
- c.Assert(err, IsNil)
- c.Assert(info, HasLen, 1)
- c.Assert(info[0].IsDir(), Equals, true)
-
- info, err = s.FS.ReadDir("/qux")
- c.Assert(err, IsNil)
- c.Assert(info, HasLen, 2)
-
- qux := s.FS.Dir("/qux")
- info, err = qux.ReadDir("/")
- c.Assert(err, IsNil)
- c.Assert(info, HasLen, 2)
-}
-
-func (s *FilesystemSuite) TestReadDirFileInfo(c *C) {
- err := WriteFile(s.FS, "foo", []byte{'F', 'O', 'O'}, 0644)
- c.Assert(err, IsNil)
-
- info, err := s.FS.ReadDir("/")
- c.Assert(err, IsNil)
- c.Assert(info, HasLen, 1)
-
- c.Assert(info[0].Size(), Equals, int64(3))
- c.Assert(info[0].IsDir(), Equals, false)
- c.Assert(info[0].Name(), Equals, "foo")
-}
-
-func (s *FilesystemSuite) TestReadDirFileInfoDirs(c *C) {
- files := []string{"qux/baz/foo"}
- for _, name := range files {
- err := WriteFile(s.FS, name, []byte{'F', 'O', 'O'}, 0644)
- c.Assert(err, IsNil)
- }
-
- info, err := s.FS.ReadDir("qux")
- c.Assert(err, IsNil)
- c.Assert(info, HasLen, 1)
- c.Assert(info[0].IsDir(), Equals, true)
- c.Assert(info[0].Name(), Equals, "baz")
-
- info, err = s.FS.ReadDir("qux/baz")
- c.Assert(err, IsNil)
- c.Assert(info, HasLen, 1)
- c.Assert(info[0].Size(), Equals, int64(3))
- c.Assert(info[0].IsDir(), Equals, false)
- c.Assert(info[0].Name(), Equals, "foo")
- c.Assert(info[0].Mode(), Not(Equals), 0)
-}
-
-func (s *FilesystemSuite) TestStat(c *C) {
- WriteFile(s.FS, "foo/bar", []byte("foo"), customMode)
-
- fi, err := s.FS.Stat("foo/bar")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "bar")
- c.Assert(fi.Size(), Equals, int64(3))
- c.Assert(fi.Mode(), Equals, customMode)
- c.Assert(fi.ModTime().IsZero(), Equals, false)
- c.Assert(fi.IsDir(), Equals, false)
-}
-
-func (s *FilesystemSuite) TestStatLink(c *C) {
- WriteFile(s.FS, "foo/bar", []byte("foo"), customMode)
- s.FS.Symlink("bar", "foo/qux")
-
- fi, err := s.FS.Stat("foo/qux")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "qux")
- c.Assert(fi.Size(), Equals, int64(3))
- c.Assert(fi.Mode(), Equals, customMode)
- c.Assert(fi.ModTime().IsZero(), Equals, false)
- c.Assert(fi.IsDir(), Equals, false)
-}
-
-func (s *FilesystemSuite) TestStatDir(c *C) {
- s.FS.MkdirAll("foo/bar", 0644)
-
- fi, err := s.FS.Stat("foo/bar")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "bar")
- c.Assert(fi.Mode().IsDir(), Equals, true)
- c.Assert(fi.ModTime().IsZero(), Equals, false)
- c.Assert(fi.IsDir(), Equals, true)
-}
-
-func (s *FilesystemSuite) TestStatNonExistent(c *C) {
- fi, err := s.FS.Stat("non-existent")
- comment := Commentf("error: %s", err)
- c.Assert(os.IsNotExist(err), Equals, true, comment)
- c.Assert(fi, IsNil)
-}
-
-func (s *FilesystemSuite) TestLstat(c *C) {
- WriteFile(s.FS, "foo/bar", []byte("foo"), customMode)
-
- fi, err := s.FS.Lstat("foo/bar")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "bar")
- c.Assert(fi.Size(), Equals, int64(3))
- c.Assert(fi.Mode()&os.ModeSymlink != 0, Equals, false)
- c.Assert(fi.ModTime().IsZero(), Equals, false)
- c.Assert(fi.IsDir(), Equals, false)
-}
-
-func (s *FilesystemSuite) TestLstatLink(c *C) {
- WriteFile(s.FS, "foo/bar", []byte("fosddddaaao"), customMode)
- s.FS.Symlink("bar", "foo/qux")
-
- fi, err := s.FS.Lstat("foo/qux")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "qux")
- c.Assert(fi.Mode()&os.ModeSymlink != 0, Equals, true)
- c.Assert(fi.ModTime().IsZero(), Equals, false)
- c.Assert(fi.IsDir(), Equals, false)
-}
-
-func (s *FilesystemSuite) TestDirStat(c *C) {
- files := []string{"foo", "bar", "qux/baz", "qux/qux"}
- for _, name := range files {
- err := WriteFile(s.FS, name, nil, 0644)
- c.Assert(err, IsNil)
- }
-
- // Some implementations detect directories based on a prefix
- // for all files; it's easy to miss path separator handling there.
- fi, err := s.FS.Stat("qu")
- c.Assert(os.IsNotExist(err), Equals, true, Commentf("error: %s", err))
- c.Assert(fi, IsNil)
-
- fi, err = s.FS.Stat("qux")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "qux")
- c.Assert(fi.IsDir(), Equals, true)
-
- qux := s.FS.Dir("qux")
-
- fi, err = qux.Stat("baz")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "baz")
- c.Assert(fi.IsDir(), Equals, false)
-
- fi, err = qux.Stat("/baz")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "baz")
- c.Assert(fi.IsDir(), Equals, false)
-}
-
-func (s *FilesystemSuite) TestCreateInDir(c *C) {
- f, err := s.FS.Dir("foo").Create("bar")
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
- c.Assert(f.Filename(), Equals, "bar")
-
- f, err = s.FS.Open("foo/bar")
- c.Assert(f.Filename(), Equals, s.FS.Join("foo", "bar"))
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestRename(c *C) {
- err := WriteFile(s.FS, "foo", nil, 0644)
- c.Assert(err, IsNil)
-
- files, err := s.FS.ReadDir("")
- c.Assert(err, IsNil)
- c.Assert(files, HasLen, 1)
-
- err = s.FS.Rename("foo", "bar")
- c.Assert(err, IsNil)
-
- foo, err := s.FS.Stat("foo")
- c.Assert(foo, IsNil)
- c.Assert(os.IsNotExist(err), Equals, true)
-
- bar, err := s.FS.Stat("bar")
- c.Assert(err, IsNil)
- c.Assert(bar, NotNil)
-
- files, err = s.FS.ReadDir("")
- c.Assert(err, IsNil)
- c.Assert(files, HasLen, 1)
-}
-
-func (s *FilesystemSuite) TestRenameToDir(c *C) {
- err := WriteFile(s.FS, "foo", nil, 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Rename("foo", "bar/qux")
- c.Assert(err, IsNil)
-
- old, err := s.FS.Stat("foo")
- c.Assert(old, IsNil)
- c.Assert(os.IsNotExist(err), Equals, true)
-
- dir, err := s.FS.Stat("bar")
- c.Assert(dir, NotNil)
- c.Assert(err, IsNil)
-
- file, err := s.FS.Stat("bar/qux")
- c.Assert(file.Name(), Equals, "qux")
- c.Assert(err, IsNil)
-}
-
-func (s *FilesystemSuite) TestRenameDir(c *C) {
- err := s.FS.MkdirAll("foo", 0644)
- c.Assert(err, IsNil)
-
- err = WriteFile(s.FS, "foo/bar", nil, 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Rename("foo", "bar")
- c.Assert(err, IsNil)
-
- dirfoo, err := s.FS.Stat("foo")
- c.Assert(dirfoo, IsNil)
- c.Assert(os.IsNotExist(err), Equals, true)
-
- dirbar, err := s.FS.Stat("bar")
- c.Assert(err, IsNil)
- c.Assert(dirbar, NotNil)
-
- foo, err := s.FS.Stat("foo/bar")
- c.Assert(os.IsNotExist(err), Equals, true)
- c.Assert(foo, IsNil)
-
- bar, err := s.FS.Stat("bar/bar")
- c.Assert(err, IsNil)
- c.Assert(bar, NotNil)
-}
-
-func (s *FilesystemSuite) TestTempFile(c *C) {
- f, err := s.FS.TempFile("", "bar")
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- c.Assert(strings.HasPrefix(f.Filename(), "bar"), Equals, true)
-}
-
-func (s *FilesystemSuite) TestTempFileWithPath(c *C) {
- f, err := s.FS.TempFile("foo", "bar")
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- c.Assert(strings.HasPrefix(f.Filename(), s.FS.Join("foo", "bar")), Equals, true)
-}
-
-func (s *FilesystemSuite) TestTempFileFullWithPath(c *C) {
- f, err := s.FS.TempFile("/foo", "bar")
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- c.Assert(strings.HasPrefix(f.Filename(), s.FS.Join("foo", "bar")), Equals, true)
-}
-
-func (s *FilesystemSuite) TestOpenAndWrite(c *C) {
- err := WriteFile(s.FS, "foo", nil, 0644)
- c.Assert(err, IsNil)
-
- foo, err := s.FS.Open("foo")
- c.Assert(foo, NotNil)
- c.Assert(err, IsNil)
-
- n, err := foo.Write([]byte("foo"))
- c.Assert(err, NotNil)
- c.Assert(n, Equals, 0)
-
- c.Assert(foo.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestOpenAndStat(c *C) {
- err := WriteFile(s.FS, "foo", []byte("foo"), 0644)
- c.Assert(err, IsNil)
-
- foo, err := s.FS.Open("foo")
- c.Assert(foo, NotNil)
- c.Assert(foo.Filename(), Equals, "foo")
- c.Assert(err, IsNil)
- c.Assert(foo.Close(), IsNil)
-
- stat, err := s.FS.Stat("foo")
- c.Assert(stat, NotNil)
- c.Assert(err, IsNil)
- c.Assert(stat.Name(), Equals, "foo")
- c.Assert(stat.Size(), Equals, int64(3))
-}
-
-func (s *FilesystemSuite) TestRemove(c *C) {
- f, err := s.FS.Create("foo")
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- err = s.FS.Remove("foo")
- c.Assert(err, IsNil)
-}
-
-func (s *FilesystemSuite) TestRemoveNonExisting(c *C) {
- err := s.FS.Remove("NON-EXISTING")
- c.Assert(err, NotNil)
- c.Assert(os.IsNotExist(err), Equals, true)
-}
-
-func (s *FilesystemSuite) TestRemoveNotEmptyDir(c *C) {
- err := WriteFile(s.FS, "foo", nil, 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Remove("no-exists")
- c.Assert(err, NotNil)
-}
-
-func (s *FilesystemSuite) TestRemoveTempFile(c *C) {
- f, err := s.FS.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
-
- fn := f.Filename()
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- c.Assert(s.FS.Remove(fn), IsNil)
-}
-
-func (s *FilesystemSuite) TestJoin(c *C) {
- c.Assert(s.FS.Join("foo", "bar"), Equals, fmt.Sprintf("foo%cbar", filepath.Separator))
-}
-
-func (s *FilesystemSuite) TestBase(c *C) {
- c.Assert(s.FS.Base(), Not(Equals), "")
-}
-
-func (s *FilesystemSuite) TestReadAtOnReadWrite(c *C) {
- f, err := s.FS.Create("foo")
- c.Assert(err, IsNil)
- _, err = f.Write([]byte("abcdefg"))
- c.Assert(err, IsNil)
-
- rf, ok := f.(io.ReaderAt)
- c.Assert(ok, Equals, true)
-
- b := make([]byte, 3)
- n, err := rf.ReadAt(b, 2)
- c.Assert(err, IsNil)
- c.Assert(n, Equals, 3)
- c.Assert(string(b), Equals, "cde")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestReadAtOnReadOnly(c *C) {
- err := WriteFile(s.FS, "foo", []byte("abcdefg"), 0644)
- c.Assert(err, IsNil)
-
- f, err := s.FS.Open("foo")
- c.Assert(err, IsNil)
-
- rf, ok := f.(io.ReaderAt)
- c.Assert(ok, Equals, true)
-
- b := make([]byte, 3)
- n, err := rf.ReadAt(b, 2)
- c.Assert(err, IsNil)
- c.Assert(n, Equals, 3)
- c.Assert(string(b), Equals, "cde")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestReadAtEOF(c *C) {
- err := WriteFile(s.FS, "foo", []byte("TEST"), 0644)
- c.Assert(err, IsNil)
-
- f, err := s.FS.Open("foo")
- c.Assert(err, IsNil)
-
- rf, ok := f.(io.ReaderAt)
- c.Assert(ok, Equals, true)
-
- b := make([]byte, 5)
- n, err := rf.ReadAt(b, 0)
- c.Assert(err, Equals, io.EOF)
- c.Assert(n, Equals, 4)
- c.Assert(string(b), Equals, "TEST\x00")
-
- err = f.Close()
- c.Assert(err, IsNil)
-}
-
-func (s *FilesystemSuite) TestReadAtOffset(c *C) {
- err := WriteFile(s.FS, "foo", []byte("TEST"), 0644)
- c.Assert(err, IsNil)
-
- f, err := s.FS.Open("foo")
- c.Assert(err, IsNil)
-
- rf, ok := f.(io.ReaderAt)
- c.Assert(ok, Equals, true)
-
- o, err := f.Seek(0, io.SeekCurrent)
- c.Assert(err, IsNil)
- c.Assert(o, Equals, int64(0))
-
- b := make([]byte, 4)
- n, err := rf.ReadAt(b, 0)
- c.Assert(err, IsNil)
- c.Assert(n, Equals, 4)
- c.Assert(string(b), Equals, "TEST")
-
- o, err = f.Seek(0, io.SeekCurrent)
- c.Assert(err, IsNil)
- c.Assert(o, Equals, int64(0))
-
- err = f.Close()
- c.Assert(err, IsNil)
-}
-
-func (s *FilesystemSuite) TestReadWriteLargeFile(c *C) {
- f, err := s.FS.Create("foo")
- c.Assert(err, IsNil)
-
- size := 1 << 20
-
- n, err := f.Write(bytes.Repeat([]byte("F"), size))
- c.Assert(err, IsNil)
- c.Assert(n, Equals, size)
-
- c.Assert(f.Close(), IsNil)
-
- f, err = s.FS.Open("foo")
- c.Assert(err, IsNil)
- b, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(len(b), Equals, size)
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestRemoveAllNonExistent(c *C) {
- c.Assert(RemoveAll(s.FS, "non-existent"), IsNil)
-}
-
-func (s *FilesystemSuite) TestRemoveAllEmptyDir(c *C) {
- c.Assert(s.FS.MkdirAll("empty", os.FileMode(0755)), IsNil)
- c.Assert(RemoveAll(s.FS, "empty"), IsNil)
- _, err := s.FS.Stat("empty")
- c.Assert(err, NotNil)
- c.Assert(os.IsNotExist(err), Equals, true)
-}
-
-func (s *FilesystemSuite) TestRemoveAll(c *C) {
- fnames := []string{
- "foo/1",
- "foo/2",
- "foo/bar/1",
- "foo/bar/2",
- "foo/bar/baz/1",
- "foo/bar/baz/qux/1",
- "foo/bar/baz/qux/2",
- "foo/bar/baz/qux/3",
- }
-
- for _, fname := range fnames {
- err := WriteFile(s.FS, fname, nil, 0644)
- c.Assert(err, IsNil)
- }
-
- c.Assert(RemoveAll(s.FS, "foo"), IsNil)
-
- for _, fname := range fnames {
- _, err := s.FS.Stat(fname)
- comment := Commentf("not removed: %s %s", fname, err)
- c.Assert(os.IsNotExist(err), Equals, true, comment)
- }
-}
-
-func (s *FilesystemSuite) TestRemoveAllRelative(c *C) {
- fnames := []string{
- "foo/1",
- "foo/2",
- "foo/bar/1",
- "foo/bar/2",
- "foo/bar/baz/1",
- "foo/bar/baz/qux/1",
- "foo/bar/baz/qux/2",
- "foo/bar/baz/qux/3",
- }
-
- for _, fname := range fnames {
- err := WriteFile(s.FS, fname, nil, 0644)
- c.Assert(err, IsNil)
- }
-
- c.Assert(RemoveAll(s.FS, "foo/bar/.."), IsNil)
-
- for _, fname := range fnames {
- _, err := s.FS.Stat(fname)
- comment := Commentf("not removed: %s %s", fname, err)
- c.Assert(os.IsNotExist(err), Equals, true, comment)
- }
-}
-
-func (s *FilesystemSuite) TestMkdirAll(c *C) {
- err := s.FS.MkdirAll("empty", os.FileMode(0755))
- c.Assert(err, IsNil)
-
- fi, err := s.FS.Stat("empty")
- c.Assert(err, IsNil)
- c.Assert(fi.IsDir(), Equals, true)
-}
-
-func (s *FilesystemSuite) TestMkdirAllNested(c *C) {
- err := s.FS.MkdirAll("foo/bar/baz", os.FileMode(0755))
- c.Assert(err, IsNil)
-
- fi, err := s.FS.Stat("foo/bar/baz")
- c.Assert(err, IsNil)
- c.Assert(fi.IsDir(), Equals, true)
-}
-
-func (s *FilesystemSuite) TestMkdirAllIdempotent(c *C) {
- err := s.FS.MkdirAll("empty", 0755)
- c.Assert(err, IsNil)
- fi, err := s.FS.Stat("empty")
- c.Assert(err, IsNil)
- c.Assert(fi.IsDir(), Equals, true)
-
- // idempotent
- err = s.FS.MkdirAll("empty", 0755)
- c.Assert(err, IsNil)
- fi, err = s.FS.Stat("empty")
- c.Assert(err, IsNil)
- c.Assert(fi.IsDir(), Equals, true)
-}
-
-func (s *FilesystemSuite) TestMkdirAllAndOpenFile(c *C) {
- err := s.FS.MkdirAll("dir", os.FileMode(0755))
- c.Assert(err, IsNil)
-
- f, err := s.FS.Create("dir/bar/foo")
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- fi, err := s.FS.Stat("dir/bar/foo")
- c.Assert(err, IsNil)
- c.Assert(fi.IsDir(), Equals, false)
-}
-
-func (s *FilesystemSuite) TestMkdirAllWithExistingFile(c *C) {
- f, err := s.FS.Create("dir/foo")
- c.Assert(err, IsNil)
- c.Assert(f.Close(), IsNil)
-
- err = s.FS.MkdirAll("dir/foo", os.FileMode(0755))
- c.Assert(err, NotNil)
-
- fi, err := s.FS.Stat("dir/foo")
- c.Assert(err, IsNil)
- c.Assert(fi.IsDir(), Equals, false)
-}
-
-func (s *FilesystemSuite) TestWriteFile(c *C) {
- err := WriteFile(s.FS, "foo", []byte("bar"), 0777)
- c.Assert(err, IsNil)
-
- f, err := s.FS.Open("foo")
- c.Assert(err, IsNil)
-
- wrote, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(string(wrote), DeepEquals, "bar")
-
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestSymlinkBasic(c *C) {
- err := WriteFile(s.FS, "file", nil, 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("file", "link")
- c.Assert(err, IsNil)
-
- fi, err := s.FS.Stat("link")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "link")
-}
-
-func (s *FilesystemSuite) TestSymlinkCrossDirs(c *C) {
- err := WriteFile(s.FS, "foo/file", nil, 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("../foo/file", "bar/link")
- c.Assert(err, IsNil)
-
- fi, err := s.FS.Stat("bar/link")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "link")
-}
-
-func (s *FilesystemSuite) TestSymlinkLinkToLink(c *C) {
- err := WriteFile(s.FS, "file", nil, 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("file", "linkA")
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("linkA", "linkB")
- c.Assert(err, IsNil)
-
- fi, err := s.FS.Stat("linkB")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "linkB")
-}
-
-func (s *FilesystemSuite) TestSymlinkToDir(c *C) {
- err := s.FS.MkdirAll("dir", 0755)
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("dir", "link")
- c.Assert(err, IsNil)
-
- fi, err := s.FS.Stat("link")
- c.Assert(err, IsNil)
- c.Assert(fi.Name(), Equals, "link")
- c.Assert(fi.IsDir(), Equals, true)
-}
-
-func (s *FilesystemSuite) TestSymlinkWithNonExistentOldname(c *C) {
- err := s.FS.Symlink("file", "link")
- c.Assert(err, IsNil)
-
- _, err = s.FS.Stat("link")
- c.Assert(os.IsNotExist(err), Equals, true)
-}
-
-func (s *FilesystemSuite) TestSymlinkWithExistingNewname(c *C) {
- err := WriteFile(s.FS, "link", nil, 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("file", "link")
- c.Assert(err, Not(IsNil))
-}
-
-func (s *FilesystemSuite) TestSymlinkOpenWithRelativePath(c *C) {
- err := WriteFile(s.FS, "dir/file", []byte("foo"), 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("file", "dir/link")
- c.Assert(err, IsNil)
-
- f, err := s.FS.Open("dir/link")
- c.Assert(err, IsNil)
-
- all, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(string(all), Equals, "foo")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestSymlinkOpenWithAbsolutePath(c *C) {
- err := WriteFile(s.FS, "dir/file", []byte("foo"), 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("/dir/file", "dir/link")
- c.Assert(err, IsNil)
-
- f, err := s.FS.Open("dir/link")
- c.Assert(err, IsNil)
-
- all, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(string(all), Equals, "foo")
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *FilesystemSuite) TestSymlinkReadDir(c *C) {
- err := WriteFile(s.FS, "dir/file", []byte("foo"), 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("dir", "link")
- c.Assert(err, IsNil)
-
- info, err := s.FS.ReadDir("link")
- c.Assert(err, IsNil)
- c.Assert(info, HasLen, 1)
-
- c.Assert(info[0].Size(), Equals, int64(3))
- c.Assert(info[0].IsDir(), Equals, false)
- c.Assert(info[0].Name(), Equals, "file")
-}
-
-func (s *FilesystemSuite) TestSymlinkRename(c *C) {
- err := s.FS.Symlink("file", "link")
- c.Assert(err, IsNil)
-
- err = s.FS.Rename("link", "newlink")
- c.Assert(err, IsNil)
-
- _, err = s.FS.Readlink("newlink")
- c.Assert(err, IsNil)
-}
-
-func (s *FilesystemSuite) TestSymlinkRemove(c *C) {
- err := s.FS.Symlink("file", "link")
- c.Assert(err, IsNil)
-
- err = s.FS.Remove("link")
- c.Assert(err, IsNil)
-
- _, err = s.FS.Readlink("link")
- c.Assert(os.IsNotExist(err), Equals, true)
-}
-
-func (s *FilesystemSuite) TestReadlinkWithRelativePath(c *C) {
- err := WriteFile(s.FS, "dir/file", nil, 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("file", "dir/link")
- c.Assert(err, IsNil)
-
- oldname, err := s.FS.Readlink("dir/link")
- c.Assert(err, IsNil)
- c.Assert(oldname, Equals, "file")
-}
-
-func (s *FilesystemSuite) TestReadlinkWithAbsolutePath(c *C) {
- err := WriteFile(s.FS, "dir/file", nil, 0644)
- c.Assert(err, IsNil)
-
- err = s.FS.Symlink("/dir/file", "dir/link")
- c.Assert(err, IsNil)
-
- oldname, err := s.FS.Readlink("dir/link")
- c.Assert(err, IsNil)
- c.Assert(oldname, Equals, expectedSymlinkTarget)
-}
-
-func (s *FilesystemSuite) TestReadlinkWithNonExistentOldname(c *C) {
- err := s.FS.Symlink("file", "link")
- c.Assert(err, IsNil)
-
- oldname, err := s.FS.Readlink("link")
- c.Assert(err, IsNil)
- c.Assert(oldname, Equals, "file")
-}
-
-func (s *FilesystemSuite) TestReadlinkWithNonExistentLink(c *C) {
- _, err := s.FS.Readlink("link")
- c.Assert(os.IsNotExist(err), Equals, true)
-}
-
-func (s *FilesystemSuite) TestReadlinkWithRegularFile(c *C) {
- err := WriteFile(s.FS, "file", nil, 0644)
- c.Assert(err, IsNil)
-
- _, err = s.FS.Readlink("file")
- c.Assert(err, Not(IsNil))
-}
diff --git a/test/fs_suite_darwin.go b/test/fs_suite_darwin.go
deleted file mode 100644
index 84dd8c1..0000000
--- a/test/fs_suite_darwin.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// +build !windows
-
-package test
-
-import "os"
-
-var (
- customMode os.FileMode = 0755
- expectedSymlinkTarget = "/dir/file"
-)
diff --git a/test/mock.go b/test/mock.go
new file mode 100644
index 0000000..aaa15bc
--- /dev/null
+++ b/test/mock.go
@@ -0,0 +1,122 @@
+package test
+
+import (
+ "bytes"
+ "os"
+ "path"
+ "path/filepath"
+
+ "gopkg.in/src-d/go-billy.v3"
+)
+
+type BasicMock struct {
+ CreateArgs []string
+ OpenArgs []string
+ OpenFileArgs [][3]interface{}
+ StatArgs []string
+ RenameArgs [][2]string
+ RemoveArgs []string
+}
+
+func (fs *BasicMock) Create(filename string) (billy.File, error) {
+ fs.CreateArgs = append(fs.CreateArgs, filename)
+ return &FileMock{name: filename}, nil
+}
+
+func (fs *BasicMock) Open(filename string) (billy.File, error) {
+ fs.OpenArgs = append(fs.OpenArgs, filename)
+ return &FileMock{name: filename}, nil
+}
+
+func (fs *BasicMock) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) {
+ fs.OpenFileArgs = append(fs.OpenFileArgs, [3]interface{}{filename, flag, mode})
+ return &FileMock{name: filename}, nil
+}
+
+func (fs *BasicMock) Stat(filename string) (os.FileInfo, error) {
+ fs.StatArgs = append(fs.StatArgs, filename)
+ return nil, nil
+}
+
+func (fs *BasicMock) Rename(target, link string) error {
+ fs.RenameArgs = append(fs.RenameArgs, [2]string{target, link})
+ return nil
+}
+
+func (fs *BasicMock) Remove(filename string) error {
+ fs.RemoveArgs = append(fs.RemoveArgs, filename)
+ return nil
+}
+
+func (fs *BasicMock) Join(elem ...string) string {
+ return path.Join(elem...)
+}
+
+type TempFileMock struct {
+ BasicMock
+ TempFileArgs [][2]string
+}
+
+func (fs *TempFileMock) TempFile(dir, prefix string) (billy.File, error) {
+ fs.TempFileArgs = append(fs.TempFileArgs, [2]string{dir, prefix})
+ return &FileMock{name: "/tmp/hardcoded/mock/temp"}, nil
+}
+
+type DirMock struct {
+ BasicMock
+ ReadDirArgs []string
+ MkdirAllArgs [][2]interface{}
+}
+
+func (fs *DirMock) ReadDir(path string) ([]os.FileInfo, error) {
+ fs.ReadDirArgs = append(fs.ReadDirArgs, path)
+ return nil, nil
+}
+
+func (fs *DirMock) MkdirAll(filename string, perm os.FileMode) error {
+ fs.MkdirAllArgs = append(fs.MkdirAllArgs, [2]interface{}{filename, perm})
+ return nil
+}
+
+type SymlinkMock struct {
+ BasicMock
+ LstatArgs []string
+ SymlinkArgs [][2]string
+ ReadlinkArgs []string
+}
+
+func (fs *SymlinkMock) Lstat(filename string) (os.FileInfo, error) {
+ fs.LstatArgs = append(fs.LstatArgs, filename)
+ return nil, nil
+}
+
+func (fs *SymlinkMock) Symlink(target, link string) error {
+ fs.SymlinkArgs = append(fs.SymlinkArgs, [2]string{target, link})
+ return nil
+}
+
+func (fs *SymlinkMock) Readlink(link string) (string, error) {
+ fs.ReadlinkArgs = append(fs.ReadlinkArgs, link)
+ return filepath.FromSlash(link), nil
+}
+
+type FileMock struct {
+ name string
+ bytes.Buffer
+}
+
+func (f *FileMock) Name() string {
+ return f.name
+}
+
+func (*FileMock) ReadAt(b []byte, off int64) (int, error) {
+ return 0, nil
+}
+
+func (*FileMock) Seek(offset int64, whence int) (int64, error) {
+ return 0, nil
+}
+
+func (*FileMock) Close() error {
+ return nil
+}
diff --git a/test/symlink.go b/test/symlink.go
new file mode 100644
index 0000000..ea68736
--- /dev/null
+++ b/test/symlink.go
@@ -0,0 +1,221 @@
+package test
+
+import (
+ "io/ioutil"
+ "os"
+
+ . "gopkg.in/check.v1"
+ . "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v3/util"
+)
+
+// SymlinkSuite is a convenient test suite to validate any implementation of
+// billy.Symlink
+type SymlinkSuite struct {
+ FS interface {
+ Basic
+ Symlink
+ }
+}
+
+func (s *SymlinkSuite) TestSymlink(c *C) {
+ err := util.WriteFile(s.FS, "file", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("file", "link")
+ c.Assert(err, IsNil)
+
+ fi, err := s.FS.Stat("link")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "link")
+}
+
+func (s *SymlinkSuite) TestSymlinkCrossDirs(c *C) {
+ err := util.WriteFile(s.FS, "foo/file", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("../foo/file", "bar/link")
+ c.Assert(err, IsNil)
+
+ fi, err := s.FS.Stat("bar/link")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "link")
+}
+
+func (s *SymlinkSuite) TestSymlinkNested(c *C) {
+ err := util.WriteFile(s.FS, "file", []byte("hello world!"), 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("file", "linkA")
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("linkA", "linkB")
+ c.Assert(err, IsNil)
+
+ fi, err := s.FS.Stat("linkB")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "linkB")
+ c.Assert(fi.Size(), Equals, int64(12))
+}
+
+func (s *SymlinkSuite) TestSymlinkWithNonExistentdTarget(c *C) {
+ err := s.FS.Symlink("file", "link")
+ c.Assert(err, IsNil)
+
+ _, err = s.FS.Stat("link")
+ c.Assert(os.IsNotExist(err), Equals, true)
+}
+
+func (s *SymlinkSuite) TestSymlinkWithExistingLink(c *C) {
+ err := util.WriteFile(s.FS, "link", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("file", "link")
+ c.Assert(err, Not(IsNil))
+}
+
+func (s *SymlinkSuite) TestOpenWithSymlinkToRelativePath(c *C) {
+ err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("file", "dir/link")
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Open("dir/link")
+ c.Assert(err, IsNil)
+
+ all, err := ioutil.ReadAll(f)
+ c.Assert(err, IsNil)
+ c.Assert(string(all), Equals, "foo")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *SymlinkSuite) TestOpenWithSymlinkToAbsolutePath(c *C) {
+ err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("/dir/file", "dir/link")
+ c.Assert(err, IsNil)
+
+ f, err := s.FS.Open("dir/link")
+ c.Assert(err, IsNil)
+
+ all, err := ioutil.ReadAll(f)
+ c.Assert(err, IsNil)
+ c.Assert(string(all), Equals, "foo")
+ c.Assert(f.Close(), IsNil)
+}
+
+func (s *SymlinkSuite) TestReadlink(c *C) {
+ err := util.WriteFile(s.FS, "file", nil, 0644)
+ c.Assert(err, IsNil)
+
+ _, err = s.FS.Readlink("file")
+ c.Assert(err, Not(IsNil))
+}
+
+func (s *SymlinkSuite) TestReadlinkWithRelativePath(c *C) {
+ err := util.WriteFile(s.FS, "dir/file", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("file", "dir/link")
+ c.Assert(err, IsNil)
+
+ oldname, err := s.FS.Readlink("dir/link")
+ c.Assert(err, IsNil)
+ c.Assert(oldname, Equals, "file")
+}
+
+func (s *SymlinkSuite) TestReadlinkWithAbsolutePath(c *C) {
+ err := util.WriteFile(s.FS, "dir/file", nil, 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("/dir/file", "dir/link")
+ c.Assert(err, IsNil)
+
+ oldname, err := s.FS.Readlink("dir/link")
+ c.Assert(err, IsNil)
+ c.Assert(oldname, Equals, expectedSymlinkTarget)
+}
+
+func (s *SymlinkSuite) TestReadlinkWithNonExistentTarget(c *C) {
+ err := s.FS.Symlink("file", "link")
+ c.Assert(err, IsNil)
+
+ oldname, err := s.FS.Readlink("link")
+ c.Assert(err, IsNil)
+ c.Assert(oldname, Equals, "file")
+}
+
+func (s *SymlinkSuite) TestReadlinkWithNonExistentLink(c *C) {
+ _, err := s.FS.Readlink("link")
+ c.Assert(os.IsNotExist(err), Equals, true)
+}
+
+func (s *SymlinkSuite) TestStatLink(c *C) {
+ util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode)
+ s.FS.Symlink("bar", "foo/qux")
+
+ fi, err := s.FS.Stat("foo/qux")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "qux")
+ c.Assert(fi.Size(), Equals, int64(3))
+ c.Assert(fi.Mode(), Equals, customMode)
+ c.Assert(fi.ModTime().IsZero(), Equals, false)
+ c.Assert(fi.IsDir(), Equals, false)
+}
+
+func (s *SymlinkSuite) TestLstat(c *C) {
+ util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode)
+
+ fi, err := s.FS.Lstat("foo/bar")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "bar")
+ c.Assert(fi.Size(), Equals, int64(3))
+ c.Assert(fi.Mode()&os.ModeSymlink != 0, Equals, false)
+ c.Assert(fi.ModTime().IsZero(), Equals, false)
+ c.Assert(fi.IsDir(), Equals, false)
+}
+
+func (s *SymlinkSuite) TestLstatLink(c *C) {
+ util.WriteFile(s.FS, "foo/bar", []byte("fosddddaaao"), customMode)
+ s.FS.Symlink("bar", "foo/qux")
+
+ fi, err := s.FS.Lstat("foo/qux")
+ c.Assert(err, IsNil)
+ c.Assert(fi.Name(), Equals, "qux")
+ c.Assert(fi.Mode()&os.ModeSymlink != 0, Equals, true)
+ c.Assert(fi.ModTime().IsZero(), Equals, false)
+ c.Assert(fi.IsDir(), Equals, false)
+}
+
+func (s *SymlinkSuite) TestRenameWithSymlink(c *C) {
+ err := s.FS.Symlink("file", "link")
+ c.Assert(err, IsNil)
+
+ err = s.FS.Rename("link", "newlink")
+ c.Assert(err, IsNil)
+
+ _, err = s.FS.Readlink("newlink")
+ c.Assert(err, IsNil)
+}
+
+func (s *SymlinkSuite) TestRemoveWithSymlink(c *C) {
+ err := util.WriteFile(s.FS, "file", []byte("foo"), 0644)
+ c.Assert(err, IsNil)
+
+ err = s.FS.Symlink("file", "link")
+ c.Assert(err, IsNil)
+
+ err = s.FS.Remove("link")
+ c.Assert(err, IsNil)
+
+ _, err = s.FS.Readlink("link")
+ c.Assert(os.IsNotExist(err), Equals, true)
+
+ _, err = s.FS.Stat("link")
+ c.Assert(os.IsNotExist(err), Equals, true)
+
+ _, err = s.FS.Stat("file")
+ c.Assert(err, IsNil)
+}
diff --git a/test/tempfile.go b/test/tempfile.go
new file mode 100644
index 0000000..5218794
--- /dev/null
+++ b/test/tempfile.go
@@ -0,0 +1,50 @@
+package test
+
+import (
+ "strings"
+
+ . "gopkg.in/check.v1"
+ "gopkg.in/src-d/go-billy.v3"
+)
+
+// TempFileSuite is a convenient test suite to validate any implementation of
+// billy.TempFile
+type TempFileSuite struct {
+ FS interface {
+ billy.Basic
+ billy.TempFile
+ }
+}
+
+func (s *TempFileSuite) TestTempFile(c *C) {
+ f, err := s.FS.TempFile("", "bar")
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+ c.Assert(strings.HasPrefix(f.Name(), "bar"), Equals, true)
+}
+
+func (s *TempFileSuite) TestTempFileWithPath(c *C) {
+ f, err := s.FS.TempFile("foo", "bar")
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+
+ c.Assert(strings.HasPrefix(f.Name(), s.FS.Join("foo", "bar")), Equals, true)
+}
+
+func (s *TempFileSuite) TestTempFileFullWithPath(c *C) {
+ f, err := s.FS.TempFile("/foo", "bar")
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+
+ c.Assert(strings.HasPrefix(f.Name(), s.FS.Join("foo", "bar")), Equals, true)
+}
+
+func (s *TempFileSuite) TestRemoveTempFile(c *C) {
+ f, err := s.FS.TempFile("test-dir", "test-prefix")
+ c.Assert(err, IsNil)
+
+ fn := f.Name()
+ c.Assert(err, IsNil)
+ c.Assert(f.Close(), IsNil)
+ c.Assert(s.FS.Remove(fn), IsNil)
+}
diff --git a/tmpoverlayfs/tmpfs.go b/tmpoverlayfs/tmpfs.go
deleted file mode 100644
index d209447..0000000
--- a/tmpoverlayfs/tmpfs.go
+++ /dev/null
@@ -1,200 +0,0 @@
-package tmpfs
-
-import (
- "errors"
- "io"
- "os"
- "path"
-
- "fmt"
-
- "gopkg.in/src-d/go-billy.v2"
- "gopkg.in/src-d/go-billy.v2/subdirfs"
-)
-
-type tmpFs struct {
- fs billy.Filesystem
- tmp billy.Filesystem
- tempFiles map[string]bool
-}
-
-// New creates a new filesystem wrapping up 'fs' and using 'tmp' for temporary
-// files. Any file created with TempFile will be created in 'tmp'. It will be
-// moved to 'fs' if Rename is called on it.
-//
-// This is particularly useful to provide TempFile for filesystems that do not
-// support it or where there is a performance penalty in doing so.
-func New(fs billy.Filesystem, tmp billy.Filesystem) billy.Filesystem {
- return &tmpFs{
- fs: fs,
- tmp: tmp,
- tempFiles: map[string]bool{},
- }
-}
-
-func (t *tmpFs) Create(path string) (billy.File, error) {
- if t.isTmpFile(path) {
- return t.tmp.Create(path)
- }
-
- return t.fs.Create(path)
-}
-
-func (t *tmpFs) Open(path string) (billy.File, error) {
- if t.isTmpFile(path) {
- return t.tmp.Open(path)
- }
-
- return t.fs.Open(path)
-
-}
-
-func (t *tmpFs) OpenFile(p string, flag int, mode os.FileMode) (
- billy.File, error) {
- if t.isTmpFile(p) {
- return t.tmp.OpenFile(p, flag, mode)
- }
-
- return t.fs.OpenFile(p, flag, mode)
-}
-
-func (t *tmpFs) ReadDir(p string) ([]billy.FileInfo, error) {
- return t.fs.ReadDir(p)
-}
-
-func (t *tmpFs) Join(elem ...string) string {
- return t.fs.Join(elem...)
-}
-
-func (t *tmpFs) Dir(p string) billy.Filesystem {
- return subdirfs.New(t, p)
-}
-
-func (t *tmpFs) MkdirAll(filename string, perm os.FileMode) error {
- return t.fs.MkdirAll(filename, perm)
-}
-
-func (t *tmpFs) Base() string {
- return t.fs.Base()
-}
-
-func (t *tmpFs) TempFile(dir string, prefix string) (billy.File, error) {
- tmpFile, err := t.tmp.TempFile(dir, prefix)
- if err != nil {
- return nil, err
- }
-
- t.tempFiles[tmpFile.Filename()] = true
-
- return tmpFile, nil
-}
-
-func (t *tmpFs) Rename(from, to string) error {
- if t.isTmpFile(to) {
- return errors.New("cannot rename to temp file")
- }
-
- if t.isTmpFile(from) {
- err := copyPath(t.tmp, t.fs, from, to)
- if err != nil {
- return err
- }
-
- if err := t.tmp.Remove(from); err != nil {
- return err
- }
-
- t.removeTempReference(from)
-
- return nil
- }
-
- return t.fs.Rename(from, to)
-}
-
-func (t *tmpFs) Remove(path string) error {
- if t.isTmpFile(path) {
- if err := t.tmp.Remove(path); err != nil {
- return err
- }
-
- t.removeTempReference(path)
-
- return nil
- }
-
- return t.fs.Remove(path)
-}
-
-// Symlink creates a symbolic-link from link to target. Using a temporal file
-// as target is not allowed.
-func (t *tmpFs) Symlink(target, link string) error {
- if t.isTmpFile(target) {
- return fmt.Errorf("links to temporal file are not supported")
- }
-
- if t.isTmpFile(link) {
- return os.ErrExist
- }
-
- return t.fs.Symlink(target, link)
-}
-
-func (t *tmpFs) Readlink(link string) (string, error) {
- return t.fs.Readlink(link)
-}
-
-func (t *tmpFs) Stat(path string) (billy.FileInfo, error) {
- if t.isTmpFile(path) {
- return t.tmp.Stat(path)
- }
-
- return t.fs.Stat(path)
-}
-
-func (t *tmpFs) Lstat(path string) (billy.FileInfo, error) {
- if t.isTmpFile(path) {
- return t.tmp.Lstat(path)
- }
-
- return t.fs.Lstat(path)
-}
-
-func (t *tmpFs) isTmpFile(p string) bool {
- p = path.Clean(p)
- _, ok := t.tempFiles[p]
- return ok
-}
-
-func (t *tmpFs) removeTempReference(p string) {
- p = path.Clean(p)
- delete(t.tempFiles, p)
-}
-
-// copyPath copies a file across filesystems.
-func copyPath(src billy.Filesystem, dst billy.Filesystem,
- srcPath string, dstPath string) error {
-
- dstFile, err := dst.Create(dstPath)
- if err != nil {
- return err
- }
-
- srcFile, err := src.Open(srcPath)
- if err != nil {
- return nil
- }
-
- _, err = io.Copy(dstFile, srcFile)
- if err != nil {
- return nil
- }
-
- err = dstFile.Close()
- if err != nil {
- _ = srcFile.Close()
- return err
- }
-
- return srcFile.Close()
-}
diff --git a/tmpoverlayfs/tmpfs_specific_test.go b/tmpoverlayfs/tmpfs_specific_test.go
deleted file mode 100644
index c6f068d..0000000
--- a/tmpoverlayfs/tmpfs_specific_test.go
+++ /dev/null
@@ -1,247 +0,0 @@
-package tmpfs
-
-import (
- "io"
- "io/ioutil"
- stdos "os"
- "path/filepath"
-
- "gopkg.in/src-d/go-billy.v2"
- "gopkg.in/src-d/go-billy.v2/osfs"
-
- . "gopkg.in/check.v1"
-)
-
-type SpecificFilesystemSuite struct {
- src billy.Filesystem
- tmp billy.Filesystem
- srcPath string
- tmpPath string
-}
-
-var _ = Suite(&SpecificFilesystemSuite{})
-
-func (s *SpecificFilesystemSuite) SetUpTest(c *C) {
- s.srcPath = c.MkDir()
- s.tmpPath = c.MkDir()
- s.src = osfs.New(s.srcPath)
- s.tmp = osfs.New(s.tmpPath)
-}
-
-func (s *SpecificFilesystemSuite) TestTempFileInTmpFs(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- filename := f.Filename()
- c.Assert(f.Close(), IsNil)
-
- _, err = stdos.Stat(filepath.Join(s.srcPath, filename))
- c.Assert(stdos.IsNotExist(err), Equals, true)
-
- _, err = stdos.Stat(filepath.Join(s.tmpPath, filename))
- c.Assert(err, IsNil)
-}
-
-func (s *SpecificFilesystemSuite) TestNonTempFileInSrcFs(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.Create("foo")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- c.Assert(f.Close(), IsNil)
-
- _, err = stdos.Stat(filepath.Join(s.srcPath, "foo"))
- c.Assert(err, IsNil)
-
- _, err = stdos.Stat(filepath.Join(s.tmpPath, "foo"))
- c.Assert(stdos.IsNotExist(err), Equals, true)
-}
-
-func (s *SpecificFilesystemSuite) TestTempFileCanBeReopened(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- n, err := f.Write([]byte("foo"))
- c.Assert(err, IsNil)
- c.Assert(n, Equals, 3)
-
- filename := f.Filename()
- c.Assert(f.Close(), IsNil)
-
- f, err = tmpFs.Open(filename)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, filename)
-
- content, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(string(content), Equals, "foo")
-
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *SpecificFilesystemSuite) TestTempFileCanBeReopenedByOpenFile(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- n, err := f.Write([]byte("foo"))
- c.Assert(err, IsNil)
- c.Assert(n, Equals, 3)
-
- filename := f.Filename()
- c.Assert(f.Close(), IsNil)
-
- f, err = tmpFs.OpenFile(filename, stdos.O_RDONLY, 0)
- c.Assert(err, IsNil)
- c.Assert(f.Filename(), Equals, filename)
-
- content, err := ioutil.ReadAll(f)
- c.Assert(err, IsNil)
- c.Assert(string(content), Equals, "foo")
-
- c.Assert(f.Close(), IsNil)
-}
-
-func (s *SpecificFilesystemSuite) TestStatTempFile(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- tempFilename := f.Filename()
- c.Assert(f.Close(), IsNil)
-
- fi, err := tmpFs.Stat(tempFilename)
- c.Assert(err, IsNil)
- c.Assert(fi, NotNil)
-}
-
-func (s *SpecificFilesystemSuite) TestSymlinkTmpFile(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- tempFilename := f.Filename()
- c.Assert(f.Close(), IsNil)
-
- err = tmpFs.Symlink(tempFilename, "foo")
- c.Assert(err, NotNil)
-}
-
-func (s *SpecificFilesystemSuite) TestSymlinkOverTmpFile(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- tempFilename := f.Filename()
- c.Assert(f.Close(), IsNil)
-
- err = tmpFs.Symlink("foo", tempFilename)
- c.Assert(err, NotNil)
-}
-
-func (s *SpecificFilesystemSuite) TestRenameFromTempFile(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- tempFilename := f.Filename()
- c.Assert(f.Close(), IsNil)
-
- err = tmpFs.Rename(tempFilename, "foo")
- c.Assert(err, IsNil)
-
- _, err = s.src.Stat("foo")
- c.Assert(err, IsNil)
-
- _, err = s.tmp.Stat("foo")
- c.Assert(err, NotNil)
-
- _, err = s.tmp.Stat(tempFilename)
- c.Assert(err, NotNil)
-}
-
-func (s *SpecificFilesystemSuite) TestCannotRenameToTempFile(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- tempFilename := f.Filename()
- c.Assert(f.Close(), IsNil)
-
- f, err = tmpFs.Create("foo")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
- c.Assert(f.Close(), IsNil)
-
- err = tmpFs.Rename("foo", tempFilename)
- c.Assert(err, NotNil)
-}
-
-func (s *SpecificFilesystemSuite) TestRemoveTempFile(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- tempFilename := f.Filename()
- c.Assert(f.Close(), IsNil)
-
- err = tmpFs.Remove(tempFilename)
- c.Assert(err, IsNil)
-
- _, err = s.tmp.Stat(tempFilename)
- c.Assert(err, NotNil)
-}
-
-func (s *SpecificFilesystemSuite) TestCreateTempFile(c *C) {
- tmpFs := New(s.src, s.tmp)
- c.Assert(tmpFs, NotNil)
-
- f, err := tmpFs.TempFile("test-dir", "test-prefix")
- c.Assert(err, IsNil)
- c.Assert(f, NotNil)
-
- n, err := f.Write([]byte("TEST"))
-
- tempFilename := f.Filename()
- c.Assert(f.Close(), IsNil)
-
- createdFile, err := tmpFs.Create(tempFilename)
- c.Assert(err, IsNil)
- c.Assert(createdFile, NotNil)
-
- bRead := make([]byte, 4)
- n, err = createdFile.Read(bRead)
- c.Assert(n, Equals, 0)
- c.Assert(err, Equals, io.EOF)
-}
diff --git a/tmpoverlayfs/tmpfs_test.go b/tmpoverlayfs/tmpfs_test.go
deleted file mode 100644
index 5a88773..0000000
--- a/tmpoverlayfs/tmpfs_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package tmpfs
-
-import (
- "io/ioutil"
- stdos "os"
- "testing"
-
- "gopkg.in/src-d/go-billy.v2"
- "gopkg.in/src-d/go-billy.v2/osfs"
- "gopkg.in/src-d/go-billy.v2/test"
-
- . "gopkg.in/check.v1"
-)
-
-func Test(t *testing.T) { TestingT(t) }
-
-type FilesystemSuite struct {
- test.FilesystemSuite
- cfs billy.Filesystem
- path string
- pathTmp string
-}
-
-var _ = Suite(&FilesystemSuite{})
-
-func (s *FilesystemSuite) SetUpTest(c *C) {
- s.path, _ = ioutil.TempDir(stdos.TempDir(), "go-billy-tmpfs-test")
- fs := osfs.New(s.path)
-
- s.pathTmp, _ = ioutil.TempDir(stdos.TempDir(), "go-billy-tmpfs-test-tmp")
- fsTmp := osfs.New(s.pathTmp)
-
- s.cfs = New(fs, fsTmp)
- s.FilesystemSuite.FS = s.cfs
-}
-
-func (s *FilesystemSuite) TearDownTest(c *C) {
- _, err := ioutil.ReadDir(s.path)
- c.Assert(err, IsNil)
-
- err = stdos.RemoveAll(s.path)
- c.Assert(err, IsNil)
-
- err = stdos.RemoveAll(s.pathTmp)
- c.Assert(err, IsNil)
-}
diff --git a/util/util.go b/util/util.go
new file mode 100644
index 0000000..e476aa3
--- /dev/null
+++ b/util/util.go
@@ -0,0 +1,165 @@
+package util
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "sync/atomic"
+ "time"
+
+ "gopkg.in/src-d/go-billy.v3"
+)
+
+// RemoveAll removes path and any children it contains. It removes everything it
+// can but returns the first error it encounters. If the path does not exist,
+// RemoveAll returns nil (no error).
+func RemoveAll(fs billy.Basic, path string) error {
+ fs, path = getUnderlyingAndPath(fs, path)
+
+ if r, ok := fs.(removerAll); ok {
+ return r.RemoveAll(path)
+ }
+
+ return removeAll(fs, path)
+}
+
+type removerAll interface {
+ RemoveAll(string) error
+}
+
+func removeAll(fs billy.Basic, path string) error {
+ // This implementation is adapted from os.RemoveAll.
+
+ // Simple case: if Remove works, we're done.
+ err := fs.Remove(path)
+ if err == nil || os.IsNotExist(err) {
+ return nil
+ }
+
+ // Otherwise, is this a directory we need to recurse into?
+ dir, serr := fs.Stat(path)
+ if serr != nil {
+ if os.IsNotExist(serr) {
+ return nil
+ }
+
+ return serr
+ }
+
+ if !dir.IsDir() {
+ // Not a directory; return the error from Remove.
+ return err
+ }
+
+ dirfs, ok := fs.(billy.Dir)
+ if !ok {
+ return billy.ErrNotSupported
+ }
+
+ // Directory.
+ fis, err := dirfs.ReadDir(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ // Race. It was deleted between the Lstat and Open.
+ // Return nil per RemoveAll's docs.
+ return nil
+ }
+
+ return err
+ }
+
+ // Remove contents & return first error.
+ err = nil
+ for _, fi := range fis {
+ cpath := fs.Join(path, fi.Name())
+ err1 := removeAll(fs, cpath)
+ if err == nil {
+ err = err1
+ }
+ }
+
+ // Remove directory.
+ err1 := fs.Remove(path)
+ if err1 == nil || os.IsNotExist(err1) {
+ return nil
+ }
+
+ if err == nil {
+ err = err1
+ }
+
+ return err
+
+}
+
+// WriteFile writes data to a file named by filename in the given filesystem.
+// If the file does not exist, WriteFile creates it with permissions perm;
+// otherwise WriteFile truncates it before writing.
+func WriteFile(fs billy.Basic, filename string, data []byte, perm os.FileMode) error {
+ f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
+ if err != nil {
+ return err
+ }
+
+ n, err := f.Write(data)
+ if err == nil && n < len(data) {
+ err = io.ErrShortWrite
+ }
+
+ if err1 := f.Close(); err == nil {
+ err = err1
+ }
+
+ return err
+}
+
+var (
+ MaxTempFiles int32 = 1024 * 4
+ tempCount int32
+)
+
+// TempFile creates a new temporary file in the directory dir with a name
+// beginning with prefix, opens the file for reading and writing, and returns
+// the resulting *os.File. If dir is the empty string, TempFile uses the default
+// directory for temporary files (see os.TempDir).
+//
+// Multiple programs calling TempFile simultaneously will not choose the same
+// file. The caller can use f.Name() to find the pathname of the file.
+//
+// It is the caller's responsibility to remove the file when no longer needed.
+func TempFile(fs billy.Basic, dir, prefix string) (billy.File, error) {
+ var fullpath string
+ for {
+ if tempCount >= MaxTempFiles {
+ return nil, errors.New("max. number of tempfiles reached")
+ }
+
+ fullpath = getTempFilename(fs, dir, prefix)
+ break
+ }
+
+ return fs.Create(fullpath)
+}
+
+func getTempFilename(fs billy.Basic, dir, prefix string) string {
+ atomic.AddInt32(&tempCount, 1)
+ filename := fmt.Sprintf("%s_%d_%d", prefix, tempCount, time.Now().UnixNano())
+ return fs.Join(dir, filename)
+}
+
+type underlying interface {
+ Underlying() billy.Basic
+}
+
+func getUnderlyingAndPath(fs billy.Basic, path string) (billy.Basic, string) {
+ u, ok := fs.(underlying)
+ if !ok {
+ return fs, path
+ }
+ if ch, ok := fs.(billy.Chroot); ok {
+ path = fs.Join(ch.Root(), path)
+ }
+
+ return u.Underlying(), path
+}
diff --git a/utils.go b/utils.go
deleted file mode 100644
index 110f954..0000000
--- a/utils.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package billy
-
-import (
- "io"
- "os"
-)
-
-// RemoveAll removes path and any children it contains.
-// It removes everything it can but returns the first error
-// it encounters. If the path does not exist, RemoveAll
-// returns nil (no error).
-func RemoveAll(fs Filesystem, path string) error {
- r, ok := fs.(removerAll)
- if ok {
- return r.RemoveAll(path)
- }
-
- return removeAll(fs, path)
-}
-
-func removeAll(fs Filesystem, path string) error {
- // This implementation is adapted from os.RemoveAll.
-
- // Simple case: if Remove works, we're done.
- err := fs.Remove(path)
- if err == nil || os.IsNotExist(err) {
- return nil
- }
-
- // Otherwise, is this a directory we need to recurse into?
- dir, serr := fs.Stat(path)
- if serr != nil {
- if os.IsNotExist(serr) {
- return nil
- }
-
- return serr
- }
-
- if !dir.IsDir() {
- // Not a directory; return the error from Remove.
- return err
- }
-
- // Directory.
- fis, err := fs.ReadDir(path)
- if err != nil {
- if os.IsNotExist(err) {
- // Race. It was deleted between the Lstat and Open.
- // Return nil per RemoveAll's docs.
- return nil
- }
-
- return err
- }
-
- // Remove contents & return first error.
- err = nil
- for _, fi := range fis {
- cpath := fs.Join(path, fi.Name())
- err1 := removeAll(fs, cpath)
- if err == nil {
- err = err1
- }
- }
-
- // Remove directory.
- err1 := fs.Remove(path)
- if err1 == nil || os.IsNotExist(err1) {
- return nil
- }
-
- if err == nil {
- err = err1
- }
-
- return err
-
-}
-
-// WriteFile writes data to a file named by filename in the given filesystem.
-// If the file does not exist, WriteFile creates it with permissions perm;
-// otherwise WriteFile truncates it before writing.
-func WriteFile(fs Filesystem, filename string, data []byte, perm os.FileMode) error {
- f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
- if err != nil {
- return err
- }
-
- n, err := f.Write(data)
- if err == nil && n < len(data) {
- err = io.ErrShortWrite
- }
-
- if err1 := f.Close(); err == nil {
- err = err1
- }
-
- return err
-}