blob: 18c3c5ff3275fd1afea96572aea13b8b4d8f5a1e [file] [log] [blame]
package test_gcsclient
import (
"bytes"
"compress/gzip"
"context"
"io"
"io/ioutil"
"strings"
"sync"
"cloud.google.com/go/storage"
"go.skia.org/infra/go/gcs"
"go.skia.org/infra/go/util"
)
// MemoryGCSClient is a struct used for testing. Instead of writing to GCS, it
// stores data in memory.
type MemoryGCSClient struct {
bucket string
data map[string][]byte
opts map[string]gcs.FileWriteOptions
mtx sync.RWMutex
}
// Return a MemoryGCSClient instance.
func NewMemoryClient(bucket string) *MemoryGCSClient {
return &MemoryGCSClient{
bucket: bucket,
data: map[string][]byte{},
opts: map[string]gcs.FileWriteOptions{},
}
}
// See documentationn for GCSClient interface.
func (c *MemoryGCSClient) FileReader(ctx context.Context, path string) (io.ReadCloser, error) {
c.mtx.RLock()
defer c.mtx.RUnlock()
contents, ok := c.data[path]
if !ok {
return nil, storage.ErrObjectNotExist
}
rv := ioutil.NopCloser(bytes.NewReader(contents))
// GCS automatically decodes gzip-encoded files. See
// https://cloud.google.com/storage/docs/transcoding. We do the same here so that tests acurately
// reflect what will happen when actually using GCS.
if c.opts[path].ContentEncoding == "gzip" {
var err error
rv, err = gzip.NewReader(rv)
if err != nil {
return nil, err
}
}
return rv, nil
}
// io.WriteCloser implementation used by MemoryGCSClient.
type memoryWriter struct {
buf *bytes.Buffer
client *MemoryGCSClient
path string
}
// See documentation for io.Writer.
func (w *memoryWriter) Write(p []byte) (int, error) {
return w.buf.Write(p)
}
// See documentation for io.Closer.
func (w *memoryWriter) Close() error {
w.client.mtx.Lock()
defer w.client.mtx.Unlock()
w.client.data[w.path] = w.buf.Bytes()
return nil
}
// See documentation for GCSClient interface.
func (c *MemoryGCSClient) FileWriter(ctx context.Context, path string, opts gcs.FileWriteOptions) io.WriteCloser {
c.mtx.Lock()
defer c.mtx.Unlock()
c.opts[path] = opts
return &memoryWriter{
buf: bytes.NewBuffer(nil),
client: c,
path: path,
}
}
// See documentation for GCSClient interface.
func (c *MemoryGCSClient) DoesFileExist(ctx context.Context, path string) (bool, error) {
_, err := c.FileReader(ctx, path)
if err != nil {
if err == storage.ErrObjectNotExist {
return false, nil
}
return false, err
}
return true, nil
}
// See documentation for GCSClient interface.
func (c *MemoryGCSClient) GetFileContents(ctx context.Context, path string) ([]byte, error) {
r, err := c.FileReader(ctx, path)
if err != nil {
return nil, err
}
return ioutil.ReadAll(r)
}
// See documentation for GCSClient interface.
func (c *MemoryGCSClient) SetFileContents(ctx context.Context, path string, opts gcs.FileWriteOptions, contents []byte) error {
return gcs.WithWriteFile(c, ctx, path, opts, func(w io.Writer) error {
_, err := w.Write(contents)
return err
})
}
// See documentation for GCSClient interface.
func (c *MemoryGCSClient) GetFileObjectAttrs(ctx context.Context, path string) (*storage.ObjectAttrs, error) {
c.mtx.RLock()
defer c.mtx.RUnlock()
data, ok := c.data[path]
if !ok {
return nil, storage.ErrObjectNotExist
}
opts, ok := c.opts[path]
if !ok {
return nil, storage.ErrObjectNotExist
}
return &storage.ObjectAttrs{
Bucket: c.bucket,
Name: path,
ContentType: opts.ContentType,
ContentLanguage: opts.ContentLanguage,
Size: int64(len(data)),
ContentEncoding: opts.ContentEncoding,
ContentDisposition: opts.ContentDisposition,
Metadata: util.CopyStringMap(opts.Metadata),
}, nil
}
// See documentation for GCSClient interface.
func (c *MemoryGCSClient) AllFilesInDirectory(ctx context.Context, prefix string, callback func(item *storage.ObjectAttrs)) error {
items := func() []*storage.ObjectAttrs {
c.mtx.RLock()
defer c.mtx.RUnlock()
var items []*storage.ObjectAttrs
for key, data := range c.data {
if strings.HasPrefix(key, prefix) {
opts := c.opts[key]
items = append(items, &storage.ObjectAttrs{
Bucket: c.bucket,
Name: key,
ContentType: opts.ContentType,
ContentLanguage: opts.ContentLanguage,
Size: int64(len(data)),
ContentEncoding: opts.ContentEncoding,
ContentDisposition: opts.ContentDisposition,
Metadata: util.CopyStringMap(opts.Metadata),
})
}
}
return items
}()
for _, item := range items {
callback(item)
}
return nil
}
// See documentation for GCSClient interface.
func (c *MemoryGCSClient) DeleteFile(ctx context.Context, path string) error {
c.mtx.Lock()
defer c.mtx.Unlock()
delete(c.data, path)
delete(c.opts, path)
return nil
}
// See documentation for GCSClient interface.
func (c *MemoryGCSClient) Bucket() string {
return c.bucket
}
// make sure MemoryGCSClient implements the GCSClient interface
var _ gcs.GCSClient = (*MemoryGCSClient)(nil)