github.com/djmaze/goofys@v0.24.2/internal/backend_gcs_test.go (about)

     1  package internal
     2  
     3  import (
     4  	"github.com/djmaze/goofys/api/common"
     5  
     6  	"bytes"
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"path"
    13  	"sort"
    14  	"syscall"
    15  
    16  	"github.com/jacobsa/fuse"
    17  	"golang.org/x/oauth2/google"
    18  	"golang.org/x/sync/errgroup"
    19  	. "gopkg.in/check.v1"
    20  )
    21  
    22  type GCSTestSpec struct {
    23  	objCount           int    // number of non-prefix blobs uploaded to the bucket
    24  	prefixCount        int    // number of prefix blobs uploaded to the bucket
    25  	existingObjKey     string // test setup will add a blob with this name
    26  	existingObjContent string
    27  	existingObjLen     uint64
    28  	defaultObjLen      uint64 // blobs created in the setup will be of this length
    29  	nonPrefixObjects   []string
    30  	prefixObjects      []string
    31  	prefixes           []string           // these are the prefixes of the prefixObjects
    32  	metadata           map[string]*string // default Metadata for all blobs
    33  	contentType        *string            // default Content-Type for all blobs
    34  	// will be reset in SetUpTest, optionally modified on each test, and cleaned up in TearDownTest
    35  	additionalBlobs []string
    36  }
    37  
    38  type GCSBackendTest struct {
    39  	gcsBackend *GCSBackend
    40  	bucketName string
    41  	testSpec   GCSTestSpec
    42  	chunkSize  int
    43  }
    44  
    45  var _ = Suite(&GCSBackendTest{})
    46  
    47  func (s *GCSBackendTest) getGCSTestConfig() (*common.GCSConfig, error) {
    48  	config := common.NewGCSConfig()
    49  	config.ChunkSize = s.chunkSize
    50  	return config, nil
    51  }
    52  
    53  func (s *GCSBackendTest) getGCSTestBackend(gcsBucket string) (*GCSBackend, error) {
    54  	config, err := s.getGCSTestConfig()
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	spec, err := ParseBucketSpec(gcsBucket)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	gcsBackend, err := NewGCS(spec.Bucket, config)
    63  
    64  	return gcsBackend, err
    65  }
    66  
    67  func (s *GCSBackendTest) SetUpSuite(c *C) {
    68  	if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" {
    69  		c.Skip("Skipping because GOOGLE_APPLICATION_CREDENTIALS variable is unset.")
    70  	}
    71  
    72  	bktName := "goofys-test-" + RandStringBytesMaskImprSrc(16)
    73  	s.bucketName = bktName
    74  	s.testSpec = GCSTestSpec{
    75  		objCount:      10,
    76  		prefixCount:   5,
    77  		defaultObjLen: 16,
    78  		metadata: map[string]*string{
    79  			"foo": PString("bar"),
    80  			"bar": PString("baz"),
    81  		},
    82  		contentType: PString("text/plain"),
    83  	}
    84  	s.chunkSize = 1 * 1024 * 1024
    85  	s.gcsBackend, _ = s.getGCSTestBackend(fmt.Sprintf("gs://%s", bktName))
    86  
    87  	_, err := s.gcsBackend.MakeBucket(&MakeBucketInput{})
    88  	c.Assert(err, IsNil)
    89  
    90  	errGroup, _ := errgroup.WithContext(context.Background())
    91  	objLen := s.testSpec.defaultObjLen
    92  
    93  	// create object without prefix
    94  	// files is stored as 00_blob, 01_blob, ... 09_blob (10 items)
    95  	for i := 0; i < s.testSpec.objCount; i++ {
    96  		objKey := fmt.Sprintf("%02d_blob", i)
    97  		objContent := RandStringBytesMaskImprSrc(int(objLen))
    98  
    99  		errGroup.Go(func() error {
   100  			_, err := s.gcsBackend.PutBlob(&PutBlobInput{
   101  				Key:         objKey,
   102  				Body:        bytes.NewReader([]byte(objContent)),
   103  				Metadata:    s.testSpec.metadata,
   104  				ContentType: s.testSpec.contentType,
   105  			})
   106  			c.Assert(err, IsNil)
   107  
   108  			// verify object attr after a put blob
   109  			objAttr, err := s.gcsBackend.HeadBlob(&HeadBlobInput{Key: objKey})
   110  			c.Assert(err, IsNil)
   111  			c.Assert(NilStr(objAttr.Key), Equals, objKey)
   112  			c.Assert(objAttr.Size, Equals, uint64(int(objLen)))
   113  			c.Assert(NilStr(objAttr.ContentType), Equals, NilStr(s.testSpec.contentType))
   114  			c.Assert(objAttr.Metadata, DeepEquals, s.testSpec.metadata)
   115  
   116  			return err
   117  		})
   118  
   119  		// set the existing object to be the first object
   120  		if s.testSpec.existingObjKey == "" {
   121  			s.testSpec.existingObjKey = objKey
   122  			s.testSpec.existingObjContent = objContent
   123  			s.testSpec.existingObjLen = objLen
   124  		}
   125  		s.testSpec.nonPrefixObjects = append(s.testSpec.nonPrefixObjects, objKey)
   126  	}
   127  
   128  	// create object with prefix
   129  	// file is stored as 00_prefix/blob, 01_prefix/blob, ..., 04_prefix/blob (5 items)
   130  	for i := 0; i < s.testSpec.prefixCount; i++ {
   131  		objKey := fmt.Sprintf("%02d_prefix/blob", i)
   132  		objContent := RandStringBytesMaskImprSrc(int(objLen))
   133  		errGroup.Go(func() error {
   134  			_, err := s.gcsBackend.PutBlob(&PutBlobInput{
   135  				Key:         objKey,
   136  				Body:        bytes.NewReader([]byte(objContent)),
   137  				Metadata:    s.testSpec.metadata,
   138  				ContentType: s.testSpec.contentType,
   139  			})
   140  			c.Assert(err, IsNil)
   141  
   142  			objAttr, err := s.gcsBackend.HeadBlob(&HeadBlobInput{Key: objKey})
   143  			c.Assert(err, IsNil)
   144  			c.Assert(NilStr(objAttr.Key), Equals, objKey)
   145  			c.Assert(objAttr.Size, Equals, uint64(int(objLen)))
   146  			c.Assert(NilStr(objAttr.ContentType), Equals, NilStr(s.testSpec.contentType))
   147  			c.Assert(objAttr.Metadata, DeepEquals, s.testSpec.metadata)
   148  			return err
   149  		})
   150  
   151  		s.testSpec.prefixObjects = append(s.testSpec.prefixObjects, objKey)
   152  		dir, _ := path.Split(objKey)
   153  		s.testSpec.prefixes = append(s.testSpec.prefixes, dir)
   154  	}
   155  
   156  	err = errGroup.Wait()
   157  	c.Assert(err, IsNil)
   158  }
   159  
   160  func (s *GCSBackendTest) TearDownSuite(c *C) {
   161  	if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" {
   162  		c.Skip("Skipping because GOOGLE_APPLICATION_CREDENTIALS variable is unset.")
   163  	}
   164  	out, err := s.gcsBackend.ListBlobs(&ListBlobsInput{})
   165  	c.Assert(err, IsNil)
   166  
   167  	var items []string
   168  	for _, item := range out.Items {
   169  		items = append(items, NilStr(item.Key))
   170  	}
   171  
   172  	_, err = s.gcsBackend.DeleteBlobs(&DeleteBlobsInput{Items: items})
   173  	c.Assert(err, IsNil)
   174  
   175  	_, err = s.gcsBackend.RemoveBucket(&RemoveBucketInput{})
   176  	c.Assert(err, IsNil)
   177  }
   178  
   179  func (s *GCSBackendTest) SetUpTest(c *C) {
   180  	s.testSpec.additionalBlobs = []string{}
   181  }
   182  
   183  func (s *GCSBackendTest) TearDownTest(c *C) {
   184  	_, err := s.gcsBackend.DeleteBlobs(&DeleteBlobsInput{s.testSpec.additionalBlobs})
   185  	// successful deletion should be: nil error or not found
   186  	c.Assert(err == nil || err == syscall.ENOENT, Equals, true)
   187  }
   188  
   189  func (s *GCSBackendTest) TestGCSBackend_Init_Authenticated(c *C) {
   190  	// No error when accessing existing private bucket.
   191  	err := s.gcsBackend.Init(s.bucketName)
   192  	c.Assert(err, IsNil)
   193  
   194  	// Not Found error when accessing nonexistent bucket.
   195  	randBktName := RandStringBytesMaskImprSrc(16)
   196  	gcsBackend, err := s.getGCSTestBackend(randBktName)
   197  	c.Assert(err, IsNil)
   198  	err = gcsBackend.Init(RandStringBytesMaskImprSrc(16))
   199  	c.Assert(err, ErrorMatches, fmt.Sprintf("bucket %s does not exist", randBktName))
   200  
   201  	// No error when accessing public bucket.
   202  	gcsBackend, err = s.getGCSTestBackend("gcp-public-data-nexrad-l2")
   203  	c.Assert(err, IsNil)
   204  	err = gcsBackend.Init(RandStringBytesMaskImprSrc(16))
   205  	c.Assert(err, IsNil)
   206  }
   207  
   208  func (s *GCSBackendTest) TestGCSBackend_Init_Unauthenticated(c *C) {
   209  	defaultCredentials := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
   210  	defer func() {
   211  		os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", defaultCredentials)
   212  	}()
   213  	os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")
   214  
   215  	credentials, err := google.FindDefaultCredentials(context.Background())
   216  	if credentials != nil {
   217  		c.Skip("Skipping this test because credentials still exist in the environment.")
   218  	}
   219  
   220  	// Access error on existing private bucket.
   221  	gcsBackend, err := s.getGCSTestBackend(s.bucketName)
   222  	c.Assert(err, IsNil)
   223  	err = gcsBackend.Init(RandStringBytesMaskImprSrc(15))
   224  	c.Assert(err, Equals, syscall.EACCES)
   225  
   226  	// Not Found error when accessing nonexistent bucket.
   227  	randBktName := RandStringBytesMaskImprSrc(16)
   228  	gcsBackend, err = s.getGCSTestBackend(randBktName)
   229  	c.Assert(err, IsNil)
   230  	err = gcsBackend.Init(RandStringBytesMaskImprSrc(16))
   231  	c.Assert(err, ErrorMatches, fmt.Sprintf("bucket %s does not exist", randBktName))
   232  
   233  	// No error when accessing public bucket.
   234  	gcsBackend, err = s.getGCSTestBackend("gcp-public-data-nexrad-l2")
   235  	c.Assert(err, IsNil)
   236  	err = gcsBackend.Init(RandStringBytesMaskImprSrc(16))
   237  	c.Assert(err, IsNil)
   238  }
   239  
   240  func (s *GCSBackendTest) TestGCSBackend_Init_Authenticated_ReadOnlyAccess(c *C) {
   241  	// READONLY_GOOGLE_APPLICATION_CREDENTIALS is a credential that is authorized to read-only access to the bucket
   242  	if os.Getenv("READONLY_GOOGLE_APPLICATION_CREDENTIALS") == "" {
   243  		c.Skip("Skipping because READONLY_GOOGLE_APPLICATION_CREDENTIALS is unset.")
   244  	}
   245  
   246  	defaultCredentials := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
   247  	defer func() {
   248  		os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", defaultCredentials)
   249  	}()
   250  	os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", os.Getenv("READONLY_GOOGLE_APPLICATION_CREDENTIALS"))
   251  
   252  	// No error when accessing existing private bucket.
   253  	gcsBackend, err := s.getGCSTestBackend(s.bucketName)
   254  	c.Assert(err, IsNil)
   255  	err = gcsBackend.Init(s.bucketName)
   256  	c.Assert(err, IsNil)
   257  
   258  	// Access error when trying to modify object.
   259  	_, err = gcsBackend.DeleteBlob(&DeleteBlobInput{Key: s.testSpec.existingObjKey})
   260  	c.Assert(err, Equals, syscall.EACCES)
   261  
   262  	// Not Found error when accessing nonexistent bucket.
   263  	randBktName := RandStringBytesMaskImprSrc(16)
   264  	gcsBackend, err = s.getGCSTestBackend(randBktName)
   265  	c.Assert(err, IsNil)
   266  	err = gcsBackend.Init(RandStringBytesMaskImprSrc(16))
   267  	c.Assert(err, ErrorMatches, fmt.Sprintf("bucket %s does not exist", randBktName))
   268  
   269  	// No error when accessing public bucket.
   270  	gcsBackend, err = s.getGCSTestBackend("gcp-public-data-nexrad-l2")
   271  	c.Assert(err, IsNil)
   272  	err = gcsBackend.Init(RandStringBytesMaskImprSrc(16))
   273  	c.Assert(err, IsNil)
   274  }
   275  
   276  func (s *GCSBackendTest) TestGCSBackend_HeadBlob_NotExist(c *C) {
   277  	objKey := RandStringBytesMaskImprSrc(16)
   278  	_, err := s.gcsBackend.HeadBlob(&HeadBlobInput{Key: objKey})
   279  	c.Assert(err, Equals, fuse.ENOENT)
   280  }
   281  
   282  func (s *GCSBackendTest) TestGCSBackend_GetBlob_NotExist(c *C) {
   283  	objKey := RandStringBytesMaskImprSrc(16)
   284  	_, err := s.gcsBackend.GetBlob(&GetBlobInput{Key: objKey})
   285  	c.Assert(err, Equals, fuse.ENOENT)
   286  }
   287  
   288  func (s *GCSBackendTest) TestGCSBackend_GetFullBlob(c *C) {
   289  	fullBlob, err := s.gcsBackend.GetBlob(&GetBlobInput{Key: s.testSpec.existingObjKey})
   290  	c.Assert(err, IsNil)
   291  	defer fullBlob.Body.Close()
   292  
   293  	actualContent, err := ioutil.ReadAll(fullBlob.Body)
   294  	c.Assert(err, IsNil)
   295  	c.Assert(string(actualContent), Equals, s.testSpec.existingObjContent)
   296  }
   297  
   298  func (s *GCSBackendTest) TestGCSBackend_GetPartialBlob(c *C) {
   299  	var startOffset uint64 = 4
   300  	var count uint64 = 8
   301  	partialBlob, err := s.gcsBackend.GetBlob(&GetBlobInput{
   302  		Key:   s.testSpec.existingObjKey,
   303  		Start: startOffset,
   304  		Count: count,
   305  	})
   306  	c.Assert(err, IsNil)
   307  	defer partialBlob.Body.Close()
   308  
   309  	actualContent, err := ioutil.ReadAll(partialBlob.Body)
   310  	c.Assert(err, IsNil)
   311  	c.Assert(string(actualContent), Equals, s.testSpec.existingObjContent[startOffset:startOffset+count])
   312  }
   313  
   314  func (s *GCSBackendTest) TestGCSBackend_PutBlob_NilBody(c *C) {
   315  	objKey := RandStringBytesMaskImprSrc(int(s.testSpec.defaultObjLen))
   316  	s.addToAdditionalBlobs(objKey)
   317  
   318  	_, err := s.gcsBackend.PutBlob(&PutBlobInput{Key: objKey, Body: nil})
   319  	c.Assert(err, IsNil)
   320  }
   321  
   322  func (s *GCSBackendTest) addToAdditionalBlobs(objKey string) {
   323  	s.testSpec.additionalBlobs = append(s.testSpec.additionalBlobs, objKey)
   324  }
   325  
   326  func (s *GCSBackendTest) TestGCSBackend_GetGzipEncodedBlob(c *C) {
   327  	objKey := "gzipObj"
   328  	s.addToAdditionalBlobs(objKey)
   329  	originalContent := RandStringBytesMaskImprSrc(int(s.testSpec.defaultObjLen))
   330  
   331  	// Goofys cannot upload a file with content-encoding: gzip. So we will use GCS sdk directly to create such blob
   332  	s.writeGzipEncodedFile(c, objKey, originalContent)
   333  
   334  	gzipBlob, err := s.gcsBackend.GetBlob(&GetBlobInput{Key: objKey})
   335  	c.Assert(err, IsNil)
   336  	defer gzipBlob.Body.Close()
   337  
   338  	actualContent, err := ioutil.ReadAll(gzipBlob.Body)
   339  	c.Assert(string(actualContent), Equals, originalContent)
   340  }
   341  
   342  func (s *GCSBackendTest) writeGzipEncodedFile(c *C, objKey string, content string) {
   343  	writer := s.gcsBackend.bucket.Object(objKey).NewWriter(context.Background())
   344  	writer.ContentEncoding = "gzip"
   345  	_, err := writer.Write([]byte(content))
   346  	defer writer.Close()
   347  	c.Assert(err, IsNil)
   348  }
   349  
   350  func (s *GCSBackendTest) TestGCSBackend_ListBlobs_NoPrefixNoDelim(c *C) {
   351  	// list all objects in bucket as items, no prefix because delim is unset
   352  	listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{})
   353  	c.Assert(err, IsNil)
   354  
   355  	actualOutputs := s.extractAllNamesFromListOutputs(listOutputs)
   356  	s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects, s.testSpec.prefixObjects)
   357  	c.Assert(listOutputs.NextContinuationToken, IsNil)
   358  	c.Assert(listOutputs.IsTruncated, Equals, false)
   359  }
   360  
   361  func (s *GCSBackendTest) TestGCSBackend_ListBlobs_NoPrefixWithDelim(c *C) {
   362  	// list all objects but separates items and prefixes
   363  	listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{Delimiter: PString("/")})
   364  	c.Assert(err, IsNil)
   365  	// should contain non prefix objects: 00_blob, ..., 09_blob and prefixes: 00_prefix/, ..., 04_prefix/
   366  	actualOutputs := s.extractAllNamesFromListOutputs(listOutputs)
   367  	s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects, s.testSpec.prefixes)
   368  	c.Assert(listOutputs.NextContinuationToken, IsNil)
   369  	c.Assert(listOutputs.IsTruncated, Equals, false)
   370  }
   371  
   372  func (s *GCSBackendTest) TestGCSBackend_ListBlobs_WithPrefixNoDelim(c *C) {
   373  	// list all objects that have a matching prefix as items
   374  	listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{Prefix: PString("00")})
   375  	c.Assert(err, IsNil)
   376  	// should contain all items under prefix: 00_blob & 00_prefix/blob
   377  	actualOutputs := s.extractAllNamesFromListOutputs(listOutputs)
   378  	s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects[:1], s.testSpec.prefixObjects[:1])
   379  	c.Assert(listOutputs.NextContinuationToken, IsNil)
   380  	c.Assert(listOutputs.IsTruncated, Equals, false)
   381  }
   382  
   383  func (s *GCSBackendTest) TestGCSBackend_ListBlobs_WithPrefixWithDelim(c *C) {
   384  	// lists objects that have a matching prefix and separates non prefix & prefixes
   385  	listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{
   386  		Prefix:    PString("00"),
   387  		Delimiter: PString("/"),
   388  	})
   389  	c.Assert(err, IsNil)
   390  	// should contain 00_blob and 00_prefix/
   391  	actualOutputs := s.extractAllNamesFromListOutputs(listOutputs)
   392  	s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects[:1], s.testSpec.prefixes[:1])
   393  	c.Assert(listOutputs.NextContinuationToken, IsNil)
   394  	c.Assert(listOutputs.IsTruncated, Equals, false)
   395  }
   396  
   397  func (s *GCSBackendTest) TestGCSBackend_ListBlobs_StartAfter(c *C) {
   398  	cutoffKey := "04_prefix/blob"
   399  	// list results all come right on or after StartAfter in lexicographical order
   400  	listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{
   401  		Delimiter:  PString("/"),
   402  		StartAfter: PString(cutoffKey),
   403  	})
   404  	c.Assert(err, IsNil)
   405  
   406  	actualOutputs := s.extractAllNamesFromListOutputs(listOutputs)
   407  	// contain elements from 05_blob and 04_prefix/
   408  	s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects[5:],
   409  		s.testSpec.prefixes[len(s.testSpec.prefixes)-1:])
   410  
   411  	// assert all items are larger or equal to StartAfter prefix (because we used delimiter) in lexicographical order
   412  	cutOffPrefix, _ := path.Split(cutoffKey)
   413  	for _, item := range actualOutputs {
   414  		c.Assert(item >= cutOffPrefix, Equals, true)
   415  	}
   416  }
   417  
   418  func (s *GCSBackendTest) TestGCSBackend_ListBlobs_MaxKeysPaginate(c *C) {
   419  	totalKeys := s.testSpec.objCount + s.testSpec.prefixCount
   420  	maxKeys := 1
   421  
   422  	var nextContToken *string
   423  	var actualOutputs []string
   424  
   425  	// iterate over pages of maxKeys
   426  	for i := 0; i < totalKeys-1; i++ {
   427  		listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{
   428  			Delimiter:         PString("/"),
   429  			MaxKeys:           PUInt32(uint32(maxKeys)),
   430  			ContinuationToken: nextContToken,
   431  		})
   432  		c.Assert(err, IsNil)
   433  		c.Assert(len(listOutputs.Items)+len(listOutputs.Prefixes), Equals, maxKeys)
   434  		c.Assert(listOutputs.NextContinuationToken, NotNil)
   435  		nextContToken = listOutputs.NextContinuationToken
   436  		actualOutputs = append(actualOutputs, s.extractAllNamesFromListOutputs(listOutputs)...)
   437  	}
   438  	// remaining object in the last page
   439  	listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{
   440  		Delimiter:         PString("/"),
   441  		MaxKeys:           PUInt32(uint32(maxKeys)),
   442  		ContinuationToken: nextContToken,
   443  	})
   444  	actualOutputs = append(actualOutputs, s.extractAllNamesFromListOutputs(listOutputs)...)
   445  	c.Assert(err, IsNil)
   446  	c.Assert(len(listOutputs.Items)+len(listOutputs.Prefixes) <= maxKeys, Equals, true)
   447  	c.Assert(listOutputs.NextContinuationToken, IsNil)
   448  	s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects, s.testSpec.prefixes)
   449  }
   450  
   451  func (s *GCSBackendTest) extractAllNamesFromListOutputs(listOutputs *ListBlobsOutput) []string {
   452  	var actualOutputs []string
   453  	for _, item := range listOutputs.Items {
   454  		actualOutputs = append(actualOutputs, NilStr(item.Key))
   455  	}
   456  	for _, prefix := range listOutputs.Prefixes {
   457  		actualOutputs = append(actualOutputs, NilStr(prefix.Prefix))
   458  	}
   459  	return actualOutputs
   460  }
   461  
   462  // This check the actual outputs against list of expected outputs
   463  func (s *GCSBackendTest) checkListOutputs(c *C, actualOutputs []string, expectedOutputsArgs ...[]string) {
   464  	var expectedResults []string
   465  	for _, expectedOutputs := range expectedOutputsArgs {
   466  		expectedResults = append(expectedResults, expectedOutputs...)
   467  	}
   468  	sort.Strings(expectedResults)
   469  	sort.Strings(actualOutputs)
   470  
   471  	c.Assert(actualOutputs, DeepEquals, expectedResults)
   472  }
   473  
   474  func (s *GCSBackendTest) TestGCSBackend_CopyBlob_PreserveMetadata(c *C) {
   475  	srcKey := s.testSpec.existingObjKey
   476  	destKey := RandStringBytesMaskImprSrc(16)
   477  	s.addToAdditionalBlobs(destKey)
   478  
   479  	srcAttr, err := s.gcsBackend.HeadBlob(&HeadBlobInput{Key: srcKey})
   480  	c.Assert(err, IsNil)
   481  
   482  	_, err = s.gcsBackend.CopyBlob(&CopyBlobInput{
   483  		Source:      srcKey,
   484  		Destination: destKey,
   485  	})
   486  	c.Assert(err, IsNil)
   487  
   488  	destAttr, err := s.gcsBackend.HeadBlob(&HeadBlobInput{Key: destKey})
   489  	c.Assert(err, IsNil)
   490  	c.Assert(NilStr(destAttr.ContentType), Equals, NilStr(srcAttr.ContentType))
   491  	c.Assert(destAttr.Metadata, DeepEquals, srcAttr.Metadata)
   492  	c.Assert(srcAttr.ETag, Not(Equals), destAttr.ETag)
   493  
   494  	destOut, err := s.gcsBackend.GetBlob(&GetBlobInput{Key: srcKey})
   495  	c.Assert(err, IsNil)
   496  	defer destOut.Body.Close()
   497  	destContent, err := ioutil.ReadAll(destOut.Body)
   498  	c.Assert(string(destContent), Equals, s.testSpec.existingObjContent)
   499  }
   500  
   501  func (s *GCSBackendTest) TestGCSBackend_MultipartUpload_BeginAddCommit(c *C) {
   502  	objKey := RandStringBytesMaskImprSrc(16)
   503  	s.addToAdditionalBlobs(objKey)
   504  
   505  	commitInput, err := s.gcsBackend.MultipartBlobBegin(&MultipartBlobBeginInput{
   506  		Key: objKey,
   507  	})
   508  	c.Assert(err, IsNil)
   509  	c.Assert(commitInput.NumParts, Equals, uint32(0))
   510  
   511  	// We will have numFullChunks+1 chunks. That last chunk is of size lastChunkSize
   512  	var numFullChunks = 2
   513  	lastChunkSize := s.chunkSize - 1
   514  	fileSize := uint64((numFullChunks * s.chunkSize) + lastChunkSize)
   515  
   516  	// generate data to simulate MPU behavior
   517  	buf := new(bytes.Buffer)
   518  	data := io.LimitReader(&SeqReader{}, int64(fileSize))
   519  	_, err = buf.ReadFrom(data)
   520  	c.Assert(err, IsNil)
   521  	reader := bytes.NewReader(buf.Bytes())
   522  
   523  	// add the full chunks
   524  	for i := 0; i < numFullChunks; i++ {
   525  		sectionReader := io.NewSectionReader(reader, int64(i*s.chunkSize), int64(s.chunkSize))
   526  		addOut, err := s.gcsBackend.MultipartBlobAdd(&MultipartBlobAddInput{
   527  			Commit:     commitInput,
   528  			PartNumber: uint32(i + 1),
   529  			Body:       sectionReader,
   530  			Size:       uint64(s.chunkSize),
   531  		})
   532  		c.Assert(err, IsNil)
   533  		c.Assert(addOut, NotNil)
   534  	}
   535  
   536  	// add the remaining chunk
   537  	sectionReader := io.NewSectionReader(reader, int64(numFullChunks*s.chunkSize), int64(lastChunkSize))
   538  	addOut, err := s.gcsBackend.MultipartBlobAdd(&MultipartBlobAddInput{
   539  		Commit:     commitInput,
   540  		PartNumber: uint32(numFullChunks + 1),
   541  		Body:       sectionReader,
   542  		Size:       uint64(lastChunkSize),
   543  	})
   544  	c.Assert(addOut, NotNil)
   545  	c.Assert(err, IsNil)
   546  
   547  	commitOut, err := s.gcsBackend.MultipartBlobCommit(commitInput)
   548  	c.Assert(err, IsNil)
   549  	c.Assert(commitOut.ETag, NotNil)
   550  
   551  	_, err = s.gcsBackend.HeadBlob(&HeadBlobInput{Key: objKey})
   552  	c.Assert(err, IsNil)
   553  
   554  	// assert uploaded content is correct
   555  	blobOutput, err := s.gcsBackend.GetBlob(&GetBlobInput{Key: objKey})
   556  	c.Assert(err, IsNil)
   557  	defer blobOutput.Body.Close()
   558  
   559  	_, err = reader.Seek(0, io.SeekStart) // seek to the beginning of the file
   560  	c.Assert(err, IsNil)
   561  	_, err = CompareReader(reader, blobOutput.Body, 0) // assert content
   562  	c.Assert(err, IsNil)
   563  }
   564  
   565  func (s *GCSBackendTest) TestGCSBackend_MultipartUpload_Abort(c *C) {
   566  	src := RandStringBytesMaskImprSrc(16)
   567  	commitInput, err := s.gcsBackend.MultipartBlobBegin(&MultipartBlobBeginInput{Key: src})
   568  	c.Assert(err, IsNil)
   569  
   570  	addOut, err := s.gcsBackend.MultipartBlobAdd(&MultipartBlobAddInput{
   571  		Commit:     commitInput,
   572  		PartNumber: 1,
   573  		Body:       bytes.NewReader([]byte(src)),
   574  		Size:       uint64(len(src)),
   575  		Last:       false,
   576  	})
   577  	c.Assert(err, IsNil)
   578  	c.Assert(addOut, NotNil)
   579  
   580  	_, err = s.gcsBackend.MultipartBlobAbort(commitInput)
   581  	c.Assert(err, IsNil)
   582  
   583  	_, err = s.gcsBackend.HeadBlob(&HeadBlobInput{Key: src})
   584  	c.Assert(err, Equals, fuse.ENOENT)
   585  }