From acf3d82e02bb393d1713243c0bc4aeb2418c514a Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Mon, 6 Jul 2020 21:22:11 -0500 Subject: [PATCH] Adding pathlib.File. Adding tests Add more tests and fix bug where IsSymlink didn't work properly because it called Stat and not Lstat --- file.go | 9 ++++++ path.go | 28 +++++++++++++----- path_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 file.go diff --git a/file.go b/file.go new file mode 100644 index 0000000..82e43e1 --- /dev/null +++ b/file.go @@ -0,0 +1,9 @@ +package pathlib + +import "github.com/spf13/afero" + +// File represents a file in the filesystem. It inherits the afero.File interface +// but might also include additional functionality. +type File struct { + afero.File +} diff --git a/path.go b/path.go index 4dd7077..e7c50db 100644 --- a/path.go +++ b/path.go @@ -90,14 +90,21 @@ func (p *Path) MkdirAll(perm os.FileMode) error { return p.Fs().MkdirAll(p.Path(), perm) } -// Open opens a file, returning it or an error, if any happens. -func (p *Path) Open() (afero.File, error) { - return p.Fs().Open(p.Path()) +// Open opens a file for read-only, returning it or an error, if any happens. +func (p *Path) Open() (*File, error) { + handle, err := p.Fs().Open(p.Path()) + return &File{ + File: handle, + }, err } // OpenFile opens a file using the given flags and the given mode. -func (p *Path) OpenFile(flag int, perm os.FileMode) (afero.File, error) { - return p.Fs().OpenFile(p.Path(), flag, perm) +// See the list of flags at: https://golang.org/pkg/os/#pkg-constants +func (p *Path) OpenFile(flag int, perm os.FileMode) (*File, error) { + handle, err := p.Fs().OpenFile(p.Path(), flag, perm) + return &File{ + File: handle, + }, err } // Remove removes a file, returning an error, if any @@ -342,9 +349,16 @@ func (p *Path) IsFile() (bool, error) { } // IsSymlink returns true if the given path is a symlink. +// Fails if the filesystem doesn't implement afero.Lstater. func (p *Path) IsSymlink() (bool, error) { - fileInfo, err := p.Fs().Stat(p.Path()) - if err != nil { + lStater, ok := p.Fs().(afero.Lstater) + if !ok { + return false, p.doesNotImplementErr("afero.Lstater") + } + fileInfo, lstatCalled, err := lStater.LstatIfPossible(p.Path()) + if err != nil || !lstatCalled { + // If lstat wasn't called then the filesystem doesn't implement it. + // Thus, it isn't a symlink return false, err } diff --git a/path_test.go b/path_test.go index 7e072ea..5720f22 100644 --- a/path_test.go +++ b/path_test.go @@ -1,7 +1,9 @@ package pathlib import ( + "errors" "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -21,6 +23,9 @@ type PathSuite struct { } func (p *PathSuite) SetupTest() { + // We actually can't use the MemMapFs because some of the tests + // are testing symlink behavior. We might want to split these + // tests out to use MemMapFs when possible. tmpdir, err := ioutil.TempDir("", "") require.NoError(p.T(), err) p.tmpdir = NewPath(tmpdir) @@ -150,6 +155,81 @@ func (p *PathSuite) TestGetLatestEmpty() { assert.Nil(p.T(), latest) } +func (p *PathSuite) TestOpen() { + msg := "cubs > cardinals" + file := p.tmpdir.Join("file.txt") + require.NoError(p.T(), file.WriteFile([]byte(msg), 0o644)) + fileHandle, err := file.Open() + require.NoError(p.T(), err) + + readBytes := make([]byte, len(msg)+5) + n, err := fileHandle.Read(readBytes) + p.Equal(len(msg), n) + p.Equal(msg, string(readBytes[0:n])) +} + +func (p *PathSuite) TestOpenFile() { + file := p.tmpdir.Join("file.txt") + fileHandle, err := file.OpenFile(os.O_RDWR|os.O_CREATE, 0o644) + require.NoError(p.T(), err) + + msg := "do you play croquet?" + n, err := fileHandle.WriteString(msg) + p.Equal(len(msg), n) + p.NoError(err) + + bytes := make([]byte, len(msg)+5) + n, err = fileHandle.ReadAt(bytes, 0) + p.Equal(len(msg), n) + p.True(errors.Is(err, io.EOF)) + p.Equal(msg, string(bytes[0:n])) +} + +func (p *PathSuite) TestDirExists() { + dir1 := p.tmpdir.Join("subdir") + exists, err := dir1.DirExists() + require.NoError(p.T(), err) + p.False(exists) + + require.NoError(p.T(), dir1.Mkdir(0o755)) + exists, err = dir1.DirExists() + require.NoError(p.T(), err) + p.True(exists) +} + +func (p *PathSuite) TestIsFile() { + file1 := p.tmpdir.Join("file.txt") + + require.NoError(p.T(), file1.WriteFile([]byte(""), 0o644)) + exists, err := file1.IsFile() + require.NoError(p.T(), err) + p.True(exists) +} + +func (p *PathSuite) TestIsEmpty() { + file1 := p.tmpdir.Join("file.txt") + + require.NoError(p.T(), file1.WriteFile([]byte(""), 0o644)) + isEmpty, err := file1.IsEmpty() + require.NoError(p.T(), err) + p.True(isEmpty) +} + +func (p *PathSuite) TestIsSymlink() { + file1 := p.tmpdir.Join("file.txt") + require.NoError(p.T(), file1.WriteFile([]byte(""), 0o644)) + + symlink := p.tmpdir.Join("symlink") + p.NoError(symlink.Symlink(file1)) + isSymlink, err := symlink.IsSymlink() + require.NoError(p.T(), err) + p.True(isSymlink) + + stat, _ := symlink.Stat() + p.T().Logf("%v", stat.Mode()) + p.T().Logf(symlink.Path()) +} + func TestPathSuite(t *testing.T) { suite.Run(t, new(PathSuite)) }