github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/nbs/file_table_persister_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  	"context"
    26  	"crypto/rand"
    27  	"fmt"
    28  	"io/ioutil"
    29  	"os"
    30  	"path/filepath"
    31  	"testing"
    32  
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  func TestFSTableCacheOnOpen(t *testing.T) {
    38  	assert := assert.New(t)
    39  	dir := makeTempDir(t)
    40  	defer os.RemoveAll(dir)
    41  
    42  	names := []addr{}
    43  	cacheSize := 2
    44  	fc := newFDCache(cacheSize)
    45  	defer fc.Drop()
    46  	fts := newFSTablePersister(dir, fc, nil)
    47  
    48  	// Create some tables manually, load them into the cache
    49  	func() {
    50  		for i := 0; i < cacheSize; i++ {
    51  			name, err := writeTableData(dir, []byte{byte(i)})
    52  			require.NoError(t, err)
    53  			names = append(names, name)
    54  		}
    55  		for _, name := range names {
    56  			_, err := fts.Open(context.Background(), name, 1, nil)
    57  			require.NoError(t, err)
    58  		}
    59  	}()
    60  
    61  	// Tables should still be cached and on disk
    62  	for i, name := range names {
    63  		src, err := fts.Open(context.Background(), name, 1, nil)
    64  		require.NoError(t, err)
    65  		h := computeAddr([]byte{byte(i)})
    66  		assert.True(src.has(h))
    67  	}
    68  
    69  	// Kick a table out of the cache
    70  	name, err := writeTableData(dir, []byte{0xff})
    71  	require.NoError(t, err)
    72  	_, err = fts.Open(context.Background(), name, 1, nil)
    73  	require.NoError(t, err)
    74  
    75  	present := fc.reportEntries()
    76  	// Since 0 refcount entries are evicted randomly, the only thing we can validate is that fc remains at its target size
    77  	assert.Len(present, cacheSize)
    78  
    79  	err = fc.ShrinkCache()
    80  	require.NoError(t, err)
    81  	err = removeTables(dir, names...)
    82  	require.NoError(t, err)
    83  }
    84  
    85  func makeTempDir(t *testing.T) string {
    86  	dir, err := ioutil.TempDir("", "")
    87  	require.NoError(t, err)
    88  	return dir
    89  }
    90  
    91  func writeTableData(dir string, chunx ...[]byte) (addr, error) {
    92  	tableData, name, err := buildTable(chunx)
    93  
    94  	if err != nil {
    95  		return addr{}, err
    96  	}
    97  
    98  	err = ioutil.WriteFile(filepath.Join(dir, name.String()), tableData, 0666)
    99  
   100  	if err != nil {
   101  		return addr{}, err
   102  	}
   103  
   104  	return name, nil
   105  }
   106  
   107  func removeTables(dir string, names ...addr) error {
   108  	for _, name := range names {
   109  		if err := os.Remove(filepath.Join(dir, name.String())); err != nil {
   110  			return err
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  func TestFSTablePersisterPersist(t *testing.T) {
   117  	assert := assert.New(t)
   118  	dir := makeTempDir(t)
   119  	defer os.RemoveAll(dir)
   120  	fc := newFDCache(defaultMaxTables)
   121  	defer fc.Drop()
   122  	fts := newFSTablePersister(dir, fc, nil)
   123  
   124  	src, err := persistTableData(fts, testChunks...)
   125  	require.NoError(t, err)
   126  	if assert.True(mustUint32(src.count()) > 0) {
   127  		buff, err := ioutil.ReadFile(filepath.Join(dir, mustAddr(src.hash()).String()))
   128  		require.NoError(t, err)
   129  		ti, err := parseTableIndex(buff)
   130  		require.NoError(t, err)
   131  		tr := newTableReader(ti, tableReaderAtFromBytes(buff), fileBlockSize)
   132  		assertChunksInReader(testChunks, tr, assert)
   133  	}
   134  }
   135  
   136  func persistTableData(p tablePersister, chunx ...[]byte) (src chunkSource, err error) {
   137  	mt := newMemTable(testMemTableSize)
   138  	for _, c := range chunx {
   139  		if !mt.addChunk(computeAddr(c), c) {
   140  			return nil, fmt.Errorf("memTable too full to add %s", computeAddr(c))
   141  		}
   142  	}
   143  	return p.Persist(context.Background(), mt, nil, &Stats{})
   144  }
   145  
   146  func TestFSTablePersisterPersistNoData(t *testing.T) {
   147  	assert := assert.New(t)
   148  	mt := newMemTable(testMemTableSize)
   149  	existingTable := newMemTable(testMemTableSize)
   150  
   151  	for _, c := range testChunks {
   152  		assert.True(mt.addChunk(computeAddr(c), c))
   153  		assert.True(existingTable.addChunk(computeAddr(c), c))
   154  	}
   155  
   156  	dir := makeTempDir(t)
   157  	defer os.RemoveAll(dir)
   158  	fc := newFDCache(defaultMaxTables)
   159  	defer fc.Drop()
   160  	fts := newFSTablePersister(dir, fc, nil)
   161  
   162  	src, err := fts.Persist(context.Background(), mt, existingTable, &Stats{})
   163  	require.NoError(t, err)
   164  	assert.True(mustUint32(src.count()) == 0)
   165  
   166  	_, err = os.Stat(filepath.Join(dir, mustAddr(src.hash()).String()))
   167  	assert.True(os.IsNotExist(err), "%v", err)
   168  }
   169  
   170  func TestFSTablePersisterCacheOnPersist(t *testing.T) {
   171  	assert := assert.New(t)
   172  	dir := makeTempDir(t)
   173  	fc := newFDCache(1)
   174  	defer fc.Drop()
   175  	fts := newFSTablePersister(dir, fc, nil)
   176  	defer os.RemoveAll(dir)
   177  
   178  	var name addr
   179  	func() {
   180  		src, err := persistTableData(fts, testChunks...)
   181  		require.NoError(t, err)
   182  		name = mustAddr(src.hash())
   183  	}()
   184  
   185  	// Table should still be cached
   186  	src, err := fts.Open(context.Background(), name, uint32(len(testChunks)), nil)
   187  	require.NoError(t, err)
   188  	assertChunksInReader(testChunks, src, assert)
   189  
   190  	// Evict |name| from cache
   191  	_, err = persistTableData(fts, []byte{0xff})
   192  	require.NoError(t, err)
   193  
   194  	present := fc.reportEntries()
   195  	// Since 0 refcount entries are evicted randomly, the only thing we can validate is that fc remains at its target size
   196  	assert.Len(present, 1)
   197  
   198  	err = removeTables(dir, name)
   199  	require.NoError(t, err)
   200  }
   201  
   202  func TestFSTablePersisterConjoinAll(t *testing.T) {
   203  	assert := assert.New(t)
   204  	assert.True(len(testChunks) > 1, "Whoops, this test isn't meaningful")
   205  	sources := make(chunkSources, len(testChunks))
   206  
   207  	dir := makeTempDir(t)
   208  	defer os.RemoveAll(dir)
   209  	fc := newFDCache(len(sources))
   210  	defer fc.Drop()
   211  	fts := newFSTablePersister(dir, fc, nil)
   212  
   213  	for i, c := range testChunks {
   214  		randChunk := make([]byte, (i+1)*13)
   215  		_, err := rand.Read(randChunk)
   216  		require.NoError(t, err)
   217  		name, err := writeTableData(dir, c, randChunk)
   218  		require.NoError(t, err)
   219  		sources[i], err = fts.Open(context.Background(), name, 2, nil)
   220  		require.NoError(t, err)
   221  	}
   222  
   223  	src, err := fts.ConjoinAll(context.Background(), sources, &Stats{})
   224  	require.NoError(t, err)
   225  
   226  	if assert.True(mustUint32(src.count()) > 0) {
   227  		buff, err := ioutil.ReadFile(filepath.Join(dir, mustAddr(src.hash()).String()))
   228  		require.NoError(t, err)
   229  		ti, err := parseTableIndex(buff)
   230  		require.NoError(t, err)
   231  		tr := newTableReader(ti, tableReaderAtFromBytes(buff), fileBlockSize)
   232  		assertChunksInReader(testChunks, tr, assert)
   233  	}
   234  
   235  	present := fc.reportEntries()
   236  	// Since 0 refcount entries are evicted randomly, the only thing we can validate is that fc remains at its target size
   237  	assert.Len(present, len(sources))
   238  }
   239  
   240  func TestFSTablePersisterConjoinAllDups(t *testing.T) {
   241  	assert := assert.New(t)
   242  	dir := makeTempDir(t)
   243  	defer os.RemoveAll(dir)
   244  	fc := newFDCache(defaultMaxTables)
   245  	defer fc.Drop()
   246  	fts := newFSTablePersister(dir, fc, nil)
   247  
   248  	reps := 3
   249  	sources := make(chunkSources, reps)
   250  	for i := 0; i < reps; i++ {
   251  		mt := newMemTable(1 << 10)
   252  		for _, c := range testChunks {
   253  			mt.addChunk(computeAddr(c), c)
   254  		}
   255  
   256  		var err error
   257  		sources[i], err = fts.Persist(context.Background(), mt, nil, &Stats{})
   258  		require.NoError(t, err)
   259  	}
   260  
   261  	src, err := fts.ConjoinAll(context.Background(), sources, &Stats{})
   262  	require.NoError(t, err)
   263  
   264  	if assert.True(mustUint32(src.count()) > 0) {
   265  		buff, err := ioutil.ReadFile(filepath.Join(dir, mustAddr(src.hash()).String()))
   266  		require.NoError(t, err)
   267  		ti, err := parseTableIndex(buff)
   268  		require.NoError(t, err)
   269  		tr := newTableReader(ti, tableReaderAtFromBytes(buff), fileBlockSize)
   270  		assertChunksInReader(testChunks, tr, assert)
   271  		assert.EqualValues(reps*len(testChunks), mustUint32(tr.count()))
   272  	}
   273  }