Migrate all backends & readme to use constructorish New...

This commit is contained in:
Steve Francia 2016-01-11 21:41:03 -05:00
parent 4f6cfb713a
commit fe8e895336
14 changed files with 80 additions and 40 deletions

View File

@ -61,11 +61,11 @@ import "github.com/spf13/afero"
First define a package variable and set it to a pointer to a filesystem. First define a package variable and set it to a pointer to a filesystem.
```go ```go
var AppFs afero.Fs = &afero.MemMapFs{} var AppFs afero.Fs = afero.NewMemMapFs()
or 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 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 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 ### Calling via Afero
```go ```go
fs := new(afero.MemMapFs) fs := afero.NewMemMapFs
afs := &Afero{Fs: fs} afs := &Afero{Fs: fs}
f, err := afs.TempFile("", "ioutil-test") f, err := afs.TempFile("", "ioutil-test")
``` ```
@ -187,39 +187,28 @@ backend is perfect for testing.
* No test cleanup needed * No test cleanup needed
One way to accomplish this is to define a variable as mentioned above. 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 In your application this will be set to afero.NewOsFs() during testing you
can set it to &afero.MemMapFs{}. can set it to afero.NewMemMapFs().
It wouldn't be uncommon to have each test initialize a blank slate memory 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 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. 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: Then in my tests I would initialize a new MemMapFs for each test:
```go ```go
func TestExist(t *testing.T) { func TestExist(t *testing.T) {
appFS = &afero.MemMapFs{} appFS = afero.NewMemMapFs()
// create test files and directories // create test files and directories
appFS.MkdirAll("src/a", 0755)) appFS.MkdirAll("src/a", 0755))
appFS.WriteFile("src/a/b", []byte("file b"), 0644) appFS.WriteFile("src/a/b", []byte("file b"), 0644)
appFS.WriteFile("src/c", []byte("file c"), 0644) appFS.WriteFile("src/c", []byte("file c"), 0644)
testExistence("src/c", true, t) _, err := appFS.Stat("src/c")
}
func testExistence(name string, e bool, t *testing.T) {
_, err := appFS.Stat(name)
if os.IsNotExist(err) { if os.IsNotExist(err) {
if e {
t.Errorf("file \"%s\" does not exist.\n", name) 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)
}
}
} }
``` ```
# Available Backends # 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 calls. It also makes it trivial to have your code use the OS during
operation and a mock filesystem during testing or as needed. operation and a mock filesystem during testing or as needed.
```go
appfs := NewOsFs()
appfs.MkdirAll("src/a", 0755))
```
## Memory Backed Storage ## Memory Backed Storage
### MemMapFs ### MemMapFs
@ -242,6 +236,11 @@ mocking and to speed up unnecessary disk io when persistence isnt
necessary. It is fully concurrent and will work within go routines necessary. It is fully concurrent and will work within go routines
safely. safely.
```go
mm := NewMemMapFs()
mm.MkdirAll("src/a", 0755))
```
#### InMemoryFile #### InMemoryFile
As part of MemMapFs, Afero also provides an atomic, fully concurrent memory 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. the base path before calling the source Fs.
```go ```go
bp := &BasePathFs{source: &OsFs{}, path: "/base/path"} bp := NewBasePathFs(NewOsFs(), "/base/path")
``` ```
### ReadOnlyFs ### ReadOnlyFs
@ -273,7 +272,7 @@ bp := &BasePathFs{source: &OsFs{}, path: "/base/path"}
A thin wrapper around the source Fs providing a read only view. A thin wrapper around the source Fs providing a read only view.
```go ```go
fs := &ReadOnlyFs{source: &OsFs{}} fs := NewReadOnlyFs(NewOsFs())
_, err := fs.Create("/file.txt") _, err := fs.Create("/file.txt")
// err = syscall.EPERM // err = syscall.EPERM
``` ```
@ -286,7 +285,7 @@ Files not matching the regexp provided will not be created.
Directories are not filtered. Directories are not filtered.
```go ```go
fs := &RegexpFs{re: regexp.MustCompile(`\.txt$`), source: &MemMapFs{}} fs := NewRegexpFs(NewMemMapFs(), regexp.MustCompile(`\.txt$`))
_, err := fs.Create("/file.html") _, err := fs.Create("/file.html")
// err = syscall.ENOENT // 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. Any Afero FileSystem can be used as an httpFs.
```go ```go
httpFs := &afero.HttpFs{source: <ExistingFS>} httpFs := afero.NewHttpFs(<ExistingFS>)
fileserver := http.FileServer(httpFs.Dir(<PATH>))) fileserver := http.FileServer(httpFs.Dir(<PATH>)))
http.Handle("/", fileserver) http.Handle("/", fileserver)
``` ```
@ -335,9 +334,9 @@ from the base to the overlay when they're not present (or outdated) in the
caching layer. caching layer.
```go ```go
base := &OsFs{} base := NewOsFs()
layer := &MemMapFs{} layer := NewMemMapFs()
ufs := &CacheOnReadFs{base: base, layer: layer, cacheTime: 100 * time.Second} ufs := NewCacheOnReadFs(base, layer, 100 * time.Second)
``` ```
### CopyOnWriteFs() ### CopyOnWriteFs()
@ -361,17 +360,17 @@ overlay will be removed/renamed.
The writable overlay layer is currently limited to MemMapFs. The writable overlay layer is currently limited to MemMapFs.
```go ```go
base := &OsFs{} base := NewOsFs()
roBase := &ReadOnlyFs{source: base} roBase := NewReadOnlyFs(base)
ufs := &CopyOnWriteFs{base: roBase, layer: &MemMapFs{}} ufs := NewCopyOnWriteFs(roBase, NewMemMapFs())
fh, _ = ufs.Create("/home/test/file2.txt") fh, _ = ufs.Create("/home/test/file2.txt")
fh.WriteString("This is a test") fh.WriteString("This is a test")
fh.Close() fh.Close()
``` ```
In this example all write operations will only occur in memory (&MemMapFs{}) In this example all write operations will only occur in memory (MemMapFs)
leaving the base filesystem (&OsFs) untouched. leaving the base filesystem (OsFs) untouched.
## Desired/possible backends ## Desired/possible backends

