From 1ec2a1e21ea8ccfbbc1f26010505d3fd5c19bd10 Mon Sep 17 00:00:00 2001 From: plastikfan Date: Fri, 25 Oct 2024 13:33:07 +0100 Subject: [PATCH] test(luna): define a testing memory-fs (#35) --- internal/laboratory/traverse-fs.go | 105 ---------------- luna/luna-suite_test.go | 13 ++ luna/mem-fs.go | 178 ++++++++++++++++++++++++++++ luna/mem-fs_test.go | 63 ++++++++++ scripts/coverage-exclusion-list.txt | 3 +- 5 files changed, 256 insertions(+), 106 deletions(-) delete mode 100644 internal/laboratory/traverse-fs.go create mode 100644 luna/luna-suite_test.go create mode 100644 luna/mem-fs.go create mode 100644 luna/mem-fs_test.go diff --git a/internal/laboratory/traverse-fs.go b/internal/laboratory/traverse-fs.go deleted file mode 100644 index 59b9bda..0000000 --- a/internal/laboratory/traverse-fs.go +++ /dev/null @@ -1,105 +0,0 @@ -package lab - -import ( - "io/fs" - "os" - "strings" - "testing/fstest" - - nef "github.com/snivilised/nefilim" - "github.com/snivilised/nefilim/internal/third/lo" -) - -type testMapFile struct { - f fstest.MapFile -} - -type TestTraverseFS struct { - fstest.MapFS -} - -func (f *TestTraverseFS) FileExists(name string) bool { - if mapFile, found := f.MapFS[name]; found && !mapFile.Mode.IsDir() { - return true - } - - return false -} - -func (f *TestTraverseFS) DirectoryExists(name string) bool { - if mapFile, found := f.MapFS[name]; found && mapFile.Mode.IsDir() { - return true - } - - return false -} - -func (f *TestTraverseFS) Create(name string) (*os.File, error) { - if _, err := f.Stat(name); err == nil { - return nil, fs.ErrExist - } - - file := &fstest.MapFile{ - Mode: Perms.File, - } - - f.MapFS[name] = file - // TODO: this needs a resolution using a file interface - // rather than using os.File which is a struct not an - // interface - dummy := &os.File{} - - return dummy, nil -} - -func (f *TestTraverseFS) MakeDir(name string, perm os.FileMode) error { - if !fs.ValidPath(name) { - return nef.NewInvalidPathError("MakeDir", name) - } - - if _, found := f.MapFS[name]; !found { - f.MapFS[name] = &fstest.MapFile{ - Mode: perm | os.ModeDir, - } - } - - return nil -} - -func (f *TestTraverseFS) MakeDirAll(name string, perm os.FileMode) error { - if !fs.ValidPath(name) { - return nef.NewInvalidPathError("MakeDirAll", name) - } - - segments := strings.Split(name, "/") - - _ = lo.Reduce(segments, - func(acc []string, s string, _ int) []string { - acc = append(acc, s) - path := strings.Join(acc, "/") - - if _, found := f.MapFS[path]; !found { - f.MapFS[path] = &fstest.MapFile{ - Mode: perm | os.ModeDir, - } - } - - return acc - }, []string{}, - ) - - return nil -} - -func (f *TestTraverseFS) WriteFile(name string, data []byte, perm os.FileMode) error { - if _, err := f.Stat(name); err == nil { - return fs.ErrExist - } - - f.MapFS[name] = &fstest.MapFile{ - Data: data, - Mode: perm, - } - - return nil -} diff --git a/luna/luna-suite_test.go b/luna/luna-suite_test.go new file mode 100644 index 0000000..67e6e64 --- /dev/null +++ b/luna/luna-suite_test.go @@ -0,0 +1,13 @@ +package luna_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" //nolint:revive // ok + . "github.com/onsi/gomega" //nolint:revive // ok +) + +func TestLuna(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Luna Suite") +} diff --git a/luna/mem-fs.go b/luna/mem-fs.go new file mode 100644 index 0000000..f659704 --- /dev/null +++ b/luna/mem-fs.go @@ -0,0 +1,178 @@ +package luna + +import ( + "io/fs" + "os" + "strings" + "testing/fstest" + + nef "github.com/snivilised/nefilim" + lab "github.com/snivilised/nefilim/internal/laboratory" + "github.com/snivilised/nefilim/internal/third/lo" +) + +// MemFS is a memory fs based on fstest.MapFS intended to be used in +// unit tests. Clients can embed and override the methods defined here +// without having to provide a full implementation from scratch. +type MemFS struct { + fstest.MapFS +} + +var ( + _ nef.UniversalFS = (*MemFS)(nil) +) + +func NewMemFS() *MemFS { + return &MemFS{ + MapFS: fstest.MapFS{}, + } +} + +func (f *MemFS) FileExists(name string) bool { + if mapFile, found := f.MapFS[name]; found && !mapFile.Mode.IsDir() { + return true + } + + return false +} + +func (f *MemFS) DirectoryExists(name string) bool { + if mapFile, found := f.MapFS[name]; found && mapFile.Mode.IsDir() { + return true + } + + return false +} + +func (f *MemFS) Create(name string) (*os.File, error) { + if _, err := f.Stat(name); err == nil { + return nil, fs.ErrExist + } + + file := &fstest.MapFile{ + Mode: lab.Perms.File, + } + + f.MapFS[name] = file + // TODO: this needs a resolution using a file interface + // rather than using os.File which is a struct not an + // interface + dummy := &os.File{} + + return dummy, nil +} + +func (f *MemFS) MakeDir(name string, perm os.FileMode) error { + if !fs.ValidPath(name) { + return nef.NewInvalidPathError("MakeDir", name) + } + + if _, found := f.MapFS[name]; !found { + f.MapFS[name] = &fstest.MapFile{ + Mode: perm | os.ModeDir, + } + } + + return nil +} + +func (f *MemFS) MakeDirAll(name string, perm os.FileMode) error { + if !fs.ValidPath(name) { + return nef.NewInvalidPathError("MakeDirAll", name) + } + + segments := strings.Split(name, "/") + + _ = lo.Reduce(segments, + func(acc []string, s string, _ int) []string { + acc = append(acc, s) + path := strings.Join(acc, "/") + + if _, found := f.MapFS[path]; !found { + f.MapFS[path] = &fstest.MapFile{ + Mode: perm | os.ModeDir, + } + } + + return acc + }, []string{}, + ) + + return nil +} + +// Ensure is not currently implemented on MemFS +func (f *MemFS) Ensure(_ nef.PathAs) (string, error) { + return "", nil +} + +// Move is not currently implemented on MemFS +func (f *MemFS) Move(_, _ string) error { + return nil +} + +// Change is not currently implemented on MemFS +func (f *MemFS) Change(_, _ string) error { + return nil +} + +// Copy is not currently implemented on MemFS +func (f *MemFS) Copy(_, _ string) error { + return nil +} + +func (f *MemFS) CopyFS(_ string, _ fs.FS) error { + return nil +} + +// Remove removes the named file or (empty) directory. +// If there is an error, it will be of type *PathError. +func (f *MemFS) Remove(name string) error { + if _, found := f.MapFS[name]; found { + delete(f.MapFS, name) + return nil + } + + return os.ErrNotExist +} + +func (f *MemFS) RemoveAll(path string) error { + keys := lo.Keys(f.MapFS) + matched := lo.Filter(keys, func(item string, _ int) bool { + return strings.HasPrefix(item, path) + }) + + if len(matched) == 0 { + return os.ErrNotExist + } + + for _, item := range matched { + delete(f.MapFS, item) + } + + return nil +} + +func (f *MemFS) Rename(from, to string) error { + if item, found := f.MapFS[from]; found { + delete(f.MapFS, from) + f.MapFS[to] = item + + return nil + } + + return os.ErrNotExist +} + +func (f *MemFS) WriteFile(name string, data []byte, perm os.FileMode) error { + if _, err := f.Stat(name); err == nil { + return fs.ErrExist + } + + f.MapFS[name] = &fstest.MapFile{ + Data: data, + Mode: perm, + } + + return nil +} diff --git a/luna/mem-fs_test.go b/luna/mem-fs_test.go new file mode 100644 index 0000000..4a11db6 --- /dev/null +++ b/luna/mem-fs_test.go @@ -0,0 +1,63 @@ +package luna_test + +import ( + "os" + + . "github.com/onsi/ginkgo/v2" //nolint:revive // ok + . "github.com/onsi/gomega" //nolint:revive // ok + + nef "github.com/snivilised/nefilim" + lab "github.com/snivilised/nefilim/internal/laboratory" + "github.com/snivilised/nefilim/luna" +) + +var ( + data = []byte("some content") +) + +var _ = Describe("MemFs", func() { + var fS nef.UniversalFS + + BeforeEach(func() { + fS = luna.NewMemFS() + }) + + Context("Remove", func() { + When("given: item exists", func() { + It("🧪 should: remove item", func() { + path := "widgets/foo.txt" + Expect(fS.WriteFile(path, data, lab.Perms.File)).To(Succeed()) + Expect(fS.Remove(path)).To(Succeed()) + Expect(fS.FileExists(path)).To(BeFalse()) + }) + }) + + When("given: item does NOT exist", func() { + It("🧪 should: return error", func() { + path := "missing/foo.txt" + Expect(fS.Remove(path)).To(MatchError(os.ErrNotExist), + "expected error os.ErrNotExist", + ) + }) + }) + }) + + Context("RemoveAll", func() { + When("given: some items exist", func() { + It("🧪 should: remove only matching item", func() { + foo := "a/b/foo.txt" + Expect(fS.WriteFile(foo, data, lab.Perms.File)).To(Succeed()) + bar := "a/b/bar.txt" + Expect(fS.WriteFile(bar, data, lab.Perms.File)).To(Succeed()) + baz := "bin/baz.txt" + Expect(fS.WriteFile(baz, data, lab.Perms.File)).To(Succeed()) + path := "a/b" + + Expect(fS.RemoveAll(path)).To(Succeed(), "failed to remove all in path") + Expect(fS.FileExists(foo)).To(BeFalse(), "foo should not exist") + Expect(fS.FileExists(bar)).To(BeFalse(), "bar should not exist") + Expect(fS.FileExists(baz)).To(BeTrue(), "baz should still exist") + }) + }) + }) +}) diff --git a/scripts/coverage-exclusion-list.txt b/scripts/coverage-exclusion-list.txt index 94ba9b5..756c004 100644 --- a/scripts/coverage-exclusion-list.txt +++ b/scripts/coverage-exclusion-list.txt @@ -1,3 +1,4 @@ +github.com/snivilised/nefilim/luna github.com/snivilised/nefilim/fs-absolute.go github.com/snivilised/nefilim/internal/third -github.com/snivilised/nefilim/internal/laboratory/ +github.com/snivilised/nefilim/internal/laboratory