...

Source file src/testing/fstest/mapfs.go

Documentation: testing/fstest

		 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 n == 0 && count > 0 {
	 227  		return nil, io.EOF
	 228  	}
	 229  	if count > 0 && n > count {
	 230  		n = count
	 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  }
	 239  

View as plain text