func (r *RecordingFs) Mkdir(name string, perm os.FileMode) error {
	r.actionsMutex.Lock()
	defer r.actionsMutex.Unlock()
	r.actions = append(r.actions, fmt.Sprintf("mkdir %s with mode %#o", name, perm))
	r.filesMutex.Lock()
	defer r.filesMutex.Unlock()
	if r.files == nil {
		r.files = make(map[string]*FakeFile)
	}
	r.files[name] = &FakeFile{name: name, dir: true}
	return nil
}
Mkdir records the action "mkdir <name> with mode <perm>" and returns nil.
package fstest
import (
	"fmt"
	"os"
	"path"
	"strings"
	"sync"
	"sync/atomic"
	"syscall"
	"github.com/tsuru/tsuru/fs"
	"github.com/tsuru/tsuru/safe"
)
type FakeFile struct {
	content string
	current int64
	name    string
	r       *safe.BytesReader
	f       *os.File
	dir     bool
}
func (f *FakeFile) reader() *safe.BytesReader {
	if f.r == nil {
		f.r = safe.NewBytesReader([]byte(f.content))
	}
	return f.r
}
func (f *FakeFile) Close() error {
	atomic.StoreInt64(&f.current, 0)
	if f.f != nil {
		f.f.Close()
		f.f = nil
	}
	return nil
}
func (f *FakeFile) Read(p []byte) (n int, err error) {
	n, err = f.reader().Read(p)
	atomic.AddInt64(&f.current, int64(n))
	return
}
func (f *FakeFile) ReadAt(p []byte, off int64) (n int, err error) {
	n, err = f.reader().ReadAt(p, off)
	atomic.AddInt64(&f.current, off+int64(n))
	return
}
func (f *FakeFile) Seek(offset int64, whence int) (int64, error) {
	ncurrent, err := f.reader().Seek(offset, whence)
	old := atomic.LoadInt64(&f.current)
	for !atomic.CompareAndSwapInt64(&f.current, old, ncurrent) {
		old = atomic.LoadInt64(&f.current)
	}
	return ncurrent, err
}
func (f *FakeFile) Name() string {
	return f.name
}
func (f *FakeFile) Fd() uintptr {
	if f.f == nil {
		var err error
		p := path.Join(os.TempDir(), "testing-fs-file.txt")
		f.f, err = os.Create(p)
		if err != nil {
			panic(err)
		}
	}
	return f.f.Fd()
}
func (f *FakeFile) Stat() (fi os.FileInfo, err error) {
	return &fileInfo{name: f.Name(), size: int64(len(f.content))}, nil
}
func (f *FakeFile) Write(p []byte) (n int, err error) {
	n = len(p)
	cur := atomic.LoadInt64(&f.current)
	currentSize := len(f.content)
	end := int(cur) + n
	if end > currentSize {
		end = currentSize
	}
	diff := cur - int64(currentSize)
	if diff > 0 {
		f.content += strings.Repeat("\x00", int(diff)) + string(p)
	} else {
		f.content = f.content[:cur] + string(p) + f.content[end:]
	}
	return
}
func (f *FakeFile) WriteString(s string) (ret int, err error) {
	return f.Write([]byte(s))
}
func (f *FakeFile) Truncate(size int64) error {
	f.content = f.content[:size]
	return nil
}
type RecordingFs struct {
	actions      []string
	actionsMutex sync.Mutex
	files      map[string]*FakeFile
	filesMutex sync.Mutex
	FileContent string
}
func (r *RecordingFs) HasAction(action string) bool {
	r.actionsMutex.Lock()
	defer r.actionsMutex.Unlock()
	for _, a := range r.actions {
		if action == a {
			return true
		}
	}
	return false
}
func (r *RecordingFs) open(name string, read bool) (fs.File, error) {
	r.filesMutex.Lock()
	defer r.filesMutex.Unlock()
	if r.files == nil {
		r.files = make(map[string]*FakeFile)
		if r.FileContent == "" && read {
			return nil, syscall.ENOENT
		}
	} else if f, ok := r.files[name]; ok {
		f.r = nil
		return f, nil
	} else if r.FileContent == "" && read {
		return nil, syscall.ENOENT
	}
	fil := &FakeFile{content: r.FileContent, name: name}
	r.files[name] = fil
	return fil, nil
}
func (r *RecordingFs) Create(name string) (fs.File, error) {
	r.actionsMutex.Lock()
	r.actions = append(r.actions, "create "+name)
	r.actionsMutex.Unlock()
	return r.open(name, false)
}