github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/nbs/block_store_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 // This file incorporates work covered by the following copyright and 16 // permission notice: 17 // 18 // Copyright 2016 Attic Labs, Inc. All rights reserved. 19 // Licensed under the Apache License, version 2.0: 20 // http://www.apache.org/licenses/LICENSE-2.0 21 22 package nbs 23 24 import ( 25 "bytes" 26 "context" 27 "crypto/rand" 28 "os" 29 "path/filepath" 30 "sort" 31 "testing" 32 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 "github.com/stretchr/testify/suite" 36 37 "github.com/dolthub/dolt/go/libraries/utils/file" 38 "github.com/dolthub/dolt/go/libraries/utils/osutil" 39 "github.com/dolthub/dolt/go/store/blobstore" 40 "github.com/dolthub/dolt/go/store/chunks" 41 "github.com/dolthub/dolt/go/store/constants" 42 "github.com/dolthub/dolt/go/store/d" 43 "github.com/dolthub/dolt/go/store/hash" 44 ) 45 46 const testMemTableSize = 1 << 8 47 48 func TestLocalStoreSuite(t *testing.T) { 49 fn := func(ctx context.Context, dir string) (*NomsBlockStore, error) { 50 nbf := constants.FormatDefaultString 51 qp := NewUnlimitedMemQuotaProvider() 52 return NewLocalStore(ctx, nbf, dir, testMemTableSize, qp) 53 } 54 suite.Run(t, &BlockStoreSuite{factory: fn}) 55 } 56 57 func TestBlobstoreSuite(t *testing.T) { 58 fn := func(ctx context.Context, dir string) (*NomsBlockStore, error) { 59 nbf := constants.FormatDefaultString 60 qp := NewUnlimitedMemQuotaProvider() 61 bs := blobstore.NewLocalBlobstore(dir) 62 return NewBSStore(ctx, nbf, bs, testMemTableSize, qp) 63 } 64 suite.Run(t, &BlockStoreSuite{factory: fn}) 65 } 66 67 type BlockStoreSuite struct { 68 suite.Suite 69 dir string 70 store *NomsBlockStore 71 factory nbsFactory 72 putCountFn func() int 73 74 // if true, skip interloper tests 75 skipInterloper bool 76 } 77 78 type nbsFactory func(ctx context.Context, dir string) (*NomsBlockStore, error) 79 80 func (suite *BlockStoreSuite) SetupTest() { 81 var err error 82 suite.dir, err = os.MkdirTemp("", "") 83 suite.NoError(err) 84 ctx := context.Background() 85 suite.store, err = suite.factory(ctx, suite.dir) 86 suite.NoError(err) 87 suite.putCountFn = func() int { 88 return int(suite.store.putCount) 89 } 90 } 91 92 func (suite *BlockStoreSuite) TearDownTest() { 93 err := suite.store.Close() 94 if !osutil.IsWindowsSharingViolation(err) { 95 suite.NoError(err) 96 } 97 err = file.RemoveAll(suite.dir) 98 if !osutil.IsWindowsSharingViolation(err) { 99 suite.NoError(err) 100 } 101 } 102 103 func (suite *BlockStoreSuite) TestChunkStoreMissingDir() { 104 newDir := filepath.Join(suite.dir, "does-not-exist") 105 _, err := NewLocalStore(context.Background(), constants.FormatDefaultString, newDir, testMemTableSize, NewUnlimitedMemQuotaProvider()) 106 suite.Error(err) 107 } 108 109 func (suite *BlockStoreSuite) TestChunkStoreNotDir() { 110 existingFile := filepath.Join(suite.dir, "path-exists-but-is-a-file") 111 f, err := os.Create(existingFile) 112 suite.NoError(err) 113 defer f.Close() 114 115 _, err = NewLocalStore(context.Background(), constants.FormatDefaultString, existingFile, testMemTableSize, NewUnlimitedMemQuotaProvider()) 116 suite.Error(err) 117 } 118 119 func noopGetAddrs(c chunks.Chunk) chunks.GetAddrsCb { 120 return func(ctx context.Context, addrs hash.HashSet, _ chunks.PendingRefExists) error { 121 return nil 122 } 123 } 124 125 func (suite *BlockStoreSuite) TestChunkStorePut() { 126 input := []byte("abc") 127 c := chunks.NewChunk(input) 128 err := suite.store.Put(context.Background(), c, noopGetAddrs) 129 suite.NoError(err) 130 h := c.Hash() 131 132 // See http://www.di-mgt.com.au/sha_testvectors.html 133 suite.Equal("rmnjb8cjc5tblj21ed4qs821649eduie", h.String()) 134 135 rt, err := suite.store.Root(context.Background()) 136 suite.NoError(err) 137 success, err := suite.store.Commit(context.Background(), h, rt) // Commit writes 138 suite.NoError(err) 139 suite.True(success) 140 141 // And reading it via the API should work... 142 assertInputInStore(input, h, suite.store, suite.Assert()) 143 if suite.putCountFn != nil { 144 suite.Equal(1, suite.putCountFn()) 145 } 146 147 // Re-writing the same data should cause a second put 148 c = chunks.NewChunk(input) 149 err = suite.store.Put(context.Background(), c, noopGetAddrs) 150 suite.NoError(err) 151 suite.Equal(h, c.Hash()) 152 assertInputInStore(input, h, suite.store, suite.Assert()) 153 rt, err = suite.store.Root(context.Background()) 154 suite.NoError(err) 155 _, err = suite.store.Commit(context.Background(), h, rt) // Commit writes 156 suite.NoError(err) 157 158 if suite.putCountFn != nil { 159 suite.Equal(2, suite.putCountFn()) 160 } 161 162 // Put chunk with dangling ref should error on Commit 163 nc := chunks.NewChunk([]byte("bcd")) 164 err = suite.store.Put(context.Background(), nc, func(c chunks.Chunk) chunks.GetAddrsCb { 165 return func(ctx context.Context, addrs hash.HashSet, _ chunks.PendingRefExists) error { 166 addrs.Insert(hash.Of([]byte("lorem ipsum"))) 167 return nil 168 } 169 }) 170 suite.NoError(err) 171 root, err := suite.store.Root(context.Background()) 172 suite.NoError(err) 173 _, err = suite.store.Commit(context.Background(), root, root) 174 suite.Error(err) 175 } 176 177 func (suite *BlockStoreSuite) TestChunkStorePutMany() { 178 input1, input2 := []byte("abc"), []byte("def") 179 c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2) 180 err := suite.store.Put(context.Background(), c1, noopGetAddrs) 181 suite.NoError(err) 182 err = suite.store.Put(context.Background(), c2, noopGetAddrs) 183 suite.NoError(err) 184 185 rt, err := suite.store.Root(context.Background()) 186 suite.NoError(err) 187 success, err := suite.store.Commit(context.Background(), c1.Hash(), rt) // Commit writes 188 suite.NoError(err) 189 suite.True(success) 190 191 // And reading it via the API should work... 192 assertInputInStore(input1, c1.Hash(), suite.store, suite.Assert()) 193 assertInputInStore(input2, c2.Hash(), suite.store, suite.Assert()) 194 if suite.putCountFn != nil { 195 suite.Equal(2, suite.putCountFn()) 196 } 197 } 198 199 func (suite *BlockStoreSuite) TestChunkStoreStatsSummary() { 200 input1, input2 := []byte("abc"), []byte("def") 201 c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2) 202 err := suite.store.Put(context.Background(), c1, noopGetAddrs) 203 suite.NoError(err) 204 err = suite.store.Put(context.Background(), c2, noopGetAddrs) 205 suite.NoError(err) 206 207 rt, err := suite.store.Root(context.Background()) 208 suite.NoError(err) 209 success, err := suite.store.Commit(context.Background(), c1.Hash(), rt) // Commit writes 210 suite.True(success) 211 suite.NoError(err) 212 213 summary := suite.store.StatsSummary() 214 suite.Contains(summary, c1.Hash().String()) 215 suite.NotEqual("Unsupported", summary) 216 } 217 218 func (suite *BlockStoreSuite) TestChunkStorePutMoreThanMemTable() { 219 input1, input2 := make([]byte, testMemTableSize/2+1), make([]byte, testMemTableSize/2+1) 220 _, err := rand.Read(input1) 221 suite.NoError(err) 222 _, err = rand.Read(input2) 223 suite.NoError(err) 224 c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2) 225 err = suite.store.Put(context.Background(), c1, noopGetAddrs) 226 suite.NoError(err) 227 err = suite.store.Put(context.Background(), c2, noopGetAddrs) 228 suite.NoError(err) 229 230 rt, err := suite.store.Root(context.Background()) 231 suite.NoError(err) 232 success, err := suite.store.Commit(context.Background(), c1.Hash(), rt) // Commit writes 233 suite.NoError(err) 234 suite.True(success) 235 236 // And reading it via the API should work... 237 assertInputInStore(input1, c1.Hash(), suite.store, suite.Assert()) 238 assertInputInStore(input2, c2.Hash(), suite.store, suite.Assert()) 239 if suite.putCountFn != nil { 240 suite.Equal(2, suite.putCountFn()) 241 } 242 sz, err := suite.store.tables.physicalLen() 243 suite.NoError(err) 244 suite.True(sz > testMemTableSize) 245 } 246 247 func (suite *BlockStoreSuite) TestChunkStoreGetMany() { 248 inputs := [][]byte{make([]byte, testMemTableSize/2+1), make([]byte, testMemTableSize/2+1), []byte("abc")} 249 _, err := rand.Read(inputs[0]) 250 suite.NoError(err) 251 _, err = rand.Read(inputs[1]) 252 suite.NoError(err) 253 chnx := make([]chunks.Chunk, len(inputs)) 254 for i, data := range inputs { 255 chnx[i] = chunks.NewChunk(data) 256 err = suite.store.Put(context.Background(), chnx[i], noopGetAddrs) 257 suite.NoError(err) 258 } 259 260 rt, err := suite.store.Root(context.Background()) 261 suite.NoError(err) 262 _, err = suite.store.Commit(context.Background(), chnx[0].Hash(), rt) // Commit writes 263 suite.NoError(err) 264 265 hashes := make(hash.HashSlice, len(chnx)) 266 for i, c := range chnx { 267 hashes[i] = c.Hash() 268 } 269 270 chunkChan := make(chan *chunks.Chunk, len(hashes)) 271 err = suite.store.GetMany(context.Background(), hashes.HashSet(), func(ctx context.Context, c *chunks.Chunk) { 272 select { 273 case chunkChan <- c: 274 case <-ctx.Done(): 275 } 276 }) 277 suite.NoError(err) 278 close(chunkChan) 279 280 found := make(hash.HashSlice, 0) 281 for c := range chunkChan { 282 found = append(found, c.Hash()) 283 } 284 285 sort.Sort(found) 286 sort.Sort(hashes) 287 suite.True(found.Equals(hashes)) 288 } 289 290 func (suite *BlockStoreSuite) TestChunkStoreHasMany() { 291 chnx := []chunks.Chunk{ 292 chunks.NewChunk([]byte("abc")), 293 chunks.NewChunk([]byte("def")), 294 } 295 for _, c := range chnx { 296 err := suite.store.Put(context.Background(), c, noopGetAddrs) 297 suite.NoError(err) 298 } 299 300 rt, err := suite.store.Root(context.Background()) 301 suite.NoError(err) 302 success, err := suite.store.Commit(context.Background(), chnx[0].Hash(), rt) // Commit writes 303 suite.NoError(err) 304 suite.True(success) 305 notPresent := chunks.NewChunk([]byte("ghi")).Hash() 306 307 hashes := hash.NewHashSet(chnx[0].Hash(), chnx[1].Hash(), notPresent) 308 absent, err := suite.store.HasMany(context.Background(), hashes) 309 suite.NoError(err) 310 311 suite.Len(absent, 1) 312 for _, c := range chnx { 313 suite.False(absent.Has(c.Hash()), "%s present in %v", c.Hash(), absent) 314 } 315 suite.True(absent.Has(notPresent)) 316 } 317 318 func (suite *BlockStoreSuite) TestChunkStoreFlushOptimisticLockFail() { 319 if suite.skipInterloper { 320 suite.T().Skip() 321 } 322 input1, input2 := []byte("abc"), []byte("def") 323 c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2) 324 root, err := suite.store.Root(context.Background()) 325 suite.NoError(err) 326 327 interloper, err := suite.factory(context.Background(), suite.dir) 328 suite.NoError(err) 329 defer interloper.Close() 330 err = interloper.Put(context.Background(), c1, noopGetAddrs) 331 suite.NoError(err) 332 h, err := interloper.Root(context.Background()) 333 suite.NoError(err) 334 success, err := interloper.Commit(context.Background(), h, h) 335 suite.NoError(err) 336 suite.True(success) 337 338 err = suite.store.Put(context.Background(), c2, noopGetAddrs) 339 suite.NoError(err) 340 h, err = suite.store.Root(context.Background()) 341 suite.NoError(err) 342 success, err = suite.store.Commit(context.Background(), h, h) 343 suite.NoError(err) 344 suite.True(success) 345 346 // Reading c2 via the API should work... 347 assertInputInStore(input2, c2.Hash(), suite.store, suite.Assert()) 348 // And so should reading c1 via the API 349 assertInputInStore(input1, c1.Hash(), suite.store, suite.Assert()) 350 351 h, err = interloper.Root(context.Background()) 352 suite.NoError(err) 353 success, err = interloper.Commit(context.Background(), c1.Hash(), h) // Commit root 354 suite.NoError(err) 355 suite.True(success) 356 357 // Updating from stale root should fail... 358 success, err = suite.store.Commit(context.Background(), c2.Hash(), root) 359 suite.NoError(err) 360 suite.False(success) 361 362 // ...but new root should succeed 363 h, err = suite.store.Root(context.Background()) 364 suite.NoError(err) 365 success, err = suite.store.Commit(context.Background(), c2.Hash(), h) 366 suite.NoError(err) 367 suite.True(success) 368 } 369 370 func (suite *BlockStoreSuite) TestChunkStoreRebaseOnNoOpFlush() { 371 if suite.skipInterloper { 372 suite.T().Skip() 373 } 374 input1 := []byte("abc") 375 c1 := chunks.NewChunk(input1) 376 377 interloper, err := suite.factory(context.Background(), suite.dir) 378 suite.NoError(err) 379 defer interloper.Close() 380 err = interloper.Put(context.Background(), c1, noopGetAddrs) 381 suite.NoError(err) 382 root, err := interloper.Root(context.Background()) 383 suite.NoError(err) 384 success, err := interloper.Commit(context.Background(), c1.Hash(), root) 385 suite.NoError(err) 386 suite.True(success) 387 388 has, err := suite.store.Has(context.Background(), c1.Hash()) 389 suite.NoError(err) 390 suite.False(has) 391 392 root, err = suite.store.Root(context.Background()) 393 suite.NoError(err) 394 suite.Equal(hash.Hash{}, root) 395 396 // Should Rebase, even though there's no work to do. 397 root, err = suite.store.Root(context.Background()) 398 suite.NoError(err) 399 success, err = suite.store.Commit(context.Background(), root, root) 400 suite.NoError(err) 401 suite.True(success) 402 403 // Reading c1 via the API should work 404 assertInputInStore(input1, c1.Hash(), suite.store, suite.Assert()) 405 suite.True(suite.store.Has(context.Background(), c1.Hash())) 406 } 407 408 func (suite *BlockStoreSuite) TestChunkStorePutWithRebase() { 409 if suite.skipInterloper { 410 suite.T().Skip() 411 } 412 input1, input2 := []byte("abc"), []byte("def") 413 c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2) 414 root, err := suite.store.Root(context.Background()) 415 suite.NoError(err) 416 417 interloper, err := suite.factory(context.Background(), suite.dir) 418 suite.NoError(err) 419 defer interloper.Close() 420 err = interloper.Put(context.Background(), c1, noopGetAddrs) 421 suite.NoError(err) 422 h, err := interloper.Root(context.Background()) 423 suite.NoError(err) 424 success, err := interloper.Commit(context.Background(), h, h) 425 suite.NoError(err) 426 suite.True(success) 427 428 err = suite.store.Put(context.Background(), c2, noopGetAddrs) 429 suite.NoError(err) 430 431 // Reading c2 via the API should work pre-rebase 432 assertInputInStore(input2, c2.Hash(), suite.store, suite.Assert()) 433 // Shouldn't have c1 yet. 434 suite.False(suite.store.Has(context.Background(), c1.Hash())) 435 436 err = suite.store.Rebase(context.Background()) 437 suite.NoError(err) 438 439 // Reading c2 via the API should work post-rebase 440 assertInputInStore(input2, c2.Hash(), suite.store, suite.Assert()) 441 // And so should reading c1 via the API 442 assertInputInStore(input1, c1.Hash(), suite.store, suite.Assert()) 443 444 // Commit interloper root 445 h, err = interloper.Root(context.Background()) 446 suite.NoError(err) 447 success, err = interloper.Commit(context.Background(), c1.Hash(), h) 448 suite.NoError(err) 449 suite.True(success) 450 451 // suite.store should still have its initial root 452 h, err = suite.store.Root(context.Background()) 453 suite.NoError(err) 454 suite.EqualValues(root, h) 455 err = suite.store.Rebase(context.Background()) 456 suite.NoError(err) 457 458 // Rebase grabbed the new root, so updating should now succeed! 459 h, err = suite.store.Root(context.Background()) 460 suite.NoError(err) 461 success, err = suite.store.Commit(context.Background(), c2.Hash(), h) 462 suite.NoError(err) 463 suite.True(success) 464 465 // Interloper shouldn't see c2 yet.... 466 suite.False(interloper.Has(context.Background(), c2.Hash())) 467 err = interloper.Rebase(context.Background()) 468 suite.NoError(err) 469 // ...but post-rebase it must 470 assertInputInStore(input2, c2.Hash(), interloper, suite.Assert()) 471 } 472 473 func TestBlockStoreConjoinOnCommit(t *testing.T) { 474 t.Run("fake table persister", func(t *testing.T) { 475 testBlockStoreConjoinOnCommit(t, func(t *testing.T) tablePersister { 476 q := NewUnlimitedMemQuotaProvider() 477 return newFakeTablePersister(q) 478 }) 479 }) 480 t.Run("in memory blobstore persister", func(t *testing.T) { 481 testBlockStoreConjoinOnCommit(t, func(t *testing.T) tablePersister { 482 return &blobstorePersister{ 483 bs: blobstore.NewInMemoryBlobstore(""), 484 blockSize: 4096, 485 q: &UnlimitedQuotaProvider{}, 486 } 487 }) 488 }) 489 } 490 491 func testBlockStoreConjoinOnCommit(t *testing.T, factory func(t *testing.T) tablePersister) { 492 assertContainAll := func(t *testing.T, store chunks.ChunkStore, sources ...chunkSource) { 493 ctx := context.Background() 494 for _, src := range sources { 495 err := extractAllChunks(ctx, src, func(rec extractRecord) { 496 ok, err := store.Has(context.Background(), hash.Hash(rec.a)) 497 require.NoError(t, err) 498 assert.True(t, ok, "chunk %s from chunkSource %s not found in store", 499 rec.a.String(), src.hash().String()) 500 }) 501 require.NoError(t, err) 502 } 503 } 504 505 makeManifestManager := func(m manifest) manifestManager { 506 return manifestManager{m, newManifestCache(0), newManifestLocks()} 507 } 508 509 newChunk := chunks.NewChunk([]byte("gnu")) 510 511 t.Run("NoConjoin", func(t *testing.T) { 512 mm := makeManifestManager(&fakeManifest{}) 513 q := NewUnlimitedMemQuotaProvider() 514 defer func() { 515 require.EqualValues(t, 0, q.Usage()) 516 }() 517 p := factory(t) 518 519 c := &fakeConjoiner{} 520 521 smallTableStore, err := newNomsBlockStore(context.Background(), constants.FormatDefaultString, mm, p, q, c, testMemTableSize) 522 require.NoError(t, err) 523 defer smallTableStore.Close() 524 525 root, err := smallTableStore.Root(context.Background()) 526 require.NoError(t, err) 527 err = smallTableStore.Put(context.Background(), newChunk, noopGetAddrs) 528 require.NoError(t, err) 529 success, err := smallTableStore.Commit(context.Background(), newChunk.Hash(), root) 530 require.NoError(t, err) 531 assert.True(t, success) 532 533 ok, err := smallTableStore.Has(context.Background(), newChunk.Hash()) 534 require.NoError(t, err) 535 assert.True(t, ok) 536 }) 537 538 t.Run("ConjoinSuccess", func(t *testing.T) { 539 q := NewUnlimitedMemQuotaProvider() 540 fm := &fakeManifest{} 541 p := factory(t) 542 543 srcs := makeTestSrcs(t, []uint32{1, 1, 3, 7}, p) 544 upstream, err := toSpecs(srcs) 545 require.NoError(t, err) 546 fm.set(constants.FormatLD1String, computeAddr([]byte{0xbe}), hash.Of([]byte{0xef}), upstream, nil) 547 c := &fakeConjoiner{ 548 []cannedConjoin{ 549 {conjoinees: upstream[:2], keepers: upstream[2:]}, 550 }, 551 } 552 553 smallTableStore, err := newNomsBlockStore(context.Background(), constants.FormatDefaultString, makeManifestManager(fm), p, q, c, testMemTableSize) 554 require.NoError(t, err) 555 defer smallTableStore.Close() 556 557 root, err := smallTableStore.Root(context.Background()) 558 require.NoError(t, err) 559 err = smallTableStore.Put(context.Background(), newChunk, noopGetAddrs) 560 require.NoError(t, err) 561 success, err := smallTableStore.Commit(context.Background(), newChunk.Hash(), root) 562 require.NoError(t, err) 563 assert.True(t, success) 564 ok, err := smallTableStore.Has(context.Background(), newChunk.Hash()) 565 require.NoError(t, err) 566 assert.True(t, ok) 567 assertContainAll(t, smallTableStore, srcs...) 568 for _, src := range srcs { 569 err := src.close() 570 require.NoError(t, err) 571 } 572 }) 573 574 t.Run("ConjoinRetry", func(t *testing.T) { 575 fm := &fakeManifest{} 576 q := NewUnlimitedMemQuotaProvider() 577 p := factory(t) 578 579 srcs := makeTestSrcs(t, []uint32{1, 1, 3, 7, 13}, p) 580 upstream, err := toSpecs(srcs) 581 require.NoError(t, err) 582 fm.set(constants.FormatLD1String, computeAddr([]byte{0xbe}), hash.Of([]byte{0xef}), upstream, nil) 583 c := &fakeConjoiner{ 584 []cannedConjoin{ 585 {conjoinees: upstream[:2], keepers: upstream[2:]}, 586 {conjoinees: upstream[:4], keepers: upstream[4:]}, 587 }, 588 } 589 590 smallTableStore, err := newNomsBlockStore(context.Background(), constants.FormatDefaultString, makeManifestManager(fm), p, q, c, testMemTableSize) 591 require.NoError(t, err) 592 defer smallTableStore.Close() 593 594 root, err := smallTableStore.Root(context.Background()) 595 require.NoError(t, err) 596 err = smallTableStore.Put(context.Background(), newChunk, noopGetAddrs) 597 require.NoError(t, err) 598 success, err := smallTableStore.Commit(context.Background(), newChunk.Hash(), root) 599 require.NoError(t, err) 600 assert.True(t, success) 601 ok, err := smallTableStore.Has(context.Background(), newChunk.Hash()) 602 require.NoError(t, err) 603 assert.True(t, ok) 604 assertContainAll(t, smallTableStore, srcs...) 605 for _, src := range srcs { 606 err := src.close() 607 require.NoError(t, err) 608 } 609 }) 610 } 611 612 type cannedConjoin struct { 613 // Must name tables that are already persisted 614 conjoinees, keepers []tableSpec 615 } 616 617 type fakeConjoiner struct { 618 canned []cannedConjoin 619 } 620 621 func (fc *fakeConjoiner) conjoinRequired(ts tableSet) bool { 622 if len(fc.canned) == 0 { 623 return false 624 } 625 return true 626 } 627 628 func (fc *fakeConjoiner) chooseConjoinees(specs []tableSpec) (conjoinees, keepers []tableSpec, err error) { 629 d.PanicIfTrue(len(fc.canned) == 0) 630 cur := fc.canned[0] 631 fc.canned = fc.canned[1:] 632 conjoinees, keepers = cur.conjoinees, cur.keepers 633 return 634 } 635 636 func assertInputInStore(input []byte, h hash.Hash, s chunks.ChunkStore, assert *assert.Assertions) { 637 ctx := context.Background() 638 c, err := s.Get(ctx, h) 639 assert.NoError(err) 640 if c.IsEmpty() { 641 c, err = s.Get(ctx, h) 642 } 643 assert.False(c.IsEmpty(), "Shouldn't get empty chunk for %s", h.String()) 644 assert.Zero(bytes.Compare(input, c.Data()), "%s != %s", string(input), string(c.Data())) 645 } 646 647 func (suite *BlockStoreSuite) TestChunkStoreGetNonExisting() { 648 h := hash.Parse("11111111111111111111111111111111") 649 c, err := suite.store.Get(context.Background(), h) 650 suite.NoError(err) 651 suite.True(c.IsEmpty()) 652 }