View File

@ -20,6 +20,10 @@ type BasePathFs struct {
path string 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, // 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 // else the given file with the base path prepended
func (b *BasePathFs) RealPath(name string) (path string, err error) { func (b *BasePathFs) RealPath(name string) (path string, err error) {

View File

@ -7,7 +7,7 @@ import (
func TestBasePath(t *testing.T) { func TestBasePath(t *testing.T) {
baseFs := &MemMapFs{} baseFs := &MemMapFs{}
baseFs.MkdirAll("/base/path/tmp", 0777) 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 { if _, err := bp.Create("/tmp/foo"); err != nil {
t.Errorf("Failed to set real path") t.Errorf("Failed to set real path")

View File

@ -20,6 +20,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
) )
type httpDir struct { type httpDir struct {
@ -48,6 +49,10 @@ type HttpFs struct {
source Fs source Fs
} }
func NewHttpFs(source Fs) *HttpFs {
return &HttpFs{source: source}
}
func (h HttpFs) Dir(s string) *httpDir { func (h HttpFs) Dir(s string) *httpDir {
return &httpDir{basePath: s, fs: h} return &httpDir{basePath: s, fs: h}
} }
@ -58,6 +63,14 @@ func (h HttpFs) Create(name string) (File, error) {
return h.source.Create(name) 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 { func (h HttpFs) Mkdir(name string, perm os.FileMode) error {
return h.source.Mkdir(name, perm) return h.source.Mkdir(name, perm)
} }

View File

@ -31,6 +31,10 @@ type MemMapFs struct {
init sync.Once init sync.Once
} }
func NewMemMapFs() Fs {
return &MemMapFs{}
}
var memfsInit sync.Once var memfsInit sync.Once
func (m *MemMapFs) getData() map[string]*mem.FileData { func (m *MemMapFs) getData() map[string]*mem.FileData {

View File

@ -34,7 +34,7 @@ func TestNormalizePath(t *testing.T) {
func TestPathErrors(t *testing.T) { func TestPathErrors(t *testing.T) {
path := filepath.Join(".", "some", "path") path := filepath.Join(".", "some", "path")
path2 := filepath.Join(".", "different", "path") path2 := filepath.Join(".", "different", "path")
fs := &MemMapFs{} fs := NewMemMapFs()
perm := os.FileMode(0755) perm := os.FileMode(0755)
// relevant functions: // relevant functions:

4
os.go
View File

@ -25,6 +25,10 @@ import (
// (http://golang.org/pkg/os/). // (http://golang.org/pkg/os/).
type OsFs struct{} type OsFs struct{}
func NewOsFs() Fs {
return &OsFs{}
}
func (OsFs) Name() string { return "OsFs" } func (OsFs) Name() string { return "OsFs" }
func (OsFs) Create(name string) (File, error) { func (OsFs) Create(name string) (File, error) {

View File

@ -10,6 +10,10 @@ type ReadOnlyFs struct {
source Fs source Fs
} }
func NewReadOnlyFs(source Fs) Fs {
return &ReadOnlyFs{source: source}
}
func (r *ReadOnlyFs) ReadDir(name string) ([]os.FileInfo, error) { func (r *ReadOnlyFs) ReadDir(name string) ([]os.FileInfo, error) {
return ReadDir(r.source, name) return ReadDir(r.source, name)
} }

View File

@ -16,6 +16,10 @@ type RegexpFs struct {
source Fs source Fs
} }
func NewRegexpFs(source Fs, re *regexp.Regexp) Fs {
return &RegexpFs{source: source, re: re}
}
type RegexpFile struct { type RegexpFile struct {
f File f File
re *regexp.Regexp re *regexp.Regexp

View File

@ -20,7 +20,7 @@ func TestFilterReadonlyRemoveAndRead(t *testing.T) {
fh.Write([]byte("content here")) fh.Write([]byte("content here"))
fh.Close() fh.Close()
fs := &ReadOnlyFs{source: mfs} fs := NewReadOnlyFs(mfs)
err = fs.Remove("/file.txt") err = fs.Remove("/file.txt")
if err == nil { if err == nil {
t.Errorf("Did not fail to remove file") t.Errorf("Did not fail to remove file")
@ -51,7 +51,7 @@ func TestFilterReadonlyRemoveAndRead(t *testing.T) {
} }
func TestFilterRegexp(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") _, err := fs.Create("/file.html")
if err == nil { if err == nil {

View File

@ -21,8 +21,8 @@ import (
// successful read in the overlay will move the cursor position in the base layer // successful read in the overlay will move the cursor position in the base layer
// by the number of bytes read. // by the number of bytes read.
type UnionFile struct { type UnionFile struct {
layer File
base File base File
layer File
off int off int
files []os.FileInfo files []os.FileInfo
} }

View File

@ -25,6 +25,10 @@ type CacheOnReadFs struct {
cacheTime time.Duration cacheTime time.Duration
} }
func NewCacheOnReadFs(base Fs, layer Fs, cacheTime time.Duration) Fs {
return &CacheOnReadFs{base: base, layer: layer, cacheTime: cacheTime}
}
type cacheState int type cacheState int
const ( const (

View File

@ -22,6 +22,10 @@ type CopyOnWriteFs struct {
layer Fs layer Fs
} }
func NewCopyOnWriteFs(base Fs, layer Fs) Fs {
return &CopyOnWriteFs{base: base, layer: layer}
}
func (u *CopyOnWriteFs) isBaseFile(name string) (bool, error) { func (u *CopyOnWriteFs) isBaseFile(name string) (bool, error) {
if _, err := u.layer.Stat(name); err == nil { if _, err := u.layer.Stat(name); err == nil {
return false, nil return false, nil

View File

@ -11,7 +11,7 @@ func TestUnionCreateExisting(t *testing.T) {
base := &MemMapFs{} base := &MemMapFs{}
roBase := &ReadOnlyFs{source: base} roBase := &ReadOnlyFs{source: base}
ufs := &CopyOnWriteFs{base: roBase, layer: &MemMapFs{}} ufs := NewCopyOnWriteFs(roBase, &MemMapFs{})
base.MkdirAll("/home/test", 0777) base.MkdirAll("/home/test", 0777)
fh, _ := base.Create("/home/test/file.txt") fh, _ := base.Create("/home/test/file.txt")
@ -86,7 +86,7 @@ func TestUnionCacheWrite(t *testing.T) {
base := &MemMapFs{} base := &MemMapFs{}
layer := &MemMapFs{} layer := &MemMapFs{}
ufs := &CacheOnReadFs{base: base, layer: layer, cacheTime: 0} ufs := NewCacheOnReadFs(base, layer, 0)
base.Mkdir("/data", 0777) base.Mkdir("/data", 0777)