| // Copyright 2022 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package testfs |
| |
| import ( |
| "fmt" |
| "io" |
| "io/fs" |
| "strings" |
| "time" |
| ) |
| |
| // TestFS implements a test file system (fs.FS) simulated by a map from filename to []byte content. |
| type TestFS map[string][]byte |
| |
| var _ fs.FS = (*TestFS)(nil) |
| var _ fs.StatFS = (*TestFS)(nil) |
| |
| // Open implements fs.FS.Open() to open a file based on the filename. |
| func (tfs *TestFS) Open(name string) (fs.File, error) { |
| if _, ok := (*tfs)[name]; !ok { |
| return nil, fmt.Errorf("unknown file %q", name) |
| } |
| return &TestFile{tfs, name, 0}, nil |
| } |
| |
| // Stat implements fs.StatFS.Stat() to examine a file based on the filename. |
| func (tfs *TestFS) Stat(name string) (fs.FileInfo, error) { |
| if content, ok := (*tfs)[name]; ok { |
| return &TestFileInfo{name, len(content), 0666}, nil |
| } |
| dirname := name |
| if !strings.HasSuffix(dirname, "/") { |
| dirname = dirname + "/" |
| } |
| for name := range (*tfs) { |
| if strings.HasPrefix(name, dirname) { |
| return &TestFileInfo{name, 8, fs.ModeDir | fs.ModePerm}, nil |
| } |
| } |
| return nil, fmt.Errorf("file not found: %q", name) |
| } |
| |
| // TestFileInfo implements a file info (fs.FileInfo) based on TestFS above. |
| type TestFileInfo struct { |
| name string |
| size int |
| mode fs.FileMode |
| } |
| |
| var _ fs.FileInfo = (*TestFileInfo)(nil) |
| |
| // Name returns the name of the file |
| func (fi *TestFileInfo) Name() string { |
| return fi.name |
| } |
| |
| // Size returns the size of the file in bytes. |
| func (fi *TestFileInfo) Size() int64 { |
| return int64(fi.size) |
| } |
| |
| // Mode returns the fs.FileMode bits. |
| func (fi *TestFileInfo) Mode() fs.FileMode { |
| return fi.mode |
| } |
| |
| // ModTime fakes a modification time. |
| func (fi *TestFileInfo) ModTime() time.Time { |
| return time.UnixMicro(0xb0bb) |
| } |
| |
| // IsDir is a synonym for Mode().IsDir() |
| func (fi *TestFileInfo) IsDir() bool { |
| return fi.mode.IsDir() |
| } |
| |
| // Sys is unused and returns nil. |
| func (fi *TestFileInfo) Sys() any { |
| return nil |
| } |
| |
| // TestFile implements a test file (fs.File) based on TestFS above. |
| type TestFile struct { |
| fs *TestFS |
| name string |
| posn int |
| } |
| |
| var _ fs.File = (*TestFile)(nil) |
| |
| // Stat not implemented to obviate implementing fs.FileInfo. |
| func (f *TestFile) Stat() (fs.FileInfo, error) { |
| return f.fs.Stat(f.name) |
| } |
| |
| // Read copies bytes from the TestFS map. |
| func (f *TestFile) Read(b []byte) (int, error) { |
| if f.posn < 0 { |
| return 0, fmt.Errorf("file not open: %q", f.name) |
| } |
| if f.posn >= len((*f.fs)[f.name]) { |
| return 0, io.EOF |
| } |
| n := copy(b, (*f.fs)[f.name][f.posn:]) |
| f.posn += n |
| return n, nil |
| } |
| |
| // Close marks the TestFile as no longer in use. |
| func (f *TestFile) Close() error { |
| if f.posn < 0 { |
| return fmt.Errorf("file already closed: %q", f.name) |
| } |
| f.posn = -1 |
| return nil |
| } |