gobot.io/x/gobot/v2@v2.1.0/system/fs_mock.go (about) 1 package system 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path" 8 "regexp" 9 "strings" 10 "time" 11 ) 12 13 var _ File = (*MockFile)(nil) 14 var _ filesystem = (*MockFilesystem)(nil) 15 16 // MockFilesystem represents a filesystem of mock files. 17 type MockFilesystem struct { 18 Seq int // Increases with each write or read. 19 Files map[string]*MockFile 20 WithReadError bool 21 WithWriteError bool 22 WithCloseError bool 23 } 24 25 // A MockFile represents a mock file that contains a single string. Any write 26 // overwrites, and any read returns from the start. 27 type MockFile struct { 28 Contents string 29 Seq int // When this file was last written or read. 30 Opened bool 31 Closed bool 32 fd uintptr 33 34 fs *MockFilesystem 35 } 36 37 var ( 38 errRead = fmt.Errorf("read error") 39 errWrite = fmt.Errorf("write error") 40 errClose = fmt.Errorf("close error") 41 ) 42 43 // Write writes string(b) to f.Contents 44 func (f *MockFile) Write(b []byte) (n int, err error) { 45 if f.fs.WithWriteError { 46 return 0, errWrite 47 } 48 return f.WriteString(string(b)) 49 } 50 51 // Seek seeks to a specific offset in a file 52 func (f *MockFile) Seek(offset int64, whence int) (ret int64, err error) { 53 return offset, nil 54 } 55 56 // WriteString writes s to f.Contents 57 func (f *MockFile) WriteString(s string) (ret int, err error) { 58 f.Contents = s 59 f.Seq = f.fs.next() 60 return len(s), nil 61 } 62 63 // Sync implements the File interface Sync function 64 func (f *MockFile) Sync() (err error) { 65 return nil 66 } 67 68 // Read copies b bytes from f.Contents 69 func (f *MockFile) Read(b []byte) (n int, err error) { 70 if f.fs.WithReadError { 71 return 0, errRead 72 } 73 74 count := len(b) 75 if len(f.Contents) < count { 76 count = len(f.Contents) 77 } 78 copy(b, []byte(f.Contents)[:count]) 79 f.Seq = f.fs.next() 80 81 return count, nil 82 } 83 84 // ReadAt calls MockFile.Read 85 func (f *MockFile) ReadAt(b []byte, off int64) (n int, err error) { 86 return f.Read(b) 87 } 88 89 // Fd returns a random uintprt based on the time of the MockFile creation 90 func (f *MockFile) Fd() uintptr { 91 return f.fd 92 } 93 94 // Close implements the File interface Close function 95 func (f *MockFile) Close() error { 96 if f != nil { 97 f.Opened = false 98 f.Closed = true 99 if f.fs != nil && f.fs.WithCloseError { 100 f.Closed = false 101 return errClose 102 } 103 } 104 return nil 105 } 106 107 // newMockFilesystem returns a new MockFilesystem given a list of file and folder paths 108 func newMockFilesystem(items []string) *MockFilesystem { 109 m := &MockFilesystem{ 110 Files: make(map[string]*MockFile), 111 } 112 113 for _, item := range items { 114 m.Add(item) 115 } 116 117 return m 118 } 119 120 // OpenFile opens file name from fs.Files, if the file does not exist it returns an os.PathError 121 func (fs *MockFilesystem) openFile(name string, _ int, _ os.FileMode) (file File, err error) { 122 f, ok := fs.Files[name] 123 if ok { 124 f.Opened = true 125 f.Closed = false 126 return f, nil 127 } 128 129 return (*MockFile)(nil), &os.PathError{Err: fmt.Errorf("%s: no such file", name)} 130 } 131 132 // Stat returns a generic FileInfo for all files in fs.Files. 133 // If the file does not exist it returns an os.PathError 134 func (fs *MockFilesystem) stat(name string) (os.FileInfo, error) { 135 _, ok := fs.Files[name] 136 if ok { 137 // return file based mock FileInfo 138 tmpFile, err := ioutil.TempFile("", name) 139 if err != nil { 140 return nil, err 141 } 142 defer os.Remove(tmpFile.Name()) 143 144 return os.Stat(tmpFile.Name()) 145 } 146 147 dirName := name + "/" 148 for path := range fs.Files { 149 if strings.HasPrefix(path, dirName) { 150 // return dir based mock FileInfo, TempDir don't like "/" in between 151 tmpDir, err := ioutil.TempDir("", strings.ReplaceAll(name, "/", "_")) 152 if err != nil { 153 return nil, err 154 } 155 defer os.RemoveAll(tmpDir) 156 157 return os.Stat(tmpDir) 158 } 159 } 160 161 return nil, &os.PathError{Err: fmt.Errorf("%s: no such file", name)} 162 } 163 164 // Find returns all items (files or folders) below the given directory matching the given pattern. 165 func (fs *MockFilesystem) find(baseDir string, pattern string) ([]string, error) { 166 reg, err := regexp.Compile(pattern) 167 if err != nil { 168 return nil, err 169 } 170 171 var found []string 172 for name := range fs.Files { 173 if !strings.HasPrefix(name, baseDir) { 174 continue 175 } 176 item := strings.TrimPrefix(name[len(baseDir):], "/") 177 178 firstItem := strings.Split(item, "/")[0] 179 if reg.MatchString(firstItem) { 180 found = append(found, path.Join(baseDir, firstItem)) 181 } 182 } 183 return found, nil 184 } 185 186 // readFile returns the contents of the given file. If the file does not exist it returns an os.PathError 187 func (fs *MockFilesystem) readFile(name string) ([]byte, error) { 188 if fs.WithReadError { 189 return nil, errRead 190 } 191 192 f, ok := fs.Files[name] 193 if !ok { 194 return nil, &os.PathError{Err: fmt.Errorf("%s: no such file", name)} 195 } 196 return []byte(f.Contents), nil 197 } 198 199 func (fs *MockFilesystem) next() int { 200 fs.Seq++ 201 return fs.Seq 202 } 203 204 // Add adds a new file to fs.Files given a name, and returns the newly created file 205 func (fs *MockFilesystem) Add(name string) *MockFile { 206 f := &MockFile{ 207 Seq: -1, 208 fd: uintptr(time.Now().UnixNano() & 0xffff), 209 fs: fs, 210 } 211 fs.Files[name] = f 212 return f 213 }