github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/internal/chunkstore/chunkstore_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 chunkstore_test 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "io/fs" 12 "math" 13 "os" 14 "testing" 15 16 "github.com/ethersphere/bee/v2/pkg/sharky" 17 "github.com/ethersphere/bee/v2/pkg/storer/internal/transaction" 18 19 "github.com/ethersphere/bee/v2/pkg/storage" 20 "github.com/ethersphere/bee/v2/pkg/storage/inmemstore" 21 "github.com/ethersphere/bee/v2/pkg/storage/storagetest" 22 chunktest "github.com/ethersphere/bee/v2/pkg/storage/testing" 23 "github.com/ethersphere/bee/v2/pkg/storer/internal/chunkstore" 24 "github.com/ethersphere/bee/v2/pkg/swarm" 25 "github.com/spf13/afero" 26 "github.com/stretchr/testify/assert" 27 ) 28 29 func TestRetrievalIndexItem(t *testing.T) { 30 t.Parallel() 31 32 tests := []struct { 33 name string 34 test *storagetest.ItemMarshalAndUnmarshalTest 35 }{{ 36 name: "zero values", 37 test: &storagetest.ItemMarshalAndUnmarshalTest{ 38 Item: &chunkstore.RetrievalIndexItem{}, 39 Factory: func() storage.Item { return new(chunkstore.RetrievalIndexItem) }, 40 MarshalErr: chunkstore.ErrMarshalInvalidRetrievalIndexItemAddress, 41 }, 42 }, { 43 name: "zero address", 44 test: &storagetest.ItemMarshalAndUnmarshalTest{ 45 Item: &chunkstore.RetrievalIndexItem{ 46 Address: swarm.ZeroAddress, 47 }, 48 Factory: func() storage.Item { return new(chunkstore.RetrievalIndexItem) }, 49 MarshalErr: chunkstore.ErrMarshalInvalidRetrievalIndexItemAddress, 50 }, 51 }, { 52 name: "min values", 53 test: &storagetest.ItemMarshalAndUnmarshalTest{ 54 Item: &chunkstore.RetrievalIndexItem{ 55 Address: swarm.NewAddress(storagetest.MinAddressBytes[:]), 56 }, 57 Factory: func() storage.Item { return new(chunkstore.RetrievalIndexItem) }, 58 }, 59 }, { 60 name: "max values", 61 test: &storagetest.ItemMarshalAndUnmarshalTest{ 62 Item: &chunkstore.RetrievalIndexItem{ 63 Address: swarm.NewAddress(storagetest.MaxAddressBytes[:]), 64 Timestamp: math.MaxUint64, 65 Location: sharky.Location{ 66 Shard: math.MaxUint8, 67 Slot: math.MaxUint32, 68 Length: math.MaxUint16, 69 }, 70 RefCnt: math.MaxUint8, 71 }, 72 Factory: func() storage.Item { return new(chunkstore.RetrievalIndexItem) }, 73 }, 74 }, { 75 name: "invalid size", 76 test: &storagetest.ItemMarshalAndUnmarshalTest{ 77 Item: &storagetest.ItemStub{ 78 MarshalBuf: []byte{0xFF}, 79 UnmarshalBuf: []byte{0xFF}, 80 }, 81 Factory: func() storage.Item { return new(chunkstore.RetrievalIndexItem) }, 82 UnmarshalErr: chunkstore.ErrUnmarshalInvalidRetrievalIndexItemSize, 83 }, 84 }} 85 86 for _, tc := range tests { 87 tc := tc 88 89 t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) { 90 t.Parallel() 91 92 storagetest.TestItemMarshalAndUnmarshal(t, tc.test) 93 }) 94 95 t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) { 96 t.Parallel() 97 98 storagetest.TestItemClone(t, &storagetest.ItemCloneTest{ 99 Item: tc.test.Item, 100 CmpOpts: tc.test.CmpOpts, 101 }) 102 }) 103 } 104 } 105 106 type memFS struct { 107 afero.Fs 108 } 109 110 func (m *memFS) Open(path string) (fs.File, error) { 111 return m.Fs.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) 112 } 113 114 func TestChunkStore(t *testing.T) { 115 t.Parallel() 116 117 store := inmemstore.New() 118 sharky, err := sharky.New(&memFS{Fs: afero.NewMemMapFs()}, 1, swarm.SocMaxChunkSize) 119 if err != nil { 120 t.Fatal(err) 121 } 122 123 st := transaction.NewStorage(sharky, store) 124 125 t.Cleanup(func() { 126 if err := store.Close(); err != nil { 127 t.Errorf("inmem store close failed: %v", err) 128 } 129 }) 130 131 testChunks := chunktest.GenerateTestRandomChunks(50) 132 133 t.Run("put chunks", func(t *testing.T) { 134 for _, ch := range testChunks { 135 err := st.Run(context.Background(), func(s transaction.Store) error { 136 return s.ChunkStore().Put(context.TODO(), ch) 137 }) 138 if err != nil { 139 t.Fatalf("failed putting new chunk: %v", err) 140 } 141 } 142 }) 143 144 t.Run("put existing chunks", func(t *testing.T) { 145 for idx, ch := range testChunks { 146 // only put duplicates for odd numbered indexes 147 if idx%2 != 0 { 148 err := st.Run(context.Background(), func(s transaction.Store) error { 149 return s.ChunkStore().Put(context.TODO(), ch) 150 }) 151 if err != nil { 152 t.Fatalf("failed putting new chunk: %v", err) 153 } 154 } 155 } 156 }) 157 158 t.Run("get chunks", func(t *testing.T) { 159 for _, ch := range testChunks { 160 readCh, err := st.ChunkStore().Get(context.TODO(), ch.Address()) 161 if err != nil { 162 t.Fatalf("failed getting chunk: %v", err) 163 } 164 if !readCh.Equal(ch) { 165 t.Fatal("read chunk doesn't match") 166 } 167 } 168 }) 169 170 t.Run("has chunks", func(t *testing.T) { 171 for _, ch := range testChunks { 172 exists, err := st.ChunkStore().Has(context.TODO(), ch.Address()) 173 if err != nil { 174 t.Fatalf("failed getting chunk: %v", err) 175 } 176 if !exists { 177 t.Fatalf("chunk not found: %s", ch.Address()) 178 } 179 } 180 }) 181 182 t.Run("iterate chunks", func(t *testing.T) { 183 count := 0 184 err := chunkstore.Iterate(context.TODO(), store, sharky, func(_ swarm.Chunk) (bool, error) { 185 count++ 186 return false, nil 187 }) 188 if err != nil { 189 t.Fatalf("unexpected error while iteration: %v", err) 190 } 191 if count != 50 { 192 t.Fatalf("unexpected no of chunks, exp: %d, found: %d", 50, count) 193 } 194 }) 195 196 t.Run("iterate chunk entries", func(t *testing.T) { 197 count, shared := 0, 0 198 err := chunkstore.IterateChunkEntries(store, func(_ swarm.Address, cnt uint32) (bool, error) { 199 count++ 200 if cnt > 1 { 201 shared++ 202 } 203 return false, nil 204 }) 205 if err != nil { 206 t.Fatalf("unexpected error while iteration: %v", err) 207 } 208 if count != 50 { 209 t.Fatalf("unexpected no of chunks, exp: %d, found: %d", 50, count) 210 } 211 if shared != 25 { 212 t.Fatalf("unexpected no of chunks, exp: %d, found: %d", 25, shared) 213 } 214 }) 215 216 t.Run("delete unique chunks", func(t *testing.T) { 217 for idx, ch := range testChunks { 218 // Delete all even numbered indexes along with 0 219 if idx%2 == 0 { 220 err := st.Run(context.Background(), func(s transaction.Store) error { 221 return s.ChunkStore().Delete(context.TODO(), ch.Address()) 222 }) 223 if err != nil { 224 t.Fatalf("failed deleting chunk: %v", err) 225 } 226 } 227 } 228 }) 229 230 t.Run("check deleted chunks", func(t *testing.T) { 231 for idx, ch := range testChunks { 232 if idx%2 == 0 { 233 // Check even numbered indexes are deleted 234 _, err := st.ChunkStore().Get(context.TODO(), ch.Address()) 235 if !errors.Is(err, storage.ErrNotFound) { 236 t.Fatalf("expected storage not found error found: %v", err) 237 } 238 found, err := st.ChunkStore().Has(context.TODO(), ch.Address()) 239 if err != nil { 240 t.Fatalf("unexpected error in Has: %v", err) 241 } 242 if found { 243 t.Fatal("expected chunk to not be found") 244 } 245 } else { 246 // Check rest of the entries are intact 247 readCh, err := st.ChunkStore().Get(context.TODO(), ch.Address()) 248 if err != nil { 249 t.Fatalf("failed getting chunk: %v", err) 250 } 251 if !readCh.Equal(ch) { 252 t.Fatal("read chunk doesn't match") 253 } 254 exists, err := st.ChunkStore().Has(context.TODO(), ch.Address()) 255 if err != nil { 256 t.Fatalf("failed getting chunk: %v", err) 257 } 258 if !exists { 259 t.Fatalf("chunk not found: %s", ch.Address()) 260 } 261 } 262 } 263 }) 264 265 t.Run("iterate chunks after delete", func(t *testing.T) { 266 count := 0 267 err := chunkstore.Iterate(context.TODO(), store, sharky, func(_ swarm.Chunk) (bool, error) { 268 count++ 269 return false, nil 270 }) 271 if err != nil { 272 t.Fatalf("unexpected error while iteration: %v", err) 273 } 274 if count != 25 { 275 t.Fatalf("unexpected no of chunks, exp: %d, found: %d", 25, count) 276 } 277 }) 278 279 // due to refCnt 2, these chunks should be present in the store even after delete 280 t.Run("delete duplicate chunks", func(t *testing.T) { 281 for idx, ch := range testChunks { 282 if idx%2 != 0 { 283 err := st.Run(context.Background(), func(s transaction.Store) error { 284 return s.ChunkStore().Delete(context.TODO(), ch.Address()) 285 }) 286 if err != nil { 287 t.Fatalf("failed deleting chunk: %v", err) 288 } 289 } 290 } 291 }) 292 293 t.Run("check chunks still exists", func(t *testing.T) { 294 for idx, ch := range testChunks { 295 if idx%2 != 0 { 296 readCh, err := st.ChunkStore().Get(context.TODO(), ch.Address()) 297 if err != nil { 298 t.Fatalf("failed getting chunk: %v", err) 299 } 300 if !readCh.Equal(ch) { 301 t.Fatal("read chunk doesn't match") 302 } 303 exists, err := st.ChunkStore().Has(context.TODO(), ch.Address()) 304 if err != nil { 305 t.Fatalf("failed getting chunk: %v", err) 306 } 307 if !exists { 308 t.Fatalf("chunk not found: %s", ch.Address()) 309 } 310 } 311 } 312 }) 313 314 t.Run("delete duplicate chunks again", func(t *testing.T) { 315 for idx, ch := range testChunks { 316 if idx%2 != 0 { 317 err := st.Run(context.Background(), func(s transaction.Store) error { 318 return s.ChunkStore().Delete(context.TODO(), ch.Address()) 319 }) 320 if err != nil { 321 t.Fatalf("failed deleting chunk: %v", err) 322 } 323 } 324 } 325 }) 326 327 t.Run("check all are deleted", func(t *testing.T) { 328 count := 0 329 err := chunkstore.Iterate(context.TODO(), store, sharky, func(_ swarm.Chunk) (bool, error) { 330 count++ 331 return false, nil 332 }) 333 if err != nil { 334 t.Fatalf("unexpected error while iteration: %v", err) 335 } 336 if count != 0 { 337 t.Fatalf("unexpected no of chunks, exp: %d, found: %d", 0, count) 338 } 339 }) 340 341 t.Run("close store", func(t *testing.T) { 342 err := st.Close() 343 if err != nil { 344 t.Fatalf("unexpected error during close: %v", err) 345 } 346 }) 347 } 348 349 // TestIterateLocations asserts that all stored chunks 350 // are retrievable by sharky using IterateLocations. 351 func TestIterateLocations(t *testing.T) { 352 t.Parallel() 353 354 const chunksCount = 50 355 356 st := makeStorage(t) 357 testChunks := chunktest.GenerateTestRandomChunks(chunksCount) 358 ctx := context.Background() 359 360 for _, ch := range testChunks { 361 assert.NoError(t, st.Run(context.Background(), func(s transaction.Store) error { return s.ChunkStore().Put(ctx, ch) })) 362 } 363 364 readCount := 0 365 respC := chunkstore.IterateLocations(ctx, st.IndexStore()) 366 367 for resp := range respC { 368 assert.NoError(t, resp.Err) 369 370 buf := make([]byte, resp.Location.Length) 371 assert.NoError(t, st.sharky.Read(ctx, resp.Location, buf)) 372 373 assert.True(t, swarm.ContainsChunkWithData(testChunks, buf)) 374 readCount++ 375 } 376 377 assert.Equal(t, chunksCount, readCount) 378 } 379 380 // TestIterateLocations_Stop asserts that IterateLocations will 381 // stop iteration when context is canceled. 382 func TestIterateLocations_Stop(t *testing.T) { 383 t.Parallel() 384 385 const chunksCount = 50 386 const stopReadAt = 10 387 388 st := makeStorage(t) 389 testChunks := chunktest.GenerateTestRandomChunks(chunksCount) 390 ctx, cancel := context.WithCancel(context.Background()) 391 defer cancel() 392 393 for _, ch := range testChunks { 394 assert.NoError(t, st.Run(context.Background(), func(s transaction.Store) error { return s.ChunkStore().Put(ctx, ch) })) 395 } 396 397 readCount := 0 398 respC := chunkstore.IterateLocations(ctx, st.IndexStore()) 399 400 for resp := range respC { 401 if resp.Err != nil { 402 assert.ErrorIs(t, resp.Err, context.Canceled) 403 break 404 } 405 406 buf := make([]byte, resp.Location.Length) 407 if err := st.sharky.Read(ctx, resp.Location, buf); err != nil { 408 assert.ErrorIs(t, err, context.Canceled) 409 break 410 } 411 412 assert.True(t, swarm.ContainsChunkWithData(testChunks, buf)) 413 readCount++ 414 415 if readCount == stopReadAt { 416 cancel() 417 } 418 } 419 420 assert.InDelta(t, stopReadAt, readCount, 1) 421 } 422 423 type chunkStore struct { 424 transaction.Storage 425 sharky *sharky.Store 426 } 427 428 func makeStorage(t *testing.T) *chunkStore { 429 t.Helper() 430 431 store := inmemstore.New() 432 sharky, err := sharky.New(&memFS{Fs: afero.NewMemMapFs()}, 1, swarm.SocMaxChunkSize) 433 assert.NoError(t, err) 434 435 t.Cleanup(func() { 436 assert.NoError(t, store.Close()) 437 assert.NoError(t, sharky.Close()) 438 }) 439 440 return &chunkStore{transaction.NewStorage(sharky, store), sharky} 441 }