...

Source file src/github.com/spf13/afero/gcsfs/gcs_mocks.go

Documentation: github.com/spf13/afero/gcsfs

     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/googleapis/google-cloud-go-testing/storage/stiface"
    22  	"github.com/spf13/afero"
    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  }
   270  

View as plain text