github.com/bpfs/defs@v0.0.15/afero/gcsfs/gcs_mocks.go (about) 1 // Copyright © 2021 Vasily Ovchinnikov <vasily@remerge.io>. 2 // 3 // A set of stiface-based mocks, replicating the GCS behavior, to make the tests not require any 4 // internet connection or real buckets. 5 // It is **not** a comprehensive set of mocks to test anything and everything GCS-related, rather 6 // a very tailored one for the current implementation - thus the tests, written with the use of 7 // these mocks are more of regression ones. 8 // If any GCS behavior changes and breaks the implementation, then it should first be adjusted by 9 // switching over to a real bucket - and then the mocks have to be adjusted to match the 10 // implementation. 11 12 package gcsfs 13 14 import ( 15 "context" 16 "io" 17 "os" 18 "strings" 19 20 "cloud.google.com/go/storage" 21 "github.com/bpfs/defs/afero" 22 "github.com/googleapis/google-cloud-go-testing/storage/stiface" 23 "google.golang.org/api/iterator" 24 ) 25 26 // sets filesystem separators to the one, expected (and hard-coded) in the tests 27 func normSeparators(s string) string { 28 return strings.Replace(s, "\\", "/", -1) 29 } 30 31 type clientMock struct { 32 stiface.Client 33 fs afero.Fs 34 } 35 36 func newClientMock() *clientMock { 37 return &clientMock{fs: afero.NewMemMapFs()} 38 } 39 40 func (m *clientMock) Bucket(name string) stiface.BucketHandle { 41 return &bucketMock{bucketName: name, fs: m.fs} 42 } 43 44 type bucketMock struct { 45 stiface.BucketHandle 46 47 bucketName string 48 49 fs afero.Fs 50 } 51 52 func (m *bucketMock) Attrs(context.Context) (*storage.BucketAttrs, error) { 53 return &storage.BucketAttrs{}, nil 54 } 55 56 func (m *bucketMock) Object(name string) stiface.ObjectHandle { 57 return &objectMock{name: name, fs: m.fs} 58 } 59 60 func (m *bucketMock) Objects(_ context.Context, q *storage.Query) (it stiface.ObjectIterator) { 61 return &objectItMock{name: q.Prefix, fs: m.fs} 62 } 63 64 type objectMock struct { 65 stiface.ObjectHandle 66 67 name string 68 fs afero.Fs 69 } 70 71 func (o *objectMock) NewWriter(_ context.Context) stiface.Writer { 72 return &writerMock{name: o.name, fs: o.fs} 73 } 74 75 func (o *objectMock) NewRangeReader(_ context.Context, offset, length int64) (stiface.Reader, error) { 76 if o.name == "" { 77 return nil, ErrEmptyObjectName 78 } 79 80 file, err := o.fs.Open(o.name) 81 if err != nil { 82 return nil, err 83 } 84 85 if offset > 0 { 86 _, err = file.Seek(offset, io.SeekStart) 87 if err != nil { 88 return nil, err 89 } 90 } 91 92 res := &readerMock{file: file} 93 if length > -1 { 94 res.buf = make([]byte, length) 95 _, err = file.Read(res.buf) 96 if err != nil { 97 return nil, err 98 } 99 } 100 101 return res, nil 102 } 103 104 func (o *objectMock) Delete(_ context.Context) error { 105 if o.name == "" { 106 return ErrEmptyObjectName 107 } 108 return o.fs.Remove(o.name) 109 } 110 111 func (o *objectMock) Attrs(_ context.Context) (*storage.ObjectAttrs, error) { 112 if o.name == "" { 113 return nil, ErrEmptyObjectName 114 } 115 116 info, err := o.fs.Stat(o.name) 117 if err != nil { 118 pathError, ok := err.(*os.PathError) 119 if ok { 120 if pathError.Err == os.ErrNotExist { 121 return nil, storage.ErrObjectNotExist 122 } 123 } 124 125 return nil, err 126 } 127 128 res := &storage.ObjectAttrs{Name: normSeparators(o.name), Size: info.Size(), Updated: info.ModTime()} 129 130 if info.IsDir() { 131 // we have to mock it here, because of FileInfo logic 132 return nil, ErrObjectDoesNotExist 133 } 134 135 return res, nil 136 } 137 138 type writerMock struct { 139 stiface.Writer 140 141 name string 142 fs afero.Fs 143 144 file afero.File 145 } 146 147 func (w *writerMock) Write(p []byte) (n int, err error) { 148 if w.name == "" { 149 return 0, ErrEmptyObjectName 150 } 151 152 if w.file == nil { 153 w.file, err = w.fs.Create(w.name) 154 if err != nil { 155 return 0, err 156 } 157 } 158 159 return w.file.Write(p) 160 } 161 162 func (w *writerMock) Close() error { 163 if w.name == "" { 164 return ErrEmptyObjectName 165 } 166 if w.file == nil { 167 var err error 168 if strings.HasSuffix(w.name, "/") { 169 err = w.fs.Mkdir(w.name, 0o755) 170 if err != nil { 171 return err 172 } 173 } else { 174 _, err = w.Write([]byte{}) 175 if err != nil { 176 return err 177 } 178 } 179 } 180 if w.file != nil { 181 return w.file.Close() 182 } 183 return nil 184 } 185 186 type readerMock struct { 187 stiface.Reader 188 189 file afero.File 190 191 buf []byte 192 } 193 194 func (r *readerMock) Remain() int64 { 195 return 0 196 } 197 198 func (r *readerMock) Read(p []byte) (int, error) { 199 if r.buf != nil { 200 copy(p, r.buf) 201 return len(r.buf), nil 202 } 203 return r.file.Read(p) 204 } 205 206 func (r *readerMock) Close() error { 207 return r.file.Close() 208 } 209 210 type objectItMock struct { 211 stiface.ObjectIterator 212 213 name string 214 fs afero.Fs 215 216 dir afero.File 217 infos []*storage.ObjectAttrs 218 } 219 220 func (it *objectItMock) Next() (*storage.ObjectAttrs, error) { 221 var err error 222 if it.dir == nil { 223 it.dir, err = it.fs.Open(it.name) 224 if err != nil { 225 return nil, err 226 } 227 228 var isDir bool 229 isDir, err = afero.IsDir(it.fs, it.name) 230 if err != nil { 231 return nil, err 232 } 233 234 it.infos = []*storage.ObjectAttrs{} 235 236 if !isDir { 237 var info os.FileInfo 238 info, err = it.dir.Stat() 239 if err != nil { 240 return nil, err 241 } 242 it.infos = append(it.infos, &storage.ObjectAttrs{Name: normSeparators(info.Name()), Size: info.Size(), Updated: info.ModTime()}) 243 } else { 244 var fInfos []os.FileInfo 245 fInfos, err = it.dir.Readdir(0) 246 if err != nil { 247 return nil, err 248 } 249 if it.name != "" { 250 it.infos = append(it.infos, &storage.ObjectAttrs{ 251 Prefix: normSeparators(it.name) + "/", 252 }) 253 } 254 255 for _, info := range fInfos { 256 it.infos = append(it.infos, &storage.ObjectAttrs{Name: normSeparators(info.Name()), Size: info.Size(), Updated: info.ModTime()}) 257 } 258 } 259 } 260 261 if len(it.infos) == 0 { 262 return nil, iterator.Done 263 } 264 265 res := it.infos[0] 266 it.infos = it.infos[1:] 267 268 return res, err 269 }