github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/internal/pinning/pinning_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 pinstore_test 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "math" 12 "testing" 13 14 storage "github.com/ethersphere/bee/v2/pkg/storage" 15 "github.com/ethersphere/bee/v2/pkg/storer/internal/transaction" 16 17 storagetest "github.com/ethersphere/bee/v2/pkg/storage/storagetest" 18 chunktest "github.com/ethersphere/bee/v2/pkg/storage/testing" 19 "github.com/ethersphere/bee/v2/pkg/storer/internal" 20 pinstore "github.com/ethersphere/bee/v2/pkg/storer/internal/pinning" 21 "github.com/ethersphere/bee/v2/pkg/swarm" 22 ) 23 24 type pinningCollection struct { 25 root swarm.Chunk 26 uniqueChunks []swarm.Chunk 27 dupChunks []swarm.Chunk 28 } 29 30 func newTestStorage(t *testing.T) transaction.Storage { 31 t.Helper() 32 storg := internal.NewInmemStorage() 33 return storg 34 } 35 36 func TestPinStore(t *testing.T) { 37 38 tests := make([]pinningCollection, 0, 3) 39 40 for _, tc := range []struct { 41 dupChunks int 42 uniqueChunks int 43 }{ 44 { 45 dupChunks: 5, 46 uniqueChunks: 10, 47 }, 48 { 49 dupChunks: 10, 50 uniqueChunks: 20, 51 }, 52 { 53 dupChunks: 15, 54 uniqueChunks: 130, 55 }, 56 } { 57 var c pinningCollection 58 c.root = chunktest.GenerateTestRandomChunk() 59 c.uniqueChunks = chunktest.GenerateTestRandomChunks(tc.uniqueChunks) 60 dupChunk := chunktest.GenerateTestRandomChunk() 61 for i := 0; i < tc.dupChunks; i++ { 62 c.dupChunks = append(c.dupChunks, dupChunk) 63 } 64 tests = append(tests, c) 65 } 66 67 st := newTestStorage(t) 68 69 t.Run("create new collections", func(t *testing.T) { 70 for tCount, tc := range tests { 71 t.Run(fmt.Sprintf("create collection %d", tCount), func(t *testing.T) { 72 73 var putter internal.PutterCloserWithReference 74 var err error 75 err = st.Run(context.Background(), func(s transaction.Store) error { 76 putter, err = pinstore.NewCollection(s.IndexStore()) 77 return err 78 }) 79 if err != nil { 80 t.Fatal(err) 81 } 82 83 for _, ch := range append(tc.uniqueChunks, tc.root) { 84 if err := st.Run(context.Background(), func(s transaction.Store) error { 85 return putter.Put(context.Background(), s, ch) 86 }); err != nil { 87 t.Fatal(err) 88 } 89 } 90 for _, ch := range tc.dupChunks { 91 if err := st.Run(context.Background(), func(s transaction.Store) error { 92 return putter.Put(context.Background(), s, ch) 93 }); err != nil { 94 t.Fatal(err) 95 } 96 } 97 98 if err := st.Run(context.Background(), func(s transaction.Store) error { 99 return putter.Close(s.IndexStore(), tc.root.Address()) 100 }); err != nil { 101 t.Fatal(err) 102 } 103 }) 104 } 105 }) 106 107 t.Run("verify all collection data", func(t *testing.T) { 108 for tCount, tc := range tests { 109 t.Run(fmt.Sprintf("verify collection %d", tCount), func(t *testing.T) { 110 allChunks := append(tc.uniqueChunks, tc.root) 111 allChunks = append(allChunks, tc.dupChunks...) 112 for _, ch := range allChunks { 113 exists, err := st.ChunkStore().Has(context.TODO(), ch.Address()) 114 if err != nil { 115 t.Fatal(err) 116 } 117 if !exists { 118 t.Fatal("chunk should exist") 119 } 120 rch, err := st.ChunkStore().Get(context.TODO(), ch.Address()) 121 if err != nil { 122 t.Fatal(err) 123 } 124 if !ch.Equal(rch) { 125 t.Fatal("read chunk not equal") 126 } 127 } 128 }) 129 } 130 }) 131 132 t.Run("verify root pins", func(t *testing.T) { 133 pins, err := pinstore.Pins(st.IndexStore()) 134 if err != nil { 135 t.Fatal(err) 136 } 137 if len(pins) != 3 { 138 t.Fatalf("incorrect no of root pins, expected 3 found %d", len(pins)) 139 } 140 for _, tc := range tests { 141 found := false 142 for _, f := range pins { 143 if f.Equal(tc.root.Address()) { 144 found = true 145 break 146 } 147 } 148 if !found { 149 t.Fatalf("pin %s not found", tc.root.Address()) 150 } 151 } 152 }) 153 154 t.Run("has pin", func(t *testing.T) { 155 for _, tc := range tests { 156 found, err := pinstore.HasPin(st.IndexStore(), tc.root.Address()) 157 if err != nil { 158 t.Fatal(err) 159 } 160 if !found { 161 t.Fatalf("expected the pin %s to be found", tc.root.Address()) 162 } 163 } 164 }) 165 166 t.Run("verify internal state", func(t *testing.T) { 167 for _, tc := range tests { 168 count := 0 169 err := pinstore.IterateCollection(st.IndexStore(), tc.root.Address(), func(addr swarm.Address) (bool, error) { 170 count++ 171 return false, nil 172 }) 173 if err != nil { 174 t.Fatal(err) 175 } 176 if count != len(tc.uniqueChunks)+2 { 177 t.Fatalf("incorrect no of chunks in collection, expected %d found %d", len(tc.uniqueChunks)+2, count) 178 } 179 stat, err := pinstore.GetStat(st.IndexStore(), tc.root.Address()) 180 if err != nil { 181 t.Fatal(err) 182 } 183 if stat.Total != uint64(len(tc.uniqueChunks)+len(tc.dupChunks)+1) { 184 t.Fatalf("incorrect no of chunks, expected %d found %d", len(tc.uniqueChunks)+len(tc.dupChunks)+1, stat.Total) 185 } 186 if stat.DupInCollection != uint64(len(tc.dupChunks)-1) { 187 t.Fatalf("incorrect no of duplicate chunks, expected %d found %d", len(tc.dupChunks)-1, stat.DupInCollection) 188 } 189 } 190 }) 191 192 t.Run("iterate stats", func(t *testing.T) { 193 count, total, dup := 0, 0, 0 194 err := pinstore.IterateCollectionStats(st.IndexStore(), func(stat pinstore.CollectionStat) (bool, error) { 195 count++ 196 total += int(stat.Total) 197 dup += int(stat.DupInCollection) 198 199 return false, nil 200 }) 201 if err != nil { 202 t.Fatalf("IterateCollectionStats: unexpected error: %v", err) 203 } 204 205 wantTotal, wantDup := 0, 0 206 for _, tc := range tests { 207 wantTotal += len(tc.uniqueChunks) + len(tc.dupChunks) + 1 208 wantDup += len(tc.dupChunks) - 1 209 } 210 211 if count != len(tests) { 212 t.Fatalf("unexpected collection count: want %d have: %d", len(tests), count) 213 } 214 if wantTotal != total { 215 t.Fatalf("unexpected total count: want %d have: %d", wantTotal, total) 216 } 217 if wantDup != dup { 218 t.Fatalf("unexpected dup count: want %d have: %d", wantDup, dup) 219 } 220 }) 221 222 t.Run("delete collection", func(t *testing.T) { 223 err := pinstore.DeletePin(context.TODO(), st, tests[0].root.Address()) 224 if err != nil { 225 t.Fatal(err) 226 } 227 228 found, err := pinstore.HasPin(st.IndexStore(), tests[0].root.Address()) 229 if err != nil { 230 t.Fatal(err) 231 } 232 if found { 233 t.Fatal("expected pin to not be found") 234 } 235 236 pins, err := pinstore.Pins(st.IndexStore()) 237 if err != nil { 238 t.Fatal(err) 239 } 240 if len(pins) != 2 { 241 t.Fatalf("incorrect no of root pins, expected 2 found %d", len(pins)) 242 } 243 244 allChunks := append(tests[0].uniqueChunks, tests[0].root) 245 allChunks = append(allChunks, tests[0].dupChunks...) 246 for _, ch := range allChunks { 247 exists, err := st.ChunkStore().Has(context.TODO(), ch.Address()) 248 if err != nil { 249 t.Fatal(err) 250 } 251 if exists { 252 t.Fatal("chunk should not exist") 253 } 254 _, err = st.ChunkStore().Get(context.TODO(), ch.Address()) 255 if !errors.Is(err, storage.ErrNotFound) { 256 t.Fatal(err) 257 } 258 } 259 }) 260 261 t.Run("error after close", func(t *testing.T) { 262 root := chunktest.GenerateTestRandomChunk() 263 264 var ( 265 putter internal.PutterCloserWithReference 266 err error 267 ) 268 err = st.Run(context.Background(), func(s transaction.Store) error { 269 putter, err = pinstore.NewCollection(s.IndexStore()) 270 return err 271 }) 272 if err != nil { 273 t.Fatal(err) 274 } 275 276 err = st.Run(context.Background(), func(s transaction.Store) error { 277 return putter.Put(context.Background(), s, root) 278 }) 279 if err != nil { 280 t.Fatal(err) 281 } 282 283 err = st.Run(context.Background(), func(s transaction.Store) error { 284 return putter.Close(s.IndexStore(), root.Address()) 285 }) 286 if err != nil { 287 t.Fatal(err) 288 } 289 290 err = st.Run(context.Background(), func(s transaction.Store) error { 291 return putter.Put(context.Background(), s, chunktest.GenerateTestRandomChunk()) 292 }) 293 if !errors.Is(err, pinstore.ErrPutterAlreadyClosed) { 294 t.Fatalf("unexpected error during Put, want: %v, got: %v", pinstore.ErrPutterAlreadyClosed, err) 295 } 296 }) 297 298 t.Run("duplicate collection", func(t *testing.T) { 299 root := chunktest.GenerateTestRandomChunk() 300 301 var ( 302 putter internal.PutterCloserWithReference 303 err error 304 ) 305 err = st.Run(context.Background(), func(s transaction.Store) error { 306 putter, err = pinstore.NewCollection(s.IndexStore()) 307 return err 308 }) 309 if err != nil { 310 t.Fatal(err) 311 } 312 313 err = st.Run(context.Background(), func(s transaction.Store) error { 314 return putter.Put(context.Background(), s, root) 315 }) 316 if err != nil { 317 t.Fatal(err) 318 } 319 320 err = st.Run(context.Background(), func(s transaction.Store) error { 321 return putter.Close(s.IndexStore(), root.Address()) 322 }) 323 if err != nil { 324 t.Fatal(err) 325 } 326 327 err = st.Run(context.Background(), func(s transaction.Store) error { 328 return putter.Close(s.IndexStore(), root.Address()) 329 }) 330 if err == nil || !errors.Is(err, pinstore.ErrDuplicatePinCollection) { 331 t.Fatalf("unexpected error during CLose, want: %v, got: %v", pinstore.ErrDuplicatePinCollection, err) 332 } 333 }) 334 335 t.Run("zero address close", func(t *testing.T) { 336 root := chunktest.GenerateTestRandomChunk() 337 338 var ( 339 putter internal.PutterCloserWithReference 340 err error 341 ) 342 err = st.Run(context.Background(), func(s transaction.Store) error { 343 putter, err = pinstore.NewCollection(s.IndexStore()) 344 return err 345 }) 346 if err != nil { 347 t.Fatal(err) 348 } 349 350 err = st.Run(context.Background(), func(s transaction.Store) error { 351 return putter.Put(context.Background(), s, root) 352 }) 353 if err != nil { 354 t.Fatal(err) 355 } 356 357 err = st.Run(context.Background(), func(s transaction.Store) error { 358 return putter.Close(s.IndexStore(), swarm.ZeroAddress) 359 }) 360 if !errors.Is(err, pinstore.ErrCollectionRootAddressIsZero) { 361 t.Fatalf("unexpected error on close, want: %v, got: %v", pinstore.ErrCollectionRootAddressIsZero, err) 362 } 363 }) 364 } 365 366 func TestCleanup(t *testing.T) { 367 t.Parallel() 368 369 t.Run("cleanup putter", func(t *testing.T) { 370 t.Parallel() 371 372 st := newTestStorage(t) 373 chunks := chunktest.GenerateTestRandomChunks(5) 374 375 var ( 376 putter internal.PutterCloserWithReference 377 err error 378 ) 379 err = st.Run(context.Background(), func(s transaction.Store) error { 380 putter, err = pinstore.NewCollection(s.IndexStore()) 381 return err 382 }) 383 if err != nil { 384 t.Fatal(err) 385 } 386 387 for _, ch := range chunks { 388 err = st.Run(context.Background(), func(s transaction.Store) error { 389 return putter.Put(context.Background(), s, ch) 390 }) 391 if err != nil { 392 t.Fatal(err) 393 } 394 } 395 396 err = putter.Cleanup(st) 397 if err != nil { 398 t.Fatal(err) 399 } 400 401 for _, ch := range chunks { 402 exists, err := st.ChunkStore().Has(context.Background(), ch.Address()) 403 if err != nil { 404 t.Fatal(err) 405 } 406 if exists { 407 t.Fatal("chunk should not exist") 408 } 409 } 410 }) 411 412 t.Run("cleanup dirty", func(t *testing.T) { 413 t.Parallel() 414 415 st := newTestStorage(t) 416 chunks := chunktest.GenerateTestRandomChunks(5) 417 418 var ( 419 putter internal.PutterCloserWithReference 420 err error 421 ) 422 err = st.Run(context.Background(), func(s transaction.Store) error { 423 putter, err = pinstore.NewCollection(s.IndexStore()) 424 return err 425 }) 426 if err != nil { 427 t.Fatal(err) 428 } 429 430 for _, ch := range chunks { 431 err = st.Run(context.Background(), func(s transaction.Store) error { 432 return putter.Put(context.Background(), s, ch) 433 }) 434 if err != nil { 435 t.Fatal(err) 436 } 437 } 438 439 err = pinstore.CleanupDirty(st) 440 if err != nil { 441 t.Fatal(err) 442 } 443 444 for _, ch := range chunks { 445 exists, err := st.ChunkStore().Has(context.Background(), ch.Address()) 446 if err != nil { 447 t.Fatal(err) 448 } 449 if exists { 450 t.Fatal("chunk should not exist") 451 } 452 } 453 }) 454 } 455 456 func TestPinCollectionItem(t *testing.T) { 457 t.Parallel() 458 459 tests := []struct { 460 name string 461 test *storagetest.ItemMarshalAndUnmarshalTest 462 }{{ 463 name: "zero values", 464 test: &storagetest.ItemMarshalAndUnmarshalTest{ 465 Item: &pinstore.PinCollectionItem{}, 466 Factory: func() storage.Item { return new(pinstore.PinCollectionItem) }, 467 MarshalErr: pinstore.ErrInvalidPinCollectionItemAddr, 468 }, 469 }, { 470 name: "zero address", 471 test: &storagetest.ItemMarshalAndUnmarshalTest{ 472 Item: &pinstore.PinCollectionItem{ 473 Addr: swarm.ZeroAddress, 474 }, 475 Factory: func() storage.Item { return new(pinstore.PinCollectionItem) }, 476 MarshalErr: pinstore.ErrInvalidPinCollectionItemAddr, 477 }, 478 }, { 479 name: "zero UUID", 480 test: &storagetest.ItemMarshalAndUnmarshalTest{ 481 Item: &pinstore.PinCollectionItem{ 482 Addr: swarm.NewAddress(storagetest.MinAddressBytes[:]), 483 }, 484 Factory: func() storage.Item { return new(pinstore.PinCollectionItem) }, 485 MarshalErr: pinstore.ErrInvalidPinCollectionItemUUID, 486 }, 487 }, { 488 name: "valid values", 489 test: &storagetest.ItemMarshalAndUnmarshalTest{ 490 Item: &pinstore.PinCollectionItem{ 491 Addr: swarm.NewAddress(storagetest.MinAddressBytes[:]), 492 UUID: pinstore.NewUUID(), 493 }, 494 Factory: func() storage.Item { return new(pinstore.PinCollectionItem) }, 495 }, 496 }, { 497 name: "max values", 498 test: &storagetest.ItemMarshalAndUnmarshalTest{ 499 Item: &pinstore.PinCollectionItem{ 500 Addr: swarm.NewAddress(storagetest.MaxEncryptedRefBytes[:]), 501 UUID: pinstore.NewUUID(), 502 Stat: pinstore.CollectionStat{ 503 Total: math.MaxUint64, 504 DupInCollection: math.MaxUint64, 505 }, 506 }, 507 Factory: func() storage.Item { return new(pinstore.PinCollectionItem) }, 508 }, 509 }, { 510 name: "invalid size", 511 test: &storagetest.ItemMarshalAndUnmarshalTest{ 512 Item: &storagetest.ItemStub{ 513 MarshalBuf: []byte{0xFF}, 514 UnmarshalBuf: []byte{0xFF}, 515 }, 516 Factory: func() storage.Item { return new(pinstore.PinCollectionItem) }, 517 UnmarshalErr: pinstore.ErrInvalidPinCollectionItemSize, 518 }, 519 }} 520 521 for _, tc := range tests { 522 tc := tc 523 524 t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) { 525 t.Parallel() 526 527 storagetest.TestItemMarshalAndUnmarshal(t, tc.test) 528 }) 529 530 t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) { 531 t.Parallel() 532 533 storagetest.TestItemClone(t, &storagetest.ItemCloneTest{ 534 Item: tc.test.Item, 535 CmpOpts: tc.test.CmpOpts, 536 }) 537 }) 538 } 539 } 540 541 func TestPinChunkItem(t *testing.T) { 542 t.Parallel() 543 544 storagetest.TestItemClone(t, &storagetest.ItemCloneTest{ 545 Item: &pinstore.PinChunkItem{ 546 UUID: pinstore.NewUUID(), 547 Addr: swarm.RandAddress(t), 548 }, 549 }) 550 } 551 552 func TestDirtyCollectionsItem(t *testing.T) { 553 t.Parallel() 554 555 storagetest.TestItemClone(t, &storagetest.ItemCloneTest{ 556 Item: &pinstore.DirtyCollection{ 557 UUID: pinstore.NewUUID(), 558 }, 559 }) 560 }