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 }