276 lines
5.5 KiB
Go
276 lines
5.5 KiB
Go
package fs
|
|
|
|
// This is a mock implementation of the "fs" module for use with tests. It does
|
|
// not actually read from the file system. Instead, it reads from a pre-specified
|
|
// map of file paths to files.
|
|
|
|
import (
|
|
"errors"
|
|
"path"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
type MockKind uint8
|
|
|
|
const (
|
|
MockUnix MockKind = iota
|
|
MockWindows
|
|
)
|
|
|
|
type mockFS struct {
|
|
dirs map[string]DirEntries
|
|
files map[string]string
|
|
Kind MockKind
|
|
}
|
|
|
|
func MockFS(input map[string]string, kind MockKind) FS {
|
|
dirs := make(map[string]DirEntries)
|
|
files := make(map[string]string)
|
|
|
|
for k, v := range input {
|
|
key := k
|
|
if kind == MockWindows {
|
|
key = "C:" + strings.ReplaceAll(key, "/", "\\")
|
|
}
|
|
files[key] = v
|
|
original := k
|
|
|
|
// Build the directory map
|
|
for {
|
|
kDir := path.Dir(k)
|
|
key := kDir
|
|
if kind == MockWindows {
|
|
key = "C:" + strings.ReplaceAll(key, "/", "\\")
|
|
}
|
|
dir, ok := dirs[key]
|
|
if !ok {
|
|
dir = DirEntries{dir: key, data: make(map[string]*Entry)}
|
|
dirs[key] = dir
|
|
}
|
|
if kDir == k {
|
|
break
|
|
}
|
|
base := path.Base(k)
|
|
if k == original {
|
|
dir.data[strings.ToLower(base)] = &Entry{kind: FileEntry, base: base}
|
|
} else {
|
|
dir.data[strings.ToLower(base)] = &Entry{kind: DirEntry, base: base}
|
|
}
|
|
k = kDir
|
|
}
|
|
}
|
|
|
|
return &mockFS{dirs, files, kind}
|
|
}
|
|
|
|
func (fs *mockFS) ReadDirectory(path string) (DirEntries, error, error) {
|
|
if fs.Kind == MockWindows {
|
|
path = strings.ReplaceAll(path, "/", "\\")
|
|
}
|
|
if dir, ok := fs.dirs[path]; ok {
|
|
return dir, nil, nil
|
|
}
|
|
return DirEntries{}, syscall.ENOENT, syscall.ENOENT
|
|
}
|
|
|
|
func (fs *mockFS) ReadFile(path string) (string, error, error) {
|
|
if fs.Kind == MockWindows {
|
|
path = strings.ReplaceAll(path, "/", "\\")
|
|
}
|
|
if contents, ok := fs.files[path]; ok {
|
|
return contents, nil, nil
|
|
}
|
|
return "", syscall.ENOENT, syscall.ENOENT
|
|
}
|
|
|
|
func (fs *mockFS) OpenFile(path string) (OpenedFile, error, error) {
|
|
if fs.Kind == MockWindows {
|
|
path = strings.ReplaceAll(path, "/", "\\")
|
|
}
|
|
if contents, ok := fs.files[path]; ok {
|
|
return &InMemoryOpenedFile{Contents: []byte(contents)}, nil, nil
|
|
}
|
|
return nil, syscall.ENOENT, syscall.ENOENT
|
|
}
|
|
|
|
func (fs *mockFS) ModKey(path string) (ModKey, error) {
|
|
return ModKey{}, errors.New("This is not available during tests")
|
|
}
|
|
|
|
func win2unix(p string) string {
|
|
if strings.HasPrefix(p, "C:\\") {
|
|
p = p[2:]
|
|
}
|
|
p = strings.ReplaceAll(p, "\\", "/")
|
|
return p
|
|
}
|
|
|
|
func unix2win(p string) string {
|
|
p = strings.ReplaceAll(p, "/", "\\")
|
|
if strings.HasPrefix(p, "\\") {
|
|
p = "C:" + p
|
|
}
|
|
return p
|
|
}
|
|
|
|
func (fs *mockFS) IsAbs(p string) bool {
|
|
if fs.Kind == MockWindows {
|
|
p = win2unix(p)
|
|
}
|
|
return path.IsAbs(p)
|
|
}
|
|
|
|
func (fs *mockFS) Abs(p string) (string, bool) {
|
|
if fs.Kind == MockWindows {
|
|
p = win2unix(p)
|
|
}
|
|
|
|
p = path.Clean(path.Join("/", p))
|
|
|
|
if fs.Kind == MockWindows {
|
|
p = unix2win(p)
|
|
}
|
|
|
|
return p, true
|
|
}
|
|
|
|
func (fs *mockFS) Dir(p string) string {
|
|
if fs.Kind == MockWindows {
|
|
p = win2unix(p)
|
|
}
|
|
|
|
p = path.Dir(p)
|
|
|
|
if fs.Kind == MockWindows {
|
|
p = unix2win(p)
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
func (fs *mockFS) Base(p string) string {
|
|
if fs.Kind == MockWindows {
|
|
p = win2unix(p)
|
|
}
|
|
|
|
p = path.Base(p)
|
|
|
|
if fs.Kind == MockWindows && p == "/" {
|
|
p = "\\"
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
func (fs *mockFS) Ext(p string) string {
|
|
if fs.Kind == MockWindows {
|
|
p = win2unix(p)
|
|
}
|
|
|
|
return path.Ext(p)
|
|
}
|
|
|
|
func (fs *mockFS) Join(parts ...string) string {
|
|
if fs.Kind == MockWindows {
|
|
converted := make([]string, len(parts))
|
|
for i, part := range parts {
|
|
converted[i] = win2unix(part)
|
|
}
|
|
parts = converted
|
|
}
|
|
|
|
p := path.Clean(path.Join(parts...))
|
|
|
|
if fs.Kind == MockWindows {
|
|
p = unix2win(p)
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
func (fs *mockFS) Cwd() string {
|
|
if fs.Kind == MockWindows {
|
|
return "C:\\"
|
|
}
|
|
return "/"
|
|
}
|
|
|
|
func splitOnSlash(path string) (string, string) {
|
|
if slash := strings.IndexByte(path, '/'); slash != -1 {
|
|
return path[:slash], path[slash+1:]
|
|
}
|
|
return path, ""
|
|
}
|
|
|
|
func (fs *mockFS) Rel(base string, target string) (string, bool) {
|
|
if fs.Kind == MockWindows {
|
|
base = win2unix(base)
|
|
target = win2unix(target)
|
|
}
|
|
|
|
base = path.Clean(base)
|
|
target = path.Clean(target)
|
|
|
|
// Go's implementation does these checks
|
|
if base == target {
|
|
return ".", true
|
|
}
|
|
if base == "." {
|
|
base = ""
|
|
}
|
|
|
|
// Go's implementation fails when this condition is false. I believe this is
|
|
// because of this part of the contract, from Go's documentation: "An error
|
|
// is returned if targpath can't be made relative to basepath or if knowing
|
|
// the current working directory would be necessary to compute it."
|
|
if (len(base) > 0 && base[0] == '/') != (len(target) > 0 && target[0] == '/') {
|
|
return "", false
|
|
}
|
|
|
|
// Find the common parent directory
|
|
for {
|
|
bHead, bTail := splitOnSlash(base)
|
|
tHead, tTail := splitOnSlash(target)
|
|
if bHead != tHead {
|
|
break
|
|
}
|
|
base = bTail
|
|
target = tTail
|
|
}
|
|
|
|
// Stop now if base is a subpath of target
|
|
if base == "" {
|
|
if fs.Kind == MockWindows {
|
|
target = unix2win(target)
|
|
}
|
|
return target, true
|
|
}
|
|
|
|
// Traverse up to the common parent
|
|
commonParent := strings.Repeat("../", strings.Count(base, "/")+1)
|
|
|
|
// Stop now if target is a subpath of base
|
|
if target == "" {
|
|
target = commonParent[:len(commonParent)-1]
|
|
if fs.Kind == MockWindows {
|
|
target = unix2win(target)
|
|
}
|
|
return target, true
|
|
}
|
|
|
|
// Otherwise, down to the parent
|
|
target = commonParent + target
|
|
if fs.Kind == MockWindows {
|
|
target = unix2win(target)
|
|
}
|
|
return target, true
|
|
}
|
|
|
|
func (fs *mockFS) kind(dir string, base string) (symlink string, kind EntryKind) {
|
|
panic("This should never be called")
|
|
}
|
|
|
|
func (fs *mockFS) WatchData() WatchData {
|
|
panic("This should never be called")
|
|
}
|