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