github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/testing/fstest/mapfs.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package fstest 6 7 import ( 8 "io" 9 "io/fs" 10 "path" 11 "sort" 12 "strings" 13 "time" 14 ) 15 16 // A MapFS is a simple in-memory file system for use in tests, 17 // represented as a map from path names (arguments to Open) 18 // to information about the files or directories they represent. 19 // 20 // The map need not include parent directories for files contained 21 // in the map; those will be synthesized if needed. 22 // But a directory can still be included by setting the MapFile.Mode's ModeDir bit; 23 // this may be necessary for detailed control over the directory's FileInfo 24 // or to create an empty directory. 25 // 26 // File system operations read directly from the map, 27 // so that the file system can be changed by editing the map as needed. 28 // An implication is that file system operations must not run concurrently 29 // with changes to the map, which would be a race. 30 // Another implication is that opening or reading a directory requires 31 // iterating over the entire map, so a MapFS should typically be used with not more 32 // than a few hundred entries or directory reads. 33 type MapFS map[string]*MapFile 34 35 // A MapFile describes a single file in a MapFS. 36 type MapFile struct { 37 Data []byte // file content 38 Mode fs.FileMode // FileInfo.Mode 39 ModTime time.Time // FileInfo.ModTime 40 Sys interface{} // FileInfo.Sys 41 } 42 43 var _ fs.FS = MapFS(nil) 44 var _ fs.File = (*openMapFile)(nil) 45 46 // Open opens the named file. 47 func (fsys MapFS) Open(name string) (fs.File, error) { 48 if !fs.ValidPath(name) { 49 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} 50 } 51 file := fsys[name] 52 if file != nil && file.Mode&fs.ModeDir == 0 { 53 // Ordinary file 54 return &openMapFile{name, mapFileInfo{path.Base(name), file}, 0}, nil 55 } 56 57 // Directory, possibly synthesized. 58 // Note that file can be nil here: the map need not contain explicit parent directories for all its files. 59 // But file can also be non-nil, in case the user wants to set metadata for the directory explicitly. 60 // Either way, we need to construct the list of children of this directory. 61 var list []mapFileInfo 62 var elem string 63 var need = make(map[string]bool) 64 if name == "." { 65 elem = "." 66 for fname, f := range fsys { 67 i := strings.Index(fname, "/") 68 if i < 0 { 69 list = append(list, mapFileInfo{fname, f}) 70 } else { 71 need[fname[:i]] = true 72 } 73 } 74 } else { 75 elem = name[strings.LastIndex(name, "/")+1:] 76 prefix := name + "/" 77 for fname, f := range fsys { 78 if strings.HasPrefix(fname, prefix) { 79 felem := fname[len(prefix):] 80 i := strings.Index(felem, "/") 81 if i < 0 { 82 list = append(list, mapFileInfo{felem, f}) 83 } else { 84 need[fname[len(prefix):len(prefix)+i]] = true 85 } 86 } 87 } 88 // If the directory name is not in the map, 89 // and there are no children of the name in the map, 90 // then the directory is treated as not existing. 91 if file == nil && list == nil && len(need) == 0 { 92 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} 93 } 94 } 95 for _, fi := range list { 96 delete(need, fi.name) 97 } 98 for name := range need { 99 list = append(list, mapFileInfo{name, &MapFile{Mode: fs.ModeDir}}) 100 } 101 sort.Slice(list, func(i, j int) bool { 102 return list[i].name < list[j].name 103 }) 104 105 if file == nil { 106 file = &MapFile{Mode: fs.ModeDir} 107 } 108 return &mapDir{name, mapFileInfo{elem, file}, list, 0}, nil 109 } 110 111 // fsOnly is a wrapper that hides all but the fs.FS methods, 112 // to avoid an infinite recursion when implementing special 113 // methods in terms of helpers that would use them. 114 // (In general, implementing these methods using the package fs helpers 115 // is redundant and unnecessary, but having the methods may make 116 // MapFS exercise more code paths when used in tests.) 117 type fsOnly struct{ fs.FS } 118 119 func (fsys MapFS) ReadFile(name string) ([]byte, error) { 120 return fs.ReadFile(fsOnly{fsys}, name) 121 } 122 123 func (fsys MapFS) Stat(name string) (fs.FileInfo, error) { 124 return fs.Stat(fsOnly{fsys}, name) 125 } 126 127 func (fsys MapFS) ReadDir(name string) ([]fs.DirEntry, error) { 128 return fs.ReadDir(fsOnly{fsys}, name) 129 } 130 131 func (fsys MapFS) Glob(pattern string) ([]string, error) { 132 return fs.Glob(fsOnly{fsys}, pattern) 133 } 134 135 type noSub struct { 136 MapFS 137 } 138 139 func (noSub) Sub() {} // not the fs.SubFS signature 140 141 func (fsys MapFS) Sub(dir string) (fs.FS, error) { 142 return fs.Sub(noSub{fsys}, dir) 143 } 144 145 // A mapFileInfo implements fs.FileInfo and fs.DirEntry for a given map file. 146 type mapFileInfo struct { 147 name string 148 f *MapFile 149 } 150 151 func (i *mapFileInfo) Name() string { return i.name } 152 func (i *mapFileInfo) Size() int64 { return int64(len(i.f.Data)) } 153 func (i *mapFileInfo) Mode() fs.FileMode { return i.f.Mode } 154 func (i *mapFileInfo) Type() fs.FileMode { return i.f.Mode.Type() } 155 func (i *mapFileInfo) ModTime() time.Time { return i.f.ModTime } 156 func (i *mapFileInfo) IsDir() bool { return i.f.Mode&fs.ModeDir != 0 } 157 func (i *mapFileInfo) Sys() interface{} { return i.f.Sys } 158 func (i *mapFileInfo) Info() (fs.FileInfo, error) { return i, nil } 159 160 // An openMapFile is a regular (non-directory) fs.File open for reading. 161 type openMapFile struct { 162 path string 163 mapFileInfo 164 offset int64 165 } 166 167 func (f *openMapFile) Stat() (fs.FileInfo, error) { return &f.mapFileInfo, nil } 168 169 func (f *openMapFile) Close() error { return nil } 170 171 func (f *openMapFile) Read(b []byte) (int, error) { 172 if f.offset >= int64(len(f.f.Data)) { 173 return 0, io.EOF 174 } 175 if f.offset < 0 { 176 return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid} 177 } 178 n := copy(b, f.f.Data[f.offset:]) 179 f.offset += int64(n) 180 return n, nil 181 } 182 183 func (f *openMapFile) Seek(offset int64, whence int) (int64, error) { 184 switch whence { 185 case 0: 186 // offset += 0 187 case 1: 188 offset += f.offset 189 case 2: 190 offset += int64(len(f.f.Data)) 191 } 192 if offset < 0 || offset > int64(len(f.f.Data)) { 193 return 0, &fs.PathError{Op: "seek", Path: f.path, Err: fs.ErrInvalid} 194 } 195 f.offset = offset 196 return offset, nil 197 } 198 199 func (f *openMapFile) ReadAt(b []byte, offset int64) (int, error) { 200 if offset < 0 || offset > int64(len(f.f.Data)) { 201 return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid} 202 } 203 n := copy(b, f.f.Data[offset:]) 204 if n < len(b) { 205 return n, io.EOF 206 } 207 return n, nil 208 } 209 210 // A mapDir is a directory fs.File (so also an fs.ReadDirFile) open for reading. 211 type mapDir struct { 212 path string 213 mapFileInfo 214 entry []mapFileInfo 215 offset int 216 } 217 218 func (d *mapDir) Stat() (fs.FileInfo, error) { return &d.mapFileInfo, nil } 219 func (d *mapDir) Close() error { return nil } 220 func (d *mapDir) Read(b []byte) (int, error) { 221 return 0, &fs.PathError{Op: "read", Path: d.path, Err: fs.ErrInvalid} 222 } 223 224 func (d *mapDir) ReadDir(count int) ([]fs.DirEntry, error) { 225 n := len(d.entry) - d.offset 226 if count > 0 && n > count { 227 n = count 228 } 229 if n == 0 && count > 0 { 230 return nil, io.EOF 231 } 232 list := make([]fs.DirEntry, n) 233 for i := range list { 234 list[i] = &d.entry[d.offset+i] 235 } 236 d.offset += n 237 return list, nil 238 }