github.com/AndrienkoAleksandr/go@v0.0.19/src/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 any // 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 if fname != "." { 70 list = append(list, mapFileInfo{fname, f}) 71 } 72 } else { 73 need[fname[:i]] = true 74 } 75 } 76 } else { 77 elem = name[strings.LastIndex(name, "/")+1:] 78 prefix := name + "/" 79 for fname, f := range fsys { 80 if strings.HasPrefix(fname, prefix) { 81 felem := fname[len(prefix):] 82 i := strings.Index(felem, "/") 83 if i < 0 { 84 list = append(list, mapFileInfo{felem, f}) 85 } else { 86 need[fname[len(prefix):len(prefix)+i]] = true 87 } 88 } 89 } 90 // If the directory name is not in the map, 91 // and there are no children of the name in the map, 92 // then the directory is treated as not existing. 93 if file == nil && list == nil && len(need) == 0 { 94 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} 95 } 96 } 97 for _, fi := range list { 98 delete(need, fi.name) 99 } 100 for name := range need { 101 list = append(list, mapFileInfo{name, &MapFile{Mode: fs.ModeDir}}) 102 } 103 sort.Slice(list, func(i, j int) bool { 104 return list[i].name < list[j].name 105 }) 106 107 if file == nil { 108 file = &MapFile{Mode: fs.ModeDir} 109 } 110 return &mapDir{name, mapFileInfo{elem, file}, list, 0}, nil 111 } 112 113 // fsOnly is a wrapper that hides all but the fs.FS methods, 114 // to avoid an infinite recursion when implementing special 115 // methods in terms of helpers that would use them. 116 // (In general, implementing these methods using the package fs helpers 117 // is redundant and unnecessary, but having the methods may make 118 // MapFS exercise more code paths when used in tests.) 119 type fsOnly struct{ fs.FS } 120 121 func (fsys MapFS) ReadFile(name string) ([]byte, error) { 122 return fs.ReadFile(fsOnly{fsys}, name) 123 } 124 125 func (fsys MapFS) Stat(name string) (fs.FileInfo, error) { 126 return fs.Stat(fsOnly{fsys}, name) 127 } 128 129 func (fsys MapFS) ReadDir(name string) ([]fs.DirEntry, error) { 130 return fs.ReadDir(fsOnly{fsys}, name) 131 } 132 133 func (fsys MapFS) Glob(pattern string) ([]string, error) { 134 return fs.Glob(fsOnly{fsys}, pattern) 135 } 136 137 type noSub struct { 138 MapFS 139 } 140 141 func (noSub) Sub() {} // not the fs.SubFS signature 142 143 func (fsys MapFS) Sub(dir string) (fs.FS, error) { 144 return fs.Sub(noSub{fsys}, dir) 145 } 146 147 // A mapFileInfo implements fs.FileInfo and fs.DirEntry for a given map file. 148 type mapFileInfo struct { 149 name string 150 f *MapFile 151 } 152 153 func (i *mapFileInfo) Name() string { return i.name } 154 func (i *mapFileInfo) Size() int64 { return int64(len(i.f.Data)) } 155 func (i *mapFileInfo) Mode() fs.FileMode { return i.f.Mode } 156 func (i *mapFileInfo) Type() fs.FileMode { return i.f.Mode.Type() } 157 func (i *mapFileInfo) ModTime() time.Time { return i.f.ModTime } 158 func (i *mapFileInfo) IsDir() bool { return i.f.Mode&fs.ModeDir != 0 } 159 func (i *mapFileInfo) Sys() any { return i.f.Sys } 160 func (i *mapFileInfo) Info() (fs.FileInfo, error) { return i, nil } 161 162 func (i *mapFileInfo) String() string { 163 return fs.FormatFileInfo(i) 164 } 165 166 // An openMapFile is a regular (non-directory) fs.File open for reading. 167 type openMapFile struct { 168 path string 169 mapFileInfo 170 offset int64 171 } 172 173 func (f *openMapFile) Stat() (fs.FileInfo, error) { return &f.mapFileInfo, nil } 174 175 func (f *openMapFile) Close() error { return nil } 176 177 func (f *openMapFile) Read(b []byte) (int, error) { 178 if f.offset >= int64(len(f.f.Data)) { 179 return 0, io.EOF 180 } 181 if f.offset < 0 { 182 return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid} 183 } 184 n := copy(b, f.f.Data[f.offset:]) 185 f.offset += int64(n) 186 return n, nil 187 } 188 189 func (f *openMapFile) Seek(offset int64, whence int) (int64, error) { 190 switch whence { 191 case 0: 192 // offset += 0 193 case 1: 194 offset += f.offset 195 case 2: 196 offset += int64(len(f.f.Data)) 197 } 198 if offset < 0 || offset > int64(len(f.f.Data)) { 199 return 0, &fs.PathError{Op: "seek", Path: f.path, Err: fs.ErrInvalid} 200 } 201 f.offset = offset 202 return offset, nil 203 } 204 205 func (f *openMapFile) ReadAt(b []byte, offset int64) (int, error) { 206 if offset < 0 || offset > int64(len(f.f.Data)) { 207 return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid} 208 } 209 n := copy(b, f.f.Data[offset:]) 210 if n < len(b) { 211 return n, io.EOF 212 } 213 return n, nil 214 } 215 216 // A mapDir is a directory fs.File (so also an fs.ReadDirFile) open for reading. 217 type mapDir struct { 218 path string 219 mapFileInfo 220 entry []mapFileInfo 221 offset int 222 } 223 224 func (d *mapDir) Stat() (fs.FileInfo, error) { return &d.mapFileInfo, nil } 225 func (d *mapDir) Close() error { return nil } 226 func (d *mapDir) Read(b []byte) (int, error) { 227 return 0, &fs.PathError{Op: "read", Path: d.path, Err: fs.ErrInvalid} 228 } 229 230 func (d *mapDir) ReadDir(count int) ([]fs.DirEntry, error) { 231 n := len(d.entry) - d.offset 232 if n == 0 && count > 0 { 233 return nil, io.EOF 234 } 235 if count > 0 && n > count { 236 n = count 237 } 238 list := make([]fs.DirEntry, n) 239 for i := range list { 240 list[i] = &d.entry[d.offset+i] 241 } 242 d.offset += n 243 return list, nil 244 }