]> Cypherpunks.ru repositories - gostls13.git/blob - src/testing/fstest/mapfs.go
8912e98930d328ea6697da6dbbf6156779f10877
[gostls13.git] / src / testing / fstest / mapfs.go
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 [fs.ModeDir] bit;
23 // this may be necessary for detailed control over the directory's [fs.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 // fs.FileInfo.Mode
39         ModTime time.Time   // fs.FileInfo.ModTime
40         Sys     any         // fs.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 | 0555}})
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 | 0555}
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 }