gcs_mocks.go - afero - [fork] go afero port for 9front
(HTM) git clone https://git.drkhsh.at/afero.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
gcs_mocks.go (5438B)
---
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 "google.golang.org/api/iterator"
22
23 "github.com/spf13/afero"
24 "github.com/spf13/afero/gcsfs/internal/stiface"
25 )
26
27 // sets filesystem separators to the one, expected (and hard-coded) in the tests
28 func normSeparators(s string) string {
29 return strings.Replace(s, "\\", "/", -1)
30 }
31
32 type clientMock struct {
33 stiface.Client
34 fs afero.Fs
35 }
36
37 func newClientMock() *clientMock {
38 return &clientMock{fs: afero.NewMemMapFs()}
39 }
40
41 func (m *clientMock) Bucket(name string) stiface.BucketHandle {
42 return &bucketMock{bucketName: name, fs: m.fs}
43 }
44
45 type bucketMock struct {
46 stiface.BucketHandle
47
48 bucketName string
49
50 fs afero.Fs
51 }
52
53 func (m *bucketMock) Attrs(context.Context) (*storage.BucketAttrs, error) {
54 return &storage.BucketAttrs{}, nil
55 }
56
57 func (m *bucketMock) Object(name string) stiface.ObjectHandle {
58 return &objectMock{name: name, fs: m.fs}
59 }
60
61 func (m *bucketMock) Objects(_ context.Context, q *storage.Query) (it stiface.ObjectIterator) {
62 return &objectItMock{name: q.Prefix, fs: m.fs}
63 }
64
65 type objectMock struct {
66 stiface.ObjectHandle
67
68 name string
69 fs afero.Fs
70 }
71
72 func (o *objectMock) NewWriter(_ context.Context) stiface.Writer {
73 return &writerMock{name: o.name, fs: o.fs}
74 }
75
76 func (o *objectMock) NewRangeReader(_ context.Context, offset, length int64) (stiface.Reader, error) {
77 if o.name == "" {
78 return nil, ErrEmptyObjectName
79 }
80
81 file, err := o.fs.Open(o.name)
82 if err != nil {
83 return nil, err
84 }
85
86 if offset > 0 {
87 _, err = file.Seek(offset, io.SeekStart)
88 if err != nil {
89 return nil, err
90 }
91 }
92
93 res := &readerMock{file: file}
94 if length > -1 {
95 res.buf = make([]byte, length)
96 _, err = file.Read(res.buf)
97 if err != nil {
98 return nil, err
99 }
100 }
101
102 return res, nil
103 }
104
105 func (o *objectMock) Delete(_ context.Context) error {
106 if o.name == "" {
107 return ErrEmptyObjectName
108 }
109 return o.fs.Remove(o.name)
110 }
111
112 func (o *objectMock) Attrs(_ context.Context) (*storage.ObjectAttrs, error) {
113 if o.name == "" {
114 return nil, ErrEmptyObjectName
115 }
116
117 info, err := o.fs.Stat(o.name)
118 if err != nil {
119 pathError, ok := err.(*os.PathError)
120 if ok {
121 if pathError.Err == os.ErrNotExist {
122 return nil, storage.ErrObjectNotExist
123 }
124 }
125
126 return nil, err
127 }
128
129 res := &storage.ObjectAttrs{Name: normSeparators(o.name), Size: info.Size(), Updated: info.ModTime()}
130
131 if info.IsDir() {
132 // we have to mock it here, because of FileInfo logic
133 return nil, ErrObjectDoesNotExist
134 }
135
136 return res, nil
137 }
138
139 type writerMock struct {
140 stiface.Writer
141
142 name string
143 fs afero.Fs
144
145 file afero.File
146 }
147
148 func (w *writerMock) Write(p []byte) (n int, err error) {
149 if w.name == "" {
150 return 0, ErrEmptyObjectName
151 }
152
153 if w.file == nil {
154 w.file, err = w.fs.Create(w.name)
155 if err != nil {
156 return 0, err
157 }
158 }
159
160 return w.file.Write(p)
161 }
162
163 func (w *writerMock) Close() error {
164 if w.name == "" {
165 return ErrEmptyObjectName
166 }
167 if w.file == nil {
168 var err error
169 if strings.HasSuffix(w.name, "/") {
170 err = w.fs.Mkdir(w.name, 0o755)
171 if err != nil {
172 return err
173 }
174 } else {
175 _, err = w.Write([]byte{})
176 if err != nil {
177 return err
178 }
179 }
180 }
181 if w.file != nil {
182 return w.file.Close()
183 }
184 return nil
185 }
186
187 type readerMock struct {
188 stiface.Reader
189
190 file afero.File
191
192 buf []byte
193 }
194
195 func (r *readerMock) Remain() int64 {
196 return 0
197 }
198
199 func (r *readerMock) Read(p []byte) (int, error) {
200 if r.buf != nil {
201 copy(p, r.buf)
202 return len(r.buf), nil
203 }
204 return r.file.Read(p)
205 }
206
207 func (r *readerMock) Close() error {
208 return r.file.Close()
209 }
210
211 type objectItMock struct {
212 stiface.ObjectIterator
213
214 name string
215 fs afero.Fs
216
217 dir afero.File
218 infos []*storage.ObjectAttrs
219 }
220
221 func (it *objectItMock) Next() (*storage.ObjectAttrs, error) {
222 var err error
223 if it.dir == nil {
224 it.dir, err = it.fs.Open(it.name)
225 if err != nil {
226 return nil, err
227 }
228
229 var isDir bool
230 isDir, err = afero.IsDir(it.fs, it.name)
231 if err != nil {
232 return nil, err
233 }
234
235 it.infos = []*storage.ObjectAttrs{}
236
237 if !isDir {
238 var info os.FileInfo
239 info, err = it.dir.Stat()
240 if err != nil {
241 return nil, err
242 }
243 it.infos = append(it.infos, &storage.ObjectAttrs{Name: normSeparators(info.Name()), Size: info.Size(), Updated: info.ModTime()})
244 } else {
245 var fInfos []os.FileInfo
246 fInfos, err = it.dir.Readdir(0)
247 if err != nil {
248 return nil, err
249 }
250 if it.name != "" {
251 it.infos = append(it.infos, &storage.ObjectAttrs{
252 Prefix: normSeparators(it.name) + "/",
253 })
254 }
255
256 for _, info := range fInfos {
257 it.infos = append(it.infos, &storage.ObjectAttrs{Name: normSeparators(info.Name()), Size: info.Size(), Updated: info.ModTime()})
258 }
259 }
260 }
261
262 if len(it.infos) == 0 {
263 return nil, iterator.Done
264 }
265
266 res := it.infos[0]
267 it.infos = it.infos[1:]
268
269 return res, err
270 }