From fe8e8953368bff0a84babecf4e2a6c965c0453c8 Mon Sep 17 00:00:00 2001 From: Steve Francia Date: Mon, 11 Jan 2016 21:41:03 -0500 Subject: [PATCH] Migrate all backends & readme to use constructorish New... --- README.md | 65 +++++++++++++++++++++++------------------------ basepath.go | 4 +++ basepath_test.go | 2 +- httpFs.go | 13 ++++++++++ memmap.go | 4 +++ memmap_test.go | 2 +- os.go | 4 +++ readonlyfs.go | 4 +++ regexpfs.go | 4 +++ ro_regexp_test.go | 4 +-- union.go | 2 +- union_cache.go | 4 +++ union_cow.go | 4 +++ union_test.go | 4 +-- 14 files changed, 80 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index f63e748..56d5a0b 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,11 @@ import "github.com/spf13/afero" First define a package variable and set it to a pointer to a filesystem. ```go -var AppFs afero.Fs = &afero.MemMapFs{} +var AppFs afero.Fs = afero.NewMemMapFs() or -var AppFs afero.Fs = &afero.OsFs{} +var AppFs afero.Fs = afero.NewOsFs() ``` It is important to note that if you repeat the composite literal you will be using a completely new and isolated filesystem. In the case of @@ -166,7 +166,7 @@ f, err := afero.TempFile(fs,"", "ioutil-test") ### Calling via Afero ```go -fs := new(afero.MemMapFs) +fs := afero.NewMemMapFs afs := &Afero{Fs: fs} f, err := afs.TempFile("", "ioutil-test") ``` @@ -187,39 +187,28 @@ backend is perfect for testing. * No test cleanup needed One way to accomplish this is to define a variable as mentioned above. -In your application this will be set to &afero.OsFs{} during testing you -can set it to &afero.MemMapFs{}. +In your application this will be set to afero.NewOsFs() during testing you +can set it to afero.NewMemMapFs(). It wouldn't be uncommon to have each test initialize a blank slate memory -backend. To do this I would define my `appFS = &afero.OsFs{}` somewhere +backend. To do this I would define my `appFS = afero.NewOsFs()` somewhere appropriate in my application code. This approach ensures that Tests are order independent, with no test relying on the state left by an earlier test. Then in my tests I would initialize a new MemMapFs for each test: ```go func TestExist(t *testing.T) { - appFS = &afero.MemMapFs{} + appFS = afero.NewMemMapFs() // create test files and directories appFS.MkdirAll("src/a", 0755)) appFS.WriteFile("src/a/b", []byte("file b"), 0644) appFS.WriteFile("src/c", []byte("file c"), 0644) - testExistence("src/c", true, t) -} - -func testExistence(name string, e bool, t *testing.T) { - _, err := appFS.Stat(name) + _, err := appFS.Stat("src/c") if os.IsNotExist(err) { - if e { - t.Errorf("file \"%s\" does not exist.\n", name) - } - } else if err != nil { - panic(err) - } else { - if !e { - t.Errorf("file \"%s\" exists.\n", name) - } + t.Errorf("file \"%s\" does not exist.\n", name) } } + ``` # Available Backends @@ -233,6 +222,11 @@ very easy to use as all of the calls are the same as the existing OS calls. It also makes it trivial to have your code use the OS during operation and a mock filesystem during testing or as needed. +```go +appfs := NewOsFs() +appfs.MkdirAll("src/a", 0755)) +``` + ## Memory Backed Storage ### MemMapFs @@ -242,6 +236,11 @@ mocking and to speed up unnecessary disk io when persistence isn’t necessary. It is fully concurrent and will work within go routines safely. +```go +mm := NewMemMapFs() +mm.MkdirAll("src/a", 0755)) +``` + #### InMemoryFile As part of MemMapFs, Afero also provides an atomic, fully concurrent memory @@ -265,7 +264,7 @@ The given file name to the operations on this Fs will be prepended with the base path before calling the source Fs. ```go -bp := &BasePathFs{source: &OsFs{}, path: "/base/path"} +bp := NewBasePathFs(NewOsFs(), "/base/path") ``` ### ReadOnlyFs @@ -273,7 +272,7 @@ bp := &BasePathFs{source: &OsFs{}, path: "/base/path"} A thin wrapper around the source Fs providing a read only view. ```go -fs := &ReadOnlyFs{source: &OsFs{}} +fs := NewReadOnlyFs(NewOsFs()) _, err := fs.Create("/file.txt") // err = syscall.EPERM ``` @@ -286,7 +285,7 @@ Files not matching the regexp provided will not be created. Directories are not filtered. ```go -fs := &RegexpFs{re: regexp.MustCompile(`\.txt$`), source: &MemMapFs{}} +fs := NewRegexpFs(NewMemMapFs(), regexp.MustCompile(`\.txt$`)) _, err := fs.Create("/file.html") // err = syscall.ENOENT ``` @@ -303,7 +302,7 @@ Afero provides an httpFs file system which satisfies this requirement. Any Afero FileSystem can be used as an httpFs. ```go -httpFs := &afero.HttpFs{source: } +httpFs := afero.NewHttpFs() fileserver := http.FileServer(httpFs.Dir())) http.Handle("/", fileserver) ``` @@ -335,9 +334,9 @@ from the base to the overlay when they're not present (or outdated) in the caching layer. ```go -base := &OsFs{} -layer := &MemMapFs{} -ufs := &CacheOnReadFs{base: base, layer: layer, cacheTime: 100 * time.Second} +base := NewOsFs() +layer := NewMemMapFs() +ufs := NewCacheOnReadFs(base, layer, 100 * time.Second) ``` ### CopyOnWriteFs() @@ -361,17 +360,17 @@ overlay will be removed/renamed. The writable overlay layer is currently limited to MemMapFs. ```go - base := &OsFs{} - roBase := &ReadOnlyFs{source: base} - ufs := &CopyOnWriteFs{base: roBase, layer: &MemMapFs{}} + base := NewOsFs() + roBase := NewReadOnlyFs(base) + ufs := NewCopyOnWriteFs(roBase, NewMemMapFs()) fh, _ = ufs.Create("/home/test/file2.txt") fh.WriteString("This is a test") fh.Close() ``` -In this example all write operations will only occur in memory (&MemMapFs{}) -leaving the base filesystem (&OsFs) untouched. +In this example all write operations will only occur in memory (MemMapFs) +leaving the base filesystem (OsFs) untouched. ## Desired/possible backends diff --git a/basepath.go b/basepath.go index 083199f..b9fa145 100644 --- a/basepath.go +++ b/basepath.go @@ -20,6 +20,10 @@ type BasePathFs struct { path string } +func NewBasePathFs(source Fs, path string) Fs { + return &BasePathFs{source: source, path: path} +} + // on a file outside the base path it returns the given file name and an error, // else the given file with the base path prepended func (b *BasePathFs) RealPath(name string) (path string, err error) { diff --git a/basepath_test.go b/basepath_test.go index 0db6783..ee9f2d3 100644 --- a/basepath_test.go +++ b/basepath_test.go @@ -7,7 +7,7 @@ import ( func TestBasePath(t *testing.T) { baseFs := &MemMapFs{} baseFs.MkdirAll("/base/path/tmp", 0777) - bp := &BasePathFs{source: baseFs, path: "/base/path"} + bp := NewBasePathFs(baseFs, "/base/path") if _, err := bp.Create("/tmp/foo"); err != nil { t.Errorf("Failed to set real path") diff --git a/httpFs.go b/httpFs.go index c7f1955..c421936 100644 --- a/httpFs.go +++ b/httpFs.go @@ -20,6 +20,7 @@ import ( "path" "path/filepath" "strings" + "time" ) type httpDir struct { @@ -48,6 +49,10 @@ type HttpFs struct { source Fs } +func NewHttpFs(source Fs) *HttpFs { + return &HttpFs{source: source} +} + func (h HttpFs) Dir(s string) *httpDir { return &httpDir{basePath: s, fs: h} } @@ -58,6 +63,14 @@ func (h HttpFs) Create(name string) (File, error) { return h.source.Create(name) } +func (h HttpFs) Chmod(name string, mode os.FileMode) error { + return h.source.Chmod(name, mode) +} + +func (h HttpFs) Chtimes(name string, atime time.Time, mtime time.Time) error { + return h.source.Chtimes(name, atime, mtime) +} + func (h HttpFs) Mkdir(name string, perm os.FileMode) error { return h.source.Mkdir(name, perm) } diff --git a/memmap.go b/memmap.go index 21fbe67..f577ac8 100644 --- a/memmap.go +++ b/memmap.go @@ -31,6 +31,10 @@ type MemMapFs struct { init sync.Once } +func NewMemMapFs() Fs { + return &MemMapFs{} +} + var memfsInit sync.Once func (m *MemMapFs) getData() map[string]*mem.FileData { diff --git a/memmap_test.go b/memmap_test.go index bb2769b..6e0f826 100644 --- a/memmap_test.go +++ b/memmap_test.go @@ -34,7 +34,7 @@ func TestNormalizePath(t *testing.T) { func TestPathErrors(t *testing.T) { path := filepath.Join(".", "some", "path") path2 := filepath.Join(".", "different", "path") - fs := &MemMapFs{} + fs := NewMemMapFs() perm := os.FileMode(0755) // relevant functions: diff --git a/os.go b/os.go index 30f23ca..4c9c0f6 100644 --- a/os.go +++ b/os.go @@ -25,6 +25,10 @@ import ( // (http://golang.org/pkg/os/). type OsFs struct{} +func NewOsFs() Fs { + return &OsFs{} +} + func (OsFs) Name() string { return "OsFs" } func (OsFs) Create(name string) (File, error) { diff --git a/readonlyfs.go b/readonlyfs.go index fa23703..f1fa55b 100644 --- a/readonlyfs.go +++ b/readonlyfs.go @@ -10,6 +10,10 @@ type ReadOnlyFs struct { source Fs } +func NewReadOnlyFs(source Fs) Fs { + return &ReadOnlyFs{source: source} +} + func (r *ReadOnlyFs) ReadDir(name string) ([]os.FileInfo, error) { return ReadDir(r.source, name) } diff --git a/regexpfs.go b/regexpfs.go index 071b1bd..9d92dbc 100644 --- a/regexpfs.go +++ b/regexpfs.go @@ -16,6 +16,10 @@ type RegexpFs struct { source Fs } +func NewRegexpFs(source Fs, re *regexp.Regexp) Fs { + return &RegexpFs{source: source, re: re} +} + type RegexpFile struct { f File re *regexp.Regexp diff --git a/ro_regexp_test.go b/ro_regexp_test.go index d408fab..ef8a35d 100644 --- a/ro_regexp_test.go +++ b/ro_regexp_test.go @@ -20,7 +20,7 @@ func TestFilterReadonlyRemoveAndRead(t *testing.T) { fh.Write([]byte("content here")) fh.Close() - fs := &ReadOnlyFs{source: mfs} + fs := NewReadOnlyFs(mfs) err = fs.Remove("/file.txt") if err == nil { t.Errorf("Did not fail to remove file") @@ -51,7 +51,7 @@ func TestFilterReadonlyRemoveAndRead(t *testing.T) { } func TestFilterRegexp(t *testing.T) { - fs := &RegexpFs{re: regexp.MustCompile(`\.txt$`), source: &MemMapFs{}} + fs := NewRegexpFs(&MemMapFs{}, regexp.MustCompile(`\.txt$`)) _, err := fs.Create("/file.html") if err == nil { diff --git a/union.go b/union.go index 977232b..a854e5b 100644 --- a/union.go +++ b/union.go @@ -21,8 +21,8 @@ import ( // successful read in the overlay will move the cursor position in the base layer // by the number of bytes read. type UnionFile struct { - layer File base File + layer File off int files []os.FileInfo } diff --git a/union_cache.go b/union_cache.go index aadcca9..d742425 100644 --- a/union_cache.go +++ b/union_cache.go @@ -25,6 +25,10 @@ type CacheOnReadFs struct { cacheTime time.Duration } +func NewCacheOnReadFs(base Fs, layer Fs, cacheTime time.Duration) Fs { + return &CacheOnReadFs{base: base, layer: layer, cacheTime: cacheTime} +} + type cacheState int const ( diff --git a/union_cow.go b/union_cow.go index d01b7ae..176aeb7 100644 --- a/union_cow.go +++ b/union_cow.go @@ -22,6 +22,10 @@ type CopyOnWriteFs struct { layer Fs } +func NewCopyOnWriteFs(base Fs, layer Fs) Fs { + return &CopyOnWriteFs{base: base, layer: layer} +} + func (u *CopyOnWriteFs) isBaseFile(name string) (bool, error) { if _, err := u.layer.Stat(name); err == nil { return false, nil diff --git a/union_test.go b/union_test.go index 221d032..e8d7be6 100644 --- a/union_test.go +++ b/union_test.go @@ -11,7 +11,7 @@ func TestUnionCreateExisting(t *testing.T) { base := &MemMapFs{} roBase := &ReadOnlyFs{source: base} - ufs := &CopyOnWriteFs{base: roBase, layer: &MemMapFs{}} + ufs := NewCopyOnWriteFs(roBase, &MemMapFs{}) base.MkdirAll("/home/test", 0777) fh, _ := base.Create("/home/test/file.txt") @@ -86,7 +86,7 @@ func TestUnionCacheWrite(t *testing.T) { base := &MemMapFs{} layer := &MemMapFs{} - ufs := &CacheOnReadFs{base: base, layer: layer, cacheTime: 0} + ufs := NewCacheOnReadFs(base, layer, 0) base.Mkdir("/data", 0777)