github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/internal/cache/cache_test.go (about) 1 // Copyright 2022 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cache_test 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "math" 12 "os" 13 "sync" 14 "testing" 15 "time" 16 17 storage "github.com/ethersphere/bee/v2/pkg/storage" 18 "github.com/ethersphere/bee/v2/pkg/storage/inmemchunkstore" 19 "github.com/ethersphere/bee/v2/pkg/storage/inmemstore" 20 "github.com/ethersphere/bee/v2/pkg/storage/storagetest" 21 chunktest "github.com/ethersphere/bee/v2/pkg/storage/testing" 22 "github.com/ethersphere/bee/v2/pkg/storer/internal/cache" 23 "github.com/ethersphere/bee/v2/pkg/storer/internal/transaction" 24 "github.com/ethersphere/bee/v2/pkg/swarm" 25 "github.com/google/go-cmp/cmp" 26 ) 27 28 func TestCacheEntryItem(t *testing.T) { 29 t.Parallel() 30 31 tests := []struct { 32 name string 33 test *storagetest.ItemMarshalAndUnmarshalTest 34 }{{ 35 name: "zero address", 36 test: &storagetest.ItemMarshalAndUnmarshalTest{ 37 Item: &cache.CacheEntry{}, 38 Factory: func() storage.Item { return new(cache.CacheEntry) }, 39 MarshalErr: cache.ErrMarshalCacheEntryInvalidAddress, 40 }, 41 }, { 42 name: "zero values", 43 test: &storagetest.ItemMarshalAndUnmarshalTest{ 44 Item: &cache.CacheEntry{ 45 Address: swarm.NewAddress(storagetest.MaxAddressBytes[:]), 46 }, 47 Factory: func() storage.Item { return new(cache.CacheEntry) }, 48 MarshalErr: cache.ErrMarshalCacheEntryInvalidTimestamp, 49 }, 50 }, { 51 name: "max values", 52 test: &storagetest.ItemMarshalAndUnmarshalTest{ 53 Item: &cache.CacheEntry{ 54 Address: swarm.NewAddress(storagetest.MaxAddressBytes[:]), 55 AccessTimestamp: math.MaxInt64, 56 }, 57 Factory: func() storage.Item { return new(cache.CacheEntry) }, 58 }, 59 }, { 60 name: "invalid size", 61 test: &storagetest.ItemMarshalAndUnmarshalTest{ 62 Item: &storagetest.ItemStub{ 63 MarshalBuf: []byte{0xFF}, 64 UnmarshalBuf: []byte{0xFF}, 65 }, 66 Factory: func() storage.Item { return new(cache.CacheEntry) }, 67 UnmarshalErr: cache.ErrUnmarshalCacheEntryInvalidSize, 68 }, 69 }} 70 71 for _, tc := range tests { 72 tc := tc 73 74 t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) { 75 t.Parallel() 76 77 storagetest.TestItemMarshalAndUnmarshal(t, tc.test) 78 }) 79 80 t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) { 81 t.Parallel() 82 83 storagetest.TestItemClone(t, &storagetest.ItemCloneTest{ 84 Item: tc.test.Item, 85 CmpOpts: tc.test.CmpOpts, 86 }) 87 }) 88 } 89 } 90 91 type timeProvider struct { 92 t int64 93 mtx sync.Mutex 94 } 95 96 func (t *timeProvider) Now() func() time.Time { 97 return func() time.Time { 98 t.mtx.Lock() 99 defer t.mtx.Unlock() 100 t.t++ 101 return time.Unix(0, t.t) 102 } 103 } 104 105 func TestMain(m *testing.M) { 106 p := &timeProvider{t: time.Now().UnixNano()} 107 done := cache.ReplaceTimeNow(p.Now()) 108 defer func() { 109 done() 110 }() 111 code := m.Run() 112 os.Exit(code) 113 } 114 115 func TestCache(t *testing.T) { 116 t.Parallel() 117 118 t.Run("fresh new cache", func(t *testing.T) { 119 t.Parallel() 120 121 st := newTestStorage(t) 122 c, err := cache.New(context.TODO(), st.IndexStore(), 10) 123 if err != nil { 124 t.Fatal(err) 125 } 126 verifyCacheState(t, st.IndexStore(), c, swarm.ZeroAddress, swarm.ZeroAddress, 0) 127 }) 128 129 t.Run("putter", func(t *testing.T) { 130 t.Parallel() 131 132 st := newTestStorage(t) 133 c, err := cache.New(context.TODO(), st.IndexStore(), 10) 134 if err != nil { 135 t.Fatal(err) 136 } 137 138 chunks := chunktest.GenerateTestRandomChunks(10) 139 140 t.Run("add till full", func(t *testing.T) { 141 for idx, ch := range chunks { 142 err := c.Putter(st).Put(context.TODO(), ch) 143 if err != nil { 144 t.Fatal(err) 145 } 146 verifyCacheState(t, st.IndexStore(), c, chunks[0].Address(), chunks[idx].Address(), uint64(idx+1)) 147 verifyCacheOrder(t, c, st.IndexStore(), chunks[:idx+1]...) 148 } 149 }) 150 151 t.Run("new cache retains state", func(t *testing.T) { 152 c2, err := cache.New(context.TODO(), st.IndexStore(), 10) 153 if err != nil { 154 t.Fatal(err) 155 } 156 verifyCacheState(t, st.IndexStore(), c2, chunks[0].Address(), chunks[len(chunks)-1].Address(), uint64(len(chunks))) 157 verifyCacheOrder(t, c2, st.IndexStore(), chunks...) 158 }) 159 }) 160 161 t.Run("getter", func(t *testing.T) { 162 t.Parallel() 163 164 st := newTestStorage(t) 165 c, err := cache.New(context.TODO(), st.IndexStore(), 10) 166 if err != nil { 167 t.Fatal(err) 168 } 169 170 chunks := chunktest.GenerateTestRandomChunks(10) 171 172 // this should have no effect on ordering 173 t.Run("add and get last", func(t *testing.T) { 174 for idx, ch := range chunks { 175 err := c.Putter(st).Put(context.TODO(), ch) 176 if err != nil { 177 t.Fatal(err) 178 } 179 180 readChunk, err := c.Getter(st).Get(context.TODO(), ch.Address()) 181 if err != nil { 182 t.Fatal(err) 183 } 184 if !readChunk.Equal(ch) { 185 t.Fatalf("incorrect chunk: %s", ch.Address()) 186 } 187 verifyCacheState(t, st.IndexStore(), c, chunks[0].Address(), chunks[idx].Address(), uint64(idx+1)) 188 verifyCacheOrder(t, c, st.IndexStore(), chunks[:idx+1]...) 189 } 190 }) 191 192 // getting the chunks in reverse order should reverse the ordering in the cache 193 // at the end 194 t.Run("get reverse order", func(t *testing.T) { 195 var newOrder []swarm.Chunk 196 for idx := len(chunks) - 1; idx >= 0; idx-- { 197 readChunk, err := c.Getter(st).Get(context.TODO(), chunks[idx].Address()) 198 if err != nil { 199 t.Fatal(err) 200 } 201 if !readChunk.Equal(chunks[idx]) { 202 t.Fatalf("incorrect chunk: %s", chunks[idx].Address()) 203 } 204 if idx == 0 { 205 // once we access the first entry, the top will change 206 verifyCacheState(t, st.IndexStore(), c, chunks[9].Address(), chunks[idx].Address(), 10) 207 } else { 208 verifyCacheState(t, st.IndexStore(), c, chunks[0].Address(), chunks[idx].Address(), 10) 209 } 210 newOrder = append(newOrder, chunks[idx]) 211 } 212 verifyCacheOrder(t, c, st.IndexStore(), newOrder...) 213 }) 214 215 t.Run("not in chunkstore returns error", func(t *testing.T) { 216 for i := 0; i < 5; i++ { 217 unknownChunk := chunktest.GenerateTestRandomChunk() 218 _, err := c.Getter(st).Get(context.TODO(), unknownChunk.Address()) 219 if !errors.Is(err, storage.ErrNotFound) { 220 t.Fatalf("expected error not found for chunk %s", unknownChunk.Address()) 221 } 222 } 223 }) 224 225 t.Run("not in cache doesn't affect state", func(t *testing.T) { 226 state := c.State(st.IndexStore()) 227 228 for i := 0; i < 5; i++ { 229 extraChunk := chunktest.GenerateTestRandomChunk() 230 err := st.Run(context.Background(), func(s transaction.Store) error { 231 return s.ChunkStore().Put(context.TODO(), extraChunk) 232 }) 233 if err != nil { 234 t.Fatal(err) 235 } 236 237 readChunk, err := c.Getter(st).Get(context.TODO(), extraChunk.Address()) 238 if err != nil { 239 t.Fatal(err) 240 } 241 if !readChunk.Equal(extraChunk) { 242 t.Fatalf("incorrect chunk: %s", extraChunk.Address()) 243 } 244 verifyCacheState(t, st.IndexStore(), c, state.Head, state.Tail, state.Size) 245 } 246 }) 247 248 t.Run("handle error", func(t *testing.T) { 249 t.Parallel() 250 251 st := newTestStorage(t) 252 c, err := cache.New(context.TODO(), st.IndexStore(), 10) 253 if err != nil { 254 t.Fatal(err) 255 } 256 257 chunks := chunktest.GenerateTestRandomChunks(5) 258 259 for _, ch := range chunks { 260 err := c.Putter(st).Put(context.TODO(), ch) 261 if err != nil { 262 t.Fatal(err) 263 } 264 } 265 // return error for state update, which occurs at the end of Get/Put operations 266 retErr := errors.New("dummy error") 267 268 st.indexStore.putFn = func(i storage.Item) error { 269 if i.Namespace() == "cacheOrderIndex" { 270 return retErr 271 } 272 273 return st.indexStore.IndexStore.Put(i) 274 } 275 276 // on error the cache expects the overarching transactions to clean itself up 277 // and undo any store updates. So here we only want to ensure the state is 278 // reverted to correct one. 279 t.Run("put error handling", func(t *testing.T) { 280 newChunk := chunktest.GenerateTestRandomChunk() 281 err := c.Putter(st).Put(context.TODO(), newChunk) 282 if !errors.Is(err, retErr) { 283 t.Fatalf("expected error %v during put, found %v", retErr, err) 284 } 285 286 // state should be preserved on failure 287 verifyCacheState(t, st.IndexStore(), c, chunks[0].Address(), chunks[4].Address(), 5) 288 }) 289 290 t.Run("get error handling", func(t *testing.T) { 291 _, err := c.Getter(st).Get(context.TODO(), chunks[2].Address()) 292 if !errors.Is(err, retErr) { 293 t.Fatalf("expected error %v during get, found %v", retErr, err) 294 } 295 296 // state should be preserved on failure 297 verifyCacheState(t, st.IndexStore(), c, chunks[0].Address(), chunks[4].Address(), 5) 298 }) 299 }) 300 }) 301 } 302 303 func TestRemoveOldest(t *testing.T) { 304 t.Parallel() 305 306 st := newTestStorage(t) 307 c, err := cache.New(context.Background(), st.IndexStore(), 10) 308 if err != nil { 309 t.Fatal(err) 310 } 311 312 chunks := chunktest.GenerateTestRandomChunks(30) 313 314 for _, ch := range chunks { 315 err = c.Putter(st).Put(context.Background(), ch) 316 if err != nil { 317 t.Fatal(err) 318 } 319 } 320 321 verifyCacheState(t, st.IndexStore(), c, chunks[0].Address(), chunks[29].Address(), 30) 322 verifyCacheOrder(t, c, st.IndexStore(), chunks...) 323 324 err = c.RemoveOldestMaxBatch(context.Background(), st, 30, 5) 325 if err != nil { 326 t.Fatal(err) 327 } 328 329 verifyCacheState(t, st.IndexStore(), c, swarm.ZeroAddress, swarm.ZeroAddress, 0) 330 331 verifyChunksDeleted(t, st.ChunkStore(), chunks...) 332 } 333 334 func TestShallowCopy(t *testing.T) { 335 t.Parallel() 336 337 st := newTestStorage(t) 338 c, err := cache.New(context.Background(), st.IndexStore(), 10) 339 if err != nil { 340 t.Fatal(err) 341 } 342 343 chunks := chunktest.GenerateTestRandomChunks(10) 344 chunksToMove := make([]swarm.Address, 0, 10) 345 346 // add the chunks to chunkstore. This simulates the reserve already populating 347 // the chunkstore with chunks. 348 for _, ch := range chunks { 349 350 err := st.Run(context.Background(), func(s transaction.Store) error { 351 return s.ChunkStore().Put(context.Background(), ch) 352 }) 353 if err != nil { 354 t.Fatal(err) 355 } 356 chunksToMove = append(chunksToMove, ch.Address()) 357 } 358 359 err = c.ShallowCopy(context.Background(), st, chunksToMove...) 360 if err != nil { 361 t.Fatal(err) 362 } 363 364 verifyCacheState(t, st.IndexStore(), c, chunks[0].Address(), chunks[9].Address(), 10) 365 verifyCacheOrder(t, c, st.IndexStore(), chunks...) 366 367 // move again, should be no-op 368 err = c.ShallowCopy(context.Background(), st, chunksToMove...) 369 if err != nil { 370 t.Fatal(err) 371 } 372 373 verifyCacheState(t, st.IndexStore(), c, chunks[0].Address(), chunks[9].Address(), 10) 374 verifyCacheOrder(t, c, st.IndexStore(), chunks...) 375 376 chunks1 := chunktest.GenerateTestRandomChunks(10) 377 chunksToMove1 := make([]swarm.Address, 0, 10) 378 379 // add the chunks to chunkstore. This simulates the reserve already populating 380 // the chunkstore with chunks. 381 for _, ch := range chunks1 { 382 err := st.Run(context.Background(), func(s transaction.Store) error { 383 return s.ChunkStore().Put(context.Background(), ch) 384 }) 385 if err != nil { 386 t.Fatal(err) 387 } 388 chunksToMove1 = append(chunksToMove1, ch.Address()) 389 } 390 391 // move new chunks 392 err = c.ShallowCopy(context.Background(), st, chunksToMove1...) 393 if err != nil { 394 t.Fatal(err) 395 } 396 397 verifyCacheState(t, st.IndexStore(), c, chunks[0].Address(), chunks1[9].Address(), 20) 398 verifyCacheOrder(t, c, st.IndexStore(), append(chunks, chunks1...)...) 399 400 err = c.RemoveOldest(context.Background(), st, 10) 401 if err != nil { 402 t.Fatal(err) 403 } 404 405 verifyChunksDeleted(t, st.ChunkStore(), chunks...) 406 } 407 408 func TestShallowCopyOverCap(t *testing.T) { 409 t.Parallel() 410 411 st := newTestStorage(t) 412 c, err := cache.New(context.Background(), st.IndexStore(), 10) 413 if err != nil { 414 t.Fatal(err) 415 } 416 417 chunks := chunktest.GenerateTestRandomChunks(15) 418 chunksToMove := make([]swarm.Address, 0, 15) 419 420 // add the chunks to chunkstore. This simulates the reserve already populating 421 // the chunkstore with chunks. 422 for _, ch := range chunks { 423 424 err := st.Run(context.Background(), func(s transaction.Store) error { 425 return s.ChunkStore().Put(context.Background(), ch) 426 }) 427 if err != nil { 428 t.Fatal(err) 429 } 430 chunksToMove = append(chunksToMove, ch.Address()) 431 } 432 433 // move new chunks 434 err = c.ShallowCopy(context.Background(), st, chunksToMove...) 435 if err != nil { 436 t.Fatal(err) 437 } 438 439 verifyCacheState(t, st.IndexStore(), c, chunks[5].Address(), chunks[14].Address(), 10) 440 verifyCacheOrder(t, c, st.IndexStore(), chunks[5:15]...) 441 442 err = c.RemoveOldest(context.Background(), st, 5) 443 if err != nil { 444 t.Fatal(err) 445 } 446 447 verifyChunksDeleted(t, st.ChunkStore(), chunks[5:10]...) 448 } 449 450 func TestShallowCopyAlreadyCached(t *testing.T) { 451 t.Parallel() 452 453 st := newTestStorage(t) 454 c, err := cache.New(context.Background(), st.IndexStore(), 1000) 455 if err != nil { 456 t.Fatal(err) 457 } 458 459 chunks := chunktest.GenerateTestRandomChunks(10) 460 chunksToMove := make([]swarm.Address, 0, 10) 461 462 for _, ch := range chunks { 463 // add the chunks to chunkstore. This simulates the reserve already populating the chunkstore with chunks. 464 465 err := st.Run(context.Background(), func(s transaction.Store) error { 466 return s.ChunkStore().Put(context.Background(), ch) 467 }) 468 if err != nil { 469 t.Fatal(err) 470 } 471 // already cached 472 err = c.Putter(st).Put(context.Background(), ch) 473 if err != nil { 474 t.Fatal(err) 475 } 476 chunksToMove = append(chunksToMove, ch.Address()) 477 } 478 479 // move new chunks 480 err = c.ShallowCopy(context.Background(), st, chunksToMove...) 481 if err != nil { 482 t.Fatal(err) 483 } 484 485 verifyChunksExist(t, st.ChunkStore(), chunks...) 486 487 err = c.RemoveOldest(context.Background(), st, 10) 488 if err != nil { 489 t.Fatal(err) 490 } 491 492 verifyChunksDeleted(t, st.ChunkStore(), chunks...) 493 } 494 495 func verifyCacheState( 496 t *testing.T, 497 store storage.Reader, 498 c *cache.Cache, 499 expStart, expEnd swarm.Address, 500 expCount uint64, 501 ) { 502 t.Helper() 503 504 state := c.State(store) 505 expState := cache.CacheState{Head: expStart, Tail: expEnd, Size: expCount} 506 507 if diff := cmp.Diff(expState, state); diff != "" { 508 t.Fatalf("state mismatch (-want +have):\n%s", diff) 509 } 510 } 511 512 func verifyCacheOrder( 513 t *testing.T, 514 c *cache.Cache, 515 st storage.Reader, 516 chs ...swarm.Chunk, 517 ) { 518 t.Helper() 519 520 state := c.State(st) 521 522 if uint64(len(chs)) != state.Size { 523 t.Fatalf("unexpected count, exp: %d found: %d", state.Size, len(chs)) 524 } 525 526 idx := 0 527 err := c.IterateOldToNew(st, state.Head, state.Tail, func(entry swarm.Address) (bool, error) { 528 if !chs[idx].Address().Equal(entry) { 529 return true, fmt.Errorf( 530 "incorrect order of cache items, idx: %d exp: %s found: %s", 531 idx, chs[idx].Address(), entry, 532 ) 533 } 534 idx++ 535 return false, nil 536 }) 537 if err != nil { 538 t.Fatalf("failed at index %d err %s", idx, err) 539 } 540 } 541 542 func verifyChunksDeleted( 543 t *testing.T, 544 chStore storage.ReadOnlyChunkStore, 545 chs ...swarm.Chunk, 546 ) { 547 t.Helper() 548 549 for _, ch := range chs { 550 found, err := chStore.Has(context.TODO(), ch.Address()) 551 if err != nil { 552 t.Fatal(err) 553 } 554 if found { 555 t.Fatalf("chunk %s expected to not be found but exists", ch.Address()) 556 } 557 _, err = chStore.Get(context.TODO(), ch.Address()) 558 if !errors.Is(err, storage.ErrNotFound) { 559 t.Fatalf("expected error %v but found %v", storage.ErrNotFound, err) 560 } 561 } 562 } 563 564 func verifyChunksExist( 565 t *testing.T, 566 chStore storage.ReadOnlyChunkStore, 567 chs ...swarm.Chunk, 568 ) { 569 t.Helper() 570 571 for _, ch := range chs { 572 found, err := chStore.Has(context.TODO(), ch.Address()) 573 if err != nil { 574 t.Fatal(err) 575 } 576 if !found { 577 t.Fatalf("chunk %s expected to be found but not exists", ch.Address()) 578 } 579 } 580 } 581 582 type inmemStorage struct { 583 indexStore *customIndexStore 584 chunkStore storage.ChunkStore 585 } 586 587 func newTestStorage(t *testing.T) *inmemStorage { 588 t.Helper() 589 590 ts := &inmemStorage{ 591 indexStore: &customIndexStore{inmemstore.New(), nil}, 592 chunkStore: inmemchunkstore.New(), 593 } 594 595 return ts 596 } 597 598 type customIndexStore struct { 599 storage.IndexStore 600 putFn func(storage.Item) error 601 } 602 603 func (s *customIndexStore) Put(i storage.Item) error { 604 if s.putFn != nil { 605 return s.putFn(i) 606 } 607 return s.IndexStore.Put(i) 608 } 609 610 func (t *inmemStorage) NewTransaction(ctx context.Context) (transaction.Transaction, func()) { 611 return &inmemTrx{t.indexStore, t.chunkStore}, func() {} 612 } 613 614 type inmemTrx struct { 615 indexStore storage.IndexStore 616 chunkStore storage.ChunkStore 617 } 618 619 func (t *inmemStorage) IndexStore() storage.Reader { return t.indexStore } 620 func (t *inmemStorage) ChunkStore() storage.ReadOnlyChunkStore { return t.chunkStore } 621 622 func (t *inmemTrx) IndexStore() storage.IndexStore { return t.indexStore } 623 func (t *inmemTrx) ChunkStore() storage.ChunkStore { return t.chunkStore } 624 func (t *inmemTrx) Commit() error { return nil } 625 626 func (t *inmemStorage) Close() error { return nil } 627 func (t *inmemStorage) Run(ctx context.Context, f func(s transaction.Store) error) error { 628 trx, done := t.NewTransaction(ctx) 629 defer done() 630 return f(trx) 631 }