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.
```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 isnt
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: <ExistingFS>}
httpFs := afero.NewHttpFs(<ExistingFS>)
fileserver := http.FileServer(httpFs.Dir(<PATH>)))
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

View File

@ -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) {

View File

@ -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")

View File

@ -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)
}

View File

@ -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 {

View File

@ -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:

4
os.go
View File

@ -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) {

View File

@ -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)
}

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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 (

View File

@ -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

View File

@ -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)