github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/data/bcache_test.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package data
     6  
     7  import (
     8  	"crypto/rand"
     9  	"testing"
    10  
    11  	"github.com/keybase/client/go/kbfs/kbfsblock"
    12  	"github.com/keybase/client/go/kbfs/kbfshash"
    13  	"github.com/keybase/client/go/kbfs/tlf"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func blockCacheTestInit(t *testing.T, capacity int,
    18  	bytesCapacity uint64) *BlockCacheStandard {
    19  	return NewBlockCacheStandard(capacity, bytesCapacity)
    20  }
    21  
    22  func testBcachePutWithBlock(t *testing.T, id kbfsblock.ID, bcache BlockCache, lifetime BlockCacheLifetime, block Block) {
    23  	ptr := BlockPointer{ID: id}
    24  	tlf := tlf.FakeID(1, tlf.Private)
    25  
    26  	// put the block
    27  	err := bcache.Put(ptr, tlf, block, lifetime, SkipCacheHash)
    28  	require.NoError(t, err)
    29  
    30  	// make sure we can get it successfully
    31  	block2, err := bcache.Get(ptr)
    32  	require.NoError(t, err)
    33  	require.Equal(t, block, block2)
    34  }
    35  
    36  func testBcachePut(t *testing.T, id kbfsblock.ID, bcache BlockCache, lifetime BlockCacheLifetime) {
    37  	block := NewFileBlock()
    38  	testBcachePutWithBlock(t, id, bcache, lifetime, block)
    39  }
    40  
    41  func testExpectedMissing(t *testing.T, id kbfsblock.ID, bcache BlockCache) {
    42  	expectedErr := NoSuchBlockError{id}
    43  	ptr := BlockPointer{ID: id}
    44  	_, err := bcache.Get(ptr)
    45  	require.EqualError(t, err, expectedErr.Error())
    46  }
    47  
    48  func TestBlockCachePut(t *testing.T) {
    49  	bcache := blockCacheTestInit(t, 100, 1<<30)
    50  	testBcachePut(t, kbfsblock.FakeID(1), bcache, TransientEntry)
    51  	testBcachePut(t, kbfsblock.FakeID(2), bcache, PermanentEntry)
    52  }
    53  
    54  func TestBlockCachePutPastCapacity(t *testing.T) {
    55  	bcache := blockCacheTestInit(t, 2, 1<<30)
    56  	id1 := kbfsblock.FakeID(1)
    57  	testBcachePut(t, id1, bcache, TransientEntry)
    58  	id2 := kbfsblock.FakeID(2)
    59  	testBcachePut(t, id2, bcache, TransientEntry)
    60  	testBcachePut(t, kbfsblock.FakeID(3), bcache, TransientEntry)
    61  
    62  	// now block 1 should have been kicked out
    63  	testExpectedMissing(t, id1, bcache)
    64  
    65  	// but 2 should still be there
    66  	_, err := bcache.Get(BlockPointer{ID: id2})
    67  	require.NoError(t, err)
    68  
    69  	// permanent blocks don't count
    70  	testBcachePut(t, kbfsblock.FakeID(4), bcache, PermanentEntry)
    71  }
    72  
    73  func TestBlockCacheCheckPtrSuccess(t *testing.T) {
    74  	bcache := blockCacheTestInit(t, 100, 1<<30)
    75  
    76  	block := NewFileBlock().(*FileBlock)
    77  	block.Contents = []byte{1, 2, 3, 4}
    78  	id := kbfsblock.FakeID(1)
    79  	ptr := BlockPointer{ID: id}
    80  	tlf := tlf.FakeID(1, tlf.Private)
    81  
    82  	err := bcache.Put(ptr, tlf, block, TransientEntry, DoCacheHash)
    83  	require.NoError(t, err)
    84  
    85  	checkedPtr, err := bcache.CheckForKnownPtr(tlf, block, DoCacheHash)
    86  	require.NoError(t, err)
    87  	require.Equal(t, ptr, checkedPtr)
    88  }
    89  
    90  func TestBlockCacheCheckPtrPermanent(t *testing.T) {
    91  	bcache := blockCacheTestInit(t, 100, 1<<30)
    92  
    93  	block := NewFileBlock().(*FileBlock)
    94  	block.Contents = []byte{1, 2, 3, 4}
    95  	id := kbfsblock.FakeID(1)
    96  	ptr := BlockPointer{ID: id}
    97  	tlf := tlf.FakeID(1, tlf.Private)
    98  
    99  	err := bcache.Put(ptr, tlf, block, PermanentEntry, SkipCacheHash)
   100  	require.NoError(t, err)
   101  
   102  	checkedPtr, err := bcache.CheckForKnownPtr(tlf, block, DoCacheHash)
   103  	require.NoError(t, err)
   104  	require.Equal(t, BlockPointer{}, checkedPtr)
   105  }
   106  
   107  func TestBlockCacheCheckPtrNotFound(t *testing.T) {
   108  	bcache := blockCacheTestInit(t, 100, 1<<30)
   109  
   110  	block := NewFileBlock().(*FileBlock)
   111  	block.Contents = []byte{1, 2, 3, 4}
   112  	id := kbfsblock.FakeID(1)
   113  	ptr := BlockPointer{ID: id}
   114  	tlf := tlf.FakeID(1, tlf.Private)
   115  
   116  	err := bcache.Put(ptr, tlf, block, TransientEntry, DoCacheHash)
   117  	require.NoError(t, err)
   118  
   119  	block2 := NewFileBlock().(*FileBlock)
   120  	block2.Contents = []byte{4, 3, 2, 1}
   121  	checkedPtr, err := bcache.CheckForKnownPtr(tlf, block2, DoCacheHash)
   122  	require.NoError(t, err)
   123  	require.False(t, checkedPtr.IsInitialized())
   124  }
   125  
   126  func TestBlockCacheDeleteTransient(t *testing.T) {
   127  	bcache := blockCacheTestInit(t, 100, 1<<30)
   128  
   129  	block := NewFileBlock().(*FileBlock)
   130  	block.Contents = []byte{1, 2, 3, 4}
   131  	id := kbfsblock.FakeID(1)
   132  	ptr := BlockPointer{ID: id}
   133  	tlf := tlf.FakeID(1, tlf.Private)
   134  
   135  	err := bcache.Put(ptr, tlf, block, TransientEntry, DoCacheHash)
   136  	require.NoError(t, err)
   137  
   138  	err = bcache.DeleteTransient(ptr.ID, tlf)
   139  	require.NoError(t, err)
   140  
   141  	// Make sure the pointer is gone from the hash cache too.
   142  	checkedPtr, err := bcache.CheckForKnownPtr(tlf, block, DoCacheHash)
   143  	require.NoError(t, err)
   144  	require.False(t, checkedPtr.IsInitialized())
   145  }
   146  
   147  func TestBlockCacheDeletePermanent(t *testing.T) {
   148  	bcache := blockCacheTestInit(t, 100, 1<<30)
   149  
   150  	id1 := kbfsblock.FakeID(1)
   151  	testBcachePut(t, id1, bcache, PermanentEntry)
   152  
   153  	id2 := kbfsblock.FakeID(2)
   154  	block2 := NewFileBlock()
   155  	testBcachePutWithBlock(t, id2, bcache, TransientEntry, block2)
   156  	testBcachePutWithBlock(t, id2, bcache, PermanentEntry, block2)
   157  
   158  	err := bcache.DeletePermanent(id1)
   159  	require.NoError(t, err)
   160  	err = bcache.DeletePermanent(id2)
   161  	require.NoError(t, err)
   162  	testExpectedMissing(t, id1, bcache)
   163  
   164  	// 2 should still be there
   165  	_, err = bcache.Get(BlockPointer{ID: id2})
   166  	require.NoError(t, err)
   167  }
   168  
   169  func TestBlockCacheEmptyTransient(t *testing.T) {
   170  	bcache := blockCacheTestInit(t, 0, 1<<30)
   171  
   172  	block := NewFileBlock()
   173  	id := kbfsblock.FakeID(1)
   174  	ptr := BlockPointer{ID: id}
   175  	tlf := tlf.FakeID(1, tlf.Private)
   176  
   177  	// Make sure all the operations work even if the cache has no
   178  	// transient capacity.
   179  
   180  	err := bcache.Put(ptr, tlf, block, TransientEntry, DoCacheHash)
   181  	require.NoError(t, err)
   182  
   183  	_, err = bcache.Get(ptr)
   184  	require.EqualError(t, err, NoSuchBlockError{ptr.ID}.Error())
   185  
   186  	err = bcache.DeletePermanent(id)
   187  	require.NoError(t, err)
   188  
   189  	_, err = bcache.CheckForKnownPtr(tlf, block.(*FileBlock), DoCacheHash)
   190  	require.NoError(t, err)
   191  }
   192  
   193  func TestBlockCacheEvictOnBytes(t *testing.T) {
   194  	// Make a cache that can only handle 5 bytes
   195  	bcache := blockCacheTestInit(t, 1000, 5)
   196  
   197  	tlf := tlf.FakeID(1, tlf.Private)
   198  	for i := byte(0); i < 8; i++ {
   199  		block := &FileBlock{
   200  			Contents: make([]byte, 1),
   201  		}
   202  		id := kbfsblock.FakeID(i)
   203  		ptr := BlockPointer{ID: id}
   204  
   205  		err := bcache.Put(ptr, tlf, block, TransientEntry, SkipCacheHash)
   206  		require.NoError(t, err)
   207  	}
   208  
   209  	// Only blocks 3 through 7 should be left
   210  	for i := byte(0); i < 3; i++ {
   211  		id := kbfsblock.FakeID(i)
   212  		testExpectedMissing(t, id, bcache)
   213  	}
   214  
   215  	for i := byte(3); i < 8; i++ {
   216  		id := kbfsblock.FakeID(i)
   217  		_, err := bcache.Get(BlockPointer{ID: id})
   218  		require.NoError(t, err)
   219  	}
   220  }
   221  
   222  func TestBlockCacheEvictIncludesPermanentSize(t *testing.T) {
   223  	// Make a cache that can only handle 5 bytes
   224  	bcache := blockCacheTestInit(t, 1000, 5)
   225  
   226  	tlf := tlf.FakeID(1, tlf.Private)
   227  	idPerm := kbfsblock.FakeID(0)
   228  	ptr := BlockPointer{ID: idPerm}
   229  	block := &FileBlock{
   230  		Contents: make([]byte, 2),
   231  	}
   232  	err := bcache.Put(ptr, tlf, block, PermanentEntry, SkipCacheHash)
   233  	require.NoError(t, err)
   234  
   235  	for i := byte(1); i < 8; i++ {
   236  		block := &FileBlock{
   237  			Contents: make([]byte, 1),
   238  		}
   239  		id := kbfsblock.FakeID(i)
   240  		ptr := BlockPointer{ID: id}
   241  
   242  		err := bcache.Put(ptr, tlf, block, TransientEntry, SkipCacheHash)
   243  		require.NoError(t, err)
   244  	}
   245  
   246  	// The permanent block shouldn't be evicted
   247  	_, err = bcache.Get(BlockPointer{ID: idPerm})
   248  	require.NoError(t, err)
   249  
   250  	// Only transient blocks 5 through 7 should be left
   251  	for i := byte(1); i < 5; i++ {
   252  		id := kbfsblock.FakeID(i)
   253  		testExpectedMissing(t, id, bcache)
   254  	}
   255  
   256  	for i := byte(5); i < 8; i++ {
   257  		id := kbfsblock.FakeID(i)
   258  		_, err := bcache.Get(BlockPointer{ID: id})
   259  		require.NoError(t, err)
   260  	}
   261  
   262  	// Try putting in a block that's too big
   263  	block = &FileBlock{
   264  		CommonBlock: CommonBlock{IsInd: true},
   265  	}
   266  	block.SetEncodedSize(7)
   267  	id := kbfsblock.FakeID(8)
   268  	ptr = BlockPointer{ID: id}
   269  	err = bcache.Put(ptr, tlf, block, TransientEntry, SkipCacheHash)
   270  	require.EqualError(t, err, CachePutCacheFullError{ptr.ID}.Error())
   271  
   272  	// All transient blocks should be gone (including the new one)
   273  	_, err = bcache.Get(BlockPointer{ID: idPerm})
   274  	require.NoError(t, err)
   275  
   276  	// Only transient blocks 5 through 7 should be left
   277  	for i := byte(1); i < 9; i++ {
   278  		id := kbfsblock.FakeID(i)
   279  		testExpectedMissing(t, id, bcache)
   280  	}
   281  
   282  	// Now try putting in a permanent block that exceeds capacity,
   283  	// which should always succeed.
   284  	idPerm2 := kbfsblock.FakeID(9)
   285  	ptr2 := BlockPointer{ID: idPerm2}
   286  	block2 := &FileBlock{
   287  		Contents: make([]byte, 10),
   288  	}
   289  	err = bcache.Put(ptr2, tlf, block2, PermanentEntry, SkipCacheHash)
   290  	require.NoError(t, err)
   291  
   292  	_, err = bcache.Get(BlockPointer{ID: idPerm})
   293  	require.NoError(t, err)
   294  	_, err = bcache.Get(BlockPointer{ID: idPerm2})
   295  	require.NoError(t, err)
   296  }
   297  
   298  func TestBlockCachePutNoHashCalculation(t *testing.T) {
   299  	bcache := blockCacheTestInit(t, 100, 1<<30)
   300  	ptr := BlockPointer{ID: kbfsblock.FakeID(1)}
   301  	tlf := tlf.FakeID(1, tlf.Private)
   302  	block := NewFileBlock().(*FileBlock)
   303  	block.Contents = []byte{1, 2, 3, 4}
   304  
   305  	// this is an invalid hash; if Put() does not calculate hash, it should go
   306  	// into the cache
   307  	hash := &kbfshash.RawDefaultHash{}
   308  	block.hash = hash
   309  	err := bcache.Put(ptr, tlf, block, TransientEntry, DoCacheHash)
   310  	require.NoError(t, err)
   311  
   312  	// CheckForKnownPtr() calculates hash only if it's nil. If the block with
   313  	// invalid hash was put into cache, this will find it.
   314  	checkedPtr, err := bcache.CheckForKnownPtr(tlf, block, DoCacheHash)
   315  	require.NoError(t, err)
   316  	require.Equal(t, ptr, checkedPtr)
   317  	require.Equal(t, hash, block.hash)
   318  }
   319  
   320  func TestBlockCacheDoublePut(t *testing.T) {
   321  	cache := blockCacheTestInit(t, 1, 1<<30)
   322  	id1 := kbfsblock.FakeID(1)
   323  	id2 := kbfsblock.FakeID(2)
   324  	buf := make([]byte, 16)
   325  	_, err := rand.Read(buf)
   326  	require.NoError(t, err)
   327  	block := &FileBlock{
   328  		Contents: buf,
   329  	}
   330  	bytes := uint64(len(block.Contents))
   331  
   332  	t.Log("Put a block into the cache. Check that the cache calculated its byte usage correctly.")
   333  	testBcachePutWithBlock(t, id1, cache, TransientEntry, block)
   334  	require.Equal(t, bytes, cache.cleanTotalBytes)
   335  
   336  	t.Log("Put the same block into the cache. Check that the cache's byte usage hasn't changed.")
   337  	testBcachePutWithBlock(t, id1, cache, TransientEntry, block)
   338  	require.Equal(t, bytes, cache.cleanTotalBytes)
   339  
   340  	t.Log("Put a new block into the cache, evicting the first one. Check that the byte usage is updated correctly.")
   341  	block = NewFileBlock().(*FileBlock)
   342  	block.Contents = []byte{1, 2, 3, 4, 5}
   343  	bytes = uint64(len(block.Contents))
   344  	testBcachePutWithBlock(t, id2, cache, TransientEntry, block)
   345  	require.Equal(t, bytes, cache.cleanTotalBytes)
   346  }