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  }