1// Copyright 2022 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package testfs 16 17import ( 18 "fmt" 19 "io" 20 "io/fs" 21 "strings" 22 "time" 23) 24 25// TestFS implements a test file system (fs.FS) simulated by a map from filename to []byte content. 26type TestFS map[string][]byte 27 28var _ fs.FS = (*TestFS)(nil) 29var _ fs.StatFS = (*TestFS)(nil) 30 31// Open implements fs.FS.Open() to open a file based on the filename. 32func (tfs *TestFS) Open(name string) (fs.File, error) { 33 if _, ok := (*tfs)[name]; !ok { 34 return nil, fmt.Errorf("unknown file %q", name) 35 } 36 return &TestFile{tfs, name, 0}, nil 37} 38 39// Stat implements fs.StatFS.Stat() to examine a file based on the filename. 40func (tfs *TestFS) Stat(name string) (fs.FileInfo, error) { 41 if content, ok := (*tfs)[name]; ok { 42 return &TestFileInfo{name, len(content), 0666}, nil 43 } 44 dirname := name 45 if !strings.HasSuffix(dirname, "/") { 46 dirname = dirname + "/" 47 } 48 for name := range (*tfs) { 49 if strings.HasPrefix(name, dirname) { 50 return &TestFileInfo{name, 8, fs.ModeDir | fs.ModePerm}, nil 51 } 52 } 53 return nil, fmt.Errorf("file not found: %q", name) 54} 55 56// TestFileInfo implements a file info (fs.FileInfo) based on TestFS above. 57type TestFileInfo struct { 58 name string 59 size int 60 mode fs.FileMode 61} 62 63var _ fs.FileInfo = (*TestFileInfo)(nil) 64 65// Name returns the name of the file 66func (fi *TestFileInfo) Name() string { 67 return fi.name 68} 69 70// Size returns the size of the file in bytes. 71func (fi *TestFileInfo) Size() int64 { 72 return int64(fi.size) 73} 74 75// Mode returns the fs.FileMode bits. 76func (fi *TestFileInfo) Mode() fs.FileMode { 77 return fi.mode 78} 79 80// ModTime fakes a modification time. 81func (fi *TestFileInfo) ModTime() time.Time { 82 return time.UnixMicro(0xb0bb) 83} 84 85// IsDir is a synonym for Mode().IsDir() 86func (fi *TestFileInfo) IsDir() bool { 87 return fi.mode.IsDir() 88} 89 90// Sys is unused and returns nil. 91func (fi *TestFileInfo) Sys() any { 92 return nil 93} 94 95// TestFile implements a test file (fs.File) based on TestFS above. 96type TestFile struct { 97 fs *TestFS 98 name string 99 posn int 100} 101 102var _ fs.File = (*TestFile)(nil) 103 104// Stat not implemented to obviate implementing fs.FileInfo. 105func (f *TestFile) Stat() (fs.FileInfo, error) { 106 return f.fs.Stat(f.name) 107} 108 109// Read copies bytes from the TestFS map. 110func (f *TestFile) Read(b []byte) (int, error) { 111 if f.posn < 0 { 112 return 0, fmt.Errorf("file not open: %q", f.name) 113 } 114 if f.posn >= len((*f.fs)[f.name]) { 115 return 0, io.EOF 116 } 117 n := copy(b, (*f.fs)[f.name][f.posn:]) 118 f.posn += n 119 return n, nil 120} 121 122// Close marks the TestFile as no longer in use. 123func (f *TestFile) Close() error { 124 if f.posn < 0 { 125 return fmt.Errorf("file already closed: %q", f.name) 126 } 127 f.posn = -1 128 return nil 129} 130