diff --git a/pathtools/fs.go b/pathtools/fs.go index 47b58c3482e725a0a499c4347376bb72bcad76fd..42174872659a7b6bc6837e26a708a9bfbb4c1acd 100644 --- a/pathtools/fs.go +++ b/pathtools/fs.go @@ -106,6 +106,9 @@ type FileSystem interface { // Lstat returns info on a file without following symlinks. Lstat(name string) (os.FileInfo, error) + // Lstat returns info on a file. + Stat(name string) (os.FileInfo, error) + // ListDirsRecursive returns a list of all the directories in a path, following symlinks if requested. ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) @@ -159,6 +162,10 @@ func (osFs) Lstat(path string) (stats os.FileInfo, err error) { return os.Lstat(path) } +func (osFs) Stat(path string) (stats os.FileInfo, err error) { + return os.Stat(path) +} + // Returns a list of all directories under dir func (osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) { return listDirsRecursive(OsFs, name, follow) @@ -349,24 +356,46 @@ func (ms *mockStat) ModTime() time.Time { return time.Time{} } func (ms *mockStat) Sys() interface{} { return nil } func (m *mockFs) Lstat(name string) (os.FileInfo, error) { - name = filepath.Clean(name) + dir, file := saneSplit(name) + dir = m.followSymlinks(dir) + name = filepath.Join(dir, file) ms := mockStat{ - name: name, - size: int64(len(m.files[name])), + name: file, } - if isSymlink, err := m.IsSymlink(name); err != nil { - // IsSymlink handles ErrNotExist - return nil, err - } else if isSymlink { + if symlink, isSymlink := m.symlinks[name]; isSymlink { ms.mode = os.ModeSymlink - } else if isDir, err := m.IsDir(name); err != nil { - return nil, err - } else if isDir { + ms.size = int64(len(symlink)) + } else if _, isDir := m.dirs[name]; isDir { ms.mode = os.ModeDir + } else if _, isFile := m.files[name]; isFile { + ms.mode = 0 + ms.size = int64(len(m.files[name])) } else { + return nil, os.ErrNotExist + } + + return &ms, nil +} + +func (m *mockFs) Stat(name string) (os.FileInfo, error) { + name = filepath.Clean(name) + origName := name + name = m.followSymlinks(name) + + ms := mockStat{ + name: filepath.Base(origName), + size: int64(len(m.files[name])), + } + + if _, isDir := m.dirs[name]; isDir { + ms.mode = os.ModeDir + } else if _, isFile := m.files[name]; isFile { ms.mode = 0 + ms.size = int64(len(m.files[name])) + } else { + return nil, os.ErrNotExist } return &ms, nil diff --git a/pathtools/fs_test.go b/pathtools/fs_test.go index 3bcce2f8dcf7799746dea802c033e6571fba24cd..1b5c458110ae1304ac8dc87e72e986bc19d43256 100644 --- a/pathtools/fs_test.go +++ b/pathtools/fs_test.go @@ -409,11 +409,89 @@ func TestFs_Lstat(t *testing.T) { return } if got.Mode()&os.ModeType != test.mode { - t.Errorf("fs.Readlink(%q).Mode()&os.ModeType want: %x, got %x", + t.Errorf("fs.Lstat(%q).Mode()&os.ModeType want: %x, got %x", test.name, test.mode, got.Mode()&os.ModeType) } if test.mode == 0 && got.Size() != test.size { - t.Errorf("fs.Readlink(%q).Size() want: %d, got %d", test.name, test.size, got.Size()) + t.Errorf("fs.Lstat(%q).Size() want: %d, got %d", test.name, test.size, got.Size()) + } + }) + } + }) + } +} + +func TestFs_Stat(t *testing.T) { + testCases := []struct { + name string + mode os.FileMode + size int64 + err error + }{ + {".", os.ModeDir, 0, nil}, + {"/", os.ModeDir, 0, nil}, + + {"a", os.ModeDir, 0, nil}, + {"a/a", os.ModeDir, 0, nil}, + {"a/a/a", 0, 0, nil}, + {"a/a/f", 0, 0, nil}, + + {"b", os.ModeDir, 0, nil}, + {"b/a", os.ModeDir, 0, nil}, + {"b/a/a", 0, 0, nil}, + {"b/a/f", 0, 0, nil}, + + {"c", os.ModeDir, 0, nil}, + {"c/a", 0, 0, nil}, + {"c/f", 0, 0, nil}, + + {"d/a", 0, 0, nil}, + {"d/f", 0, 0, nil}, + + {"e", 0, 0, nil}, + + {"f", 0, 0, nil}, + + {"dangling", 0, 0, os.ErrNotExist}, + + {"a/missing", 0, 0, os.ErrNotExist}, + {"b/missing", 0, 0, os.ErrNotExist}, + {"c/missing", 0, 0, os.ErrNotExist}, + {"d/missing", 0, 0, os.ErrNotExist}, + {"e/missing", 0, 0, os.ErrNotExist}, + {"dangling/missing", 0, 0, os.ErrNotExist}, + + {"a/missing/missing", 0, 0, os.ErrNotExist}, + {"b/missing/missing", 0, 0, os.ErrNotExist}, + {"c/missing/missing", 0, 0, os.ErrNotExist}, + {"d/missing/missing", 0, 0, os.ErrNotExist}, + {"e/missing/missing", 0, 0, os.ErrNotExist}, + {"dangling/missing/missing", 0, 0, os.ErrNotExist}, + } + + mock := symlinkMockFs() + fsList := []FileSystem{mock, OsFs} + names := []string{"mock", "os"} + + os.Chdir("testdata/dangling") + defer os.Chdir("../..") + + for i, fs := range fsList { + t.Run(names[i], func(t *testing.T) { + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + got, err := fs.Stat(test.name) + checkErr(t, test.err, err) + if err != nil { + return + } + if got.Mode()&os.ModeType != test.mode { + t.Errorf("fs.Stat(%q).Mode()&os.ModeType want: %x, got %x", + test.name, test.mode, got.Mode()&os.ModeType) + } + if test.mode == 0 && got.Size() != test.size { + t.Errorf("fs.Stat(%q).Size() want: %d, got %d", test.name, test.size, got.Size()) } }) }