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 }