github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/blobstore/blobstore_test.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package blobstore
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/binary"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"log"
    24  	"math/rand"
    25  	"os"
    26  	"reflect"
    27  	"runtime"
    28  	"testing"
    29  
    30  	"cloud.google.com/go/storage"
    31  	"github.com/google/uuid"
    32  )
    33  
    34  const (
    35  	key        = "test"
    36  	rmwRetries = 5
    37  )
    38  
    39  var (
    40  	ctx           context.Context
    41  	bucket        *storage.BucketHandle
    42  	testGCSBucket string
    43  )
    44  
    45  func init() {
    46  	testGCSBucket = os.Getenv("TEST_GCS_BUCKET")
    47  	if testGCSBucket != "" {
    48  		ctx = context.Background()
    49  		gcs, err := storage.NewClient(ctx)
    50  
    51  		if err != nil {
    52  			panic("Could not create GCSBlobstore")
    53  		}
    54  
    55  		bucket = gcs.Bucket(testGCSBucket)
    56  	}
    57  }
    58  
    59  type BlobstoreTest struct {
    60  	bsType         string
    61  	bs             Blobstore
    62  	rmwConcurrency int
    63  	rmwIterations  int
    64  }
    65  
    66  func appendGCSTest(tests []BlobstoreTest) []BlobstoreTest {
    67  	if testGCSBucket != "" {
    68  		gcsTest := BlobstoreTest{"gcs", &GCSBlobstore{bucket, testGCSBucket, uuid.New().String() + "/"}, 4, 4}
    69  		tests = append(tests, gcsTest)
    70  	}
    71  
    72  	return tests
    73  }
    74  
    75  func appendLocalTest(tests []BlobstoreTest) []BlobstoreTest {
    76  	dir, err := ioutil.TempDir("", uuid.New().String())
    77  
    78  	if err != nil {
    79  		panic("Could not create temp dir")
    80  	}
    81  
    82  	return append(tests, BlobstoreTest{"local", NewLocalBlobstore(dir), 10, 20})
    83  }
    84  
    85  func newBlobStoreTests() []BlobstoreTest {
    86  	var tests []BlobstoreTest
    87  	tests = append(tests, BlobstoreTest{"inmem", NewInMemoryBlobstore(), 10, 20})
    88  	tests = appendLocalTest(tests)
    89  	tests = appendGCSTest(tests)
    90  
    91  	return tests
    92  }
    93  
    94  func randBytes(size int) []byte {
    95  	bytes := make([]byte, size)
    96  	rand.Read(bytes)
    97  
    98  	return bytes
    99  }
   100  
   101  func testPutAndGetBack(t *testing.T, bs Blobstore) {
   102  	testData := randBytes(32)
   103  	ver, err := PutBytes(context.Background(), bs, key, testData)
   104  
   105  	if err != nil {
   106  		t.Errorf("Put failed %v.", err)
   107  	}
   108  
   109  	retrieved, retVer, err := GetBytes(context.Background(), bs, key, BlobRange{})
   110  
   111  	if err != nil {
   112  		t.Errorf("Get failed: %v.", err)
   113  	}
   114  
   115  	if ver != retVer {
   116  		t.Errorf("Version doesn't match. Expected: %s Actual: %s.", ver, retVer)
   117  	}
   118  
   119  	if !reflect.DeepEqual(retrieved, testData) {
   120  		t.Errorf("Data mismatch.")
   121  	}
   122  }
   123  
   124  func TestPutAndGetBack(t *testing.T) {
   125  	for _, bsTest := range newBlobStoreTests() {
   126  		t.Run(bsTest.bsType, func(t *testing.T) {
   127  			testPutAndGetBack(t, bsTest.bs)
   128  		})
   129  	}
   130  }
   131  
   132  func testGetMissing(t *testing.T, bs Blobstore) {
   133  	_, _, err := GetBytes(context.Background(), bs, key, BlobRange{})
   134  
   135  	if err == nil || !IsNotFoundError(err) {
   136  		t.Errorf("Key should be missing.")
   137  	}
   138  }
   139  
   140  func TestGetMissing(t *testing.T) {
   141  	for _, bsTest := range newBlobStoreTests() {
   142  		t.Run(bsTest.bsType, func(t *testing.T) {
   143  			testGetMissing(t, bsTest.bs)
   144  		})
   145  	}
   146  }
   147  
   148  // CheckAndPutBytes is a utility method calls bs.CheckAndPut by wrapping the supplied []byte
   149  // in an io.Reader
   150  func CheckAndPutBytes(ctx context.Context, bs Blobstore, expectedVersion, key string, data []byte) (string, error) {
   151  	reader := bytes.NewReader(data)
   152  	return bs.CheckAndPut(ctx, expectedVersion, key, reader)
   153  }
   154  
   155  func testCheckAndPutError(t *testing.T, bs Blobstore) {
   156  	testData := randBytes(32)
   157  	badVersion := "bad" //has to be valid hex
   158  	_, err := CheckAndPutBytes(context.Background(), bs, badVersion, key, testData)
   159  
   160  	if err == nil {
   161  		t.Errorf("Key should be missing.")
   162  		return
   163  	} else if !IsCheckAndPutError(err) {
   164  		t.Errorf("Should have failed due to version mismatch.")
   165  		return
   166  	}
   167  
   168  	cpe, ok := err.(CheckAndPutError)
   169  
   170  	if !ok {
   171  		t.Errorf("Error is not of the expected type")
   172  	} else if cpe.Key != key || cpe.ExpectedVersion != badVersion {
   173  		t.Errorf("CheckAndPutError does not have expected values - " + cpe.Error())
   174  	}
   175  }
   176  
   177  func TestCheckAndPutError(t *testing.T) {
   178  	for _, bsTest := range newBlobStoreTests() {
   179  		t.Run(bsTest.bsType, func(t *testing.T) {
   180  			testCheckAndPutError(t, bsTest.bs)
   181  		})
   182  	}
   183  }
   184  
   185  func testCheckAndPut(t *testing.T, bs Blobstore) {
   186  	ver, err := CheckAndPutBytes(context.Background(), bs, "", key, randBytes(32))
   187  
   188  	if err != nil {
   189  		t.Errorf("Failed CheckAndPut.")
   190  	}
   191  
   192  	newVer, err := CheckAndPutBytes(context.Background(), bs, ver, key, randBytes(32))
   193  
   194  	if err != nil {
   195  		t.Errorf("Failed CheckAndPut.")
   196  	}
   197  
   198  	_, err = CheckAndPutBytes(context.Background(), bs, newVer, key, randBytes(32))
   199  
   200  	if err != nil {
   201  		t.Errorf("Failed CheckAndPut.")
   202  	}
   203  }
   204  
   205  func TestCheckAndPut(t *testing.T) {
   206  	for _, bsTest := range newBlobStoreTests() {
   207  		t.Run(bsTest.bsType, func(t *testing.T) {
   208  			testCheckAndPut(t, bsTest.bs)
   209  		})
   210  	}
   211  }
   212  
   213  func readModifyWrite(bs Blobstore, key string, iterations int, doneChan chan int) {
   214  	concurrentWrites := 0
   215  	for updates, failures := 0, 0; updates < iterations; {
   216  		if failures >= rmwRetries {
   217  			panic("Having io issues.")
   218  		}
   219  
   220  		data, ver, err := GetBytes(context.Background(), bs, key, BlobRange{})
   221  
   222  		if err != nil && !IsNotFoundError(err) {
   223  			log.Println(err)
   224  			failures++
   225  			continue
   226  		}
   227  
   228  		dataSize := len(data)
   229  		newData := make([]byte, dataSize+1)
   230  		copy(newData, data)
   231  		newData[dataSize] = byte(dataSize)
   232  
   233  		_, err = CheckAndPutBytes(context.Background(), bs, ver, key, newData)
   234  
   235  		if err == nil {
   236  			updates++
   237  			failures = 0
   238  		} else if !IsCheckAndPutError(err) {
   239  			log.Println(err)
   240  			failures++
   241  		} else {
   242  			concurrentWrites++
   243  		}
   244  	}
   245  
   246  	doneChan <- concurrentWrites
   247  }
   248  
   249  func testConcurrentCheckAndPuts(t *testing.T, bsTest BlobstoreTest, key string) {
   250  	doneChan := make(chan int)
   251  	for n := 0; n < bsTest.rmwConcurrency; n++ {
   252  		go readModifyWrite(bsTest.bs, key, bsTest.rmwIterations, doneChan)
   253  	}
   254  
   255  	totalConcurrentWrites := 0
   256  	for n := 0; n < bsTest.rmwConcurrency; n++ {
   257  		totalConcurrentWrites += <-doneChan
   258  	}
   259  
   260  	// If concurrent writes is 0 this test is pretty shitty
   261  	fmt.Println(totalConcurrentWrites, "concurrent writes occurred")
   262  
   263  	var data []byte
   264  	var err error
   265  	for i := 0; i < rmwRetries; i++ {
   266  		data, _, err = GetBytes(context.Background(), bsTest.bs, key, BlobRange{})
   267  
   268  		if err == nil {
   269  			break
   270  		}
   271  	}
   272  
   273  	if err != nil {
   274  		t.Errorf("Having IO issues testing concurrent blobstore CheckAndPuts")
   275  		return
   276  	}
   277  
   278  	if len(data) != bsTest.rmwIterations*bsTest.rmwConcurrency {
   279  		t.Errorf("Output data is not of the correct size. This is caused by bad synchronization where a read/read/write/write has occurred.")
   280  	}
   281  
   282  	for i, v := range data {
   283  		if i != int(v) {
   284  			t.Errorf("Data does not match the expected output.")
   285  		}
   286  	}
   287  }
   288  
   289  func TestConcurrentCheckAndPuts(t *testing.T) {
   290  	if runtime.GOOS == "windows" {
   291  		t.Skip("Skipping on windows due to flakiness")
   292  	}
   293  	for _, bsTest := range newBlobStoreTests() {
   294  		t.Run(bsTest.bsType, func(t *testing.T) {
   295  			if bsTest.rmwIterations*bsTest.rmwConcurrency > 255 {
   296  				panic("Test epects less than 255 total updates or it won't work as is.")
   297  			}
   298  			testConcurrentCheckAndPuts(t, bsTest, uuid.New().String())
   299  		})
   300  	}
   301  }
   302  
   303  func setupRangeTest(t *testing.T, bs Blobstore, data []byte) {
   304  	_, err := PutBytes(context.Background(), bs, key, data)
   305  
   306  	if err != nil {
   307  		t.FailNow()
   308  	}
   309  }
   310  
   311  func testGetRange(t *testing.T, bs Blobstore, br BlobRange, expected []byte) {
   312  	retrieved, _, err := GetBytes(context.Background(), bs, key, br)
   313  
   314  	if err != nil {
   315  		t.Errorf("Get failed: %v.", err)
   316  	}
   317  
   318  	if len(retrieved) != len(expected) {
   319  		t.Errorf("Range results are not the right size")
   320  		return
   321  	}
   322  
   323  	for i := 0; i < len(expected); i++ {
   324  		if retrieved[i] != expected[i] {
   325  			t.Errorf("Bad Value")
   326  			return
   327  		}
   328  	}
   329  }
   330  
   331  func rangeData(min, max int64) []byte {
   332  	if max <= min {
   333  		panic("no")
   334  	}
   335  
   336  	size := max - min
   337  	data := make([]byte, 2*size)
   338  	b := bytes.NewBuffer(data[:0])
   339  
   340  	for i := int16(min); i < int16(max); i++ {
   341  		binary.Write(b, binary.BigEndian, i)
   342  	}
   343  
   344  	return data
   345  }
   346  
   347  func TestGetRange(t *testing.T) {
   348  	maxValue := int64(16 * 1024)
   349  	testData := rangeData(0, maxValue)
   350  
   351  	tests := newBlobStoreTests()
   352  	for _, bsTest := range tests {
   353  		t.Run(bsTest.bsType, func(t *testing.T) {
   354  			setupRangeTest(t, bsTest.bs, testData)
   355  			// test full range
   356  			testGetRange(t, bsTest.bs, AllRange, rangeData(0, maxValue))
   357  			// test first 2048 bytes (1024 shorts)
   358  			testGetRange(t, bsTest.bs, NewBlobRange(0, 2048), rangeData(0, 1024))
   359  
   360  			// test range of values from 1024 to 2048 stored in bytes 2048 to 4096 of the original testData
   361  			testGetRange(t, bsTest.bs, NewBlobRange(2*1024, 2*1024), rangeData(1024, 2048))
   362  
   363  			// test the last 2048 bytes of data which will be the last 1024 shorts
   364  			testGetRange(t, bsTest.bs, NewBlobRange(-2*1024, 0), rangeData(maxValue-1024, maxValue))
   365  
   366  			// test the range beginning 2048 bytes from the end of size 512 which will be shorts 1024 from the end til 768 from the end
   367  			testGetRange(t, bsTest.bs, NewBlobRange(-2*1024, 512), rangeData(maxValue-1024, maxValue-768))
   368  		})
   369  	}
   370  }
   371  
   372  func TestPanicOnNegativeRangeLength(t *testing.T) {
   373  	defer func() {
   374  		if r := recover(); r == nil {
   375  			t.Errorf("The code did not panic")
   376  		}
   377  	}()
   378  
   379  	NewBlobRange(0, -1)
   380  }