...

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  	"slices"
    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  	slices.SortFunc(list, func(a, b mapFileInfo) int {
   104  		return strings.Compare(a.name, b.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 path.Base(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  }
   245  

View as plain text