github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/netstore_test.go (about) 1 // Copyright 2023 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 storer_test 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "testing" 12 "time" 13 14 "github.com/ethersphere/bee/v2/pkg/pushsync" 15 "github.com/ethersphere/bee/v2/pkg/retrieval" 16 storage "github.com/ethersphere/bee/v2/pkg/storage" 17 chunktesting "github.com/ethersphere/bee/v2/pkg/storage/testing" 18 storer "github.com/ethersphere/bee/v2/pkg/storer" 19 "github.com/ethersphere/bee/v2/pkg/swarm" 20 ) 21 22 type testRetrieval struct { 23 fn func(swarm.Address) (swarm.Chunk, error) 24 } 25 26 func (t *testRetrieval) RetrieveChunk(_ context.Context, address swarm.Address, _ swarm.Address) (swarm.Chunk, error) { 27 return t.fn(address) 28 } 29 30 func testNetStore(t *testing.T, newStorer func(r retrieval.Interface) (*storer.DB, error)) { 31 t.Helper() 32 33 t.Run("direct upload", func(t *testing.T) { 34 t.Parallel() 35 36 t.Run("commit", func(t *testing.T) { 37 t.Parallel() 38 39 chunks := chunktesting.GenerateTestRandomChunks(10) 40 41 lstore, err := newStorer(nil) 42 if err != nil { 43 t.Fatal(err) 44 } 45 46 session := lstore.DirectUpload() 47 48 count := 0 49 quit := make(chan struct{}) 50 t.Cleanup(func() { close(quit) }) 51 go func() { 52 for { 53 select { 54 case op := <-lstore.PusherFeed(): 55 found := false 56 for _, ch := range chunks { 57 if op.Chunk.Equal(ch) { 58 found = true 59 break 60 } 61 } 62 if !found { 63 op.Err <- fmt.Errorf("incorrect chunk for push: have %s", op.Chunk.Address()) 64 continue 65 } 66 count++ 67 op.Err <- nil 68 case <-quit: 69 return 70 } 71 } 72 }() 73 74 for _, ch := range chunks { 75 err := session.Put(context.TODO(), ch) 76 if err != nil { 77 t.Fatalf("session.Put(...): unexpected error: %v", err) 78 } 79 } 80 81 err = session.Done(chunks[0].Address()) 82 if err != nil { 83 t.Fatalf("session.Done(): unexpected error: %v", err) 84 } 85 86 if count != 10 { 87 t.Fatalf("unexpected no of pusher ops want 10 have %d", count) 88 } 89 90 verifyChunks(t, lstore.Storage(), chunks, false) 91 }) 92 93 t.Run("pusher error", func(t *testing.T) { 94 t.Parallel() 95 96 chunks := chunktesting.GenerateTestRandomChunks(10) 97 98 lstore, err := newStorer(nil) 99 if err != nil { 100 t.Fatal(err) 101 } 102 103 session := lstore.DirectUpload() 104 105 count := 0 106 quit := make(chan struct{}) 107 t.Cleanup(func() { close(quit) }) 108 wantErr := errors.New("dummy error") 109 go func() { 110 for { 111 select { 112 case op := <-lstore.PusherFeed(): 113 found := false 114 for _, ch := range chunks { 115 if op.Chunk.Equal(ch) { 116 found = true 117 break 118 } 119 } 120 if !found { 121 op.Err <- fmt.Errorf("incorrect chunk for push: have %s", op.Chunk.Address()) 122 continue 123 } 124 count++ 125 if count >= 5 { 126 op.Err <- wantErr 127 } else { 128 op.Err <- nil 129 } 130 case <-quit: 131 return 132 } 133 } 134 }() 135 136 for _, ch := range chunks { 137 err := session.Put(context.TODO(), ch) 138 if err != nil && !errors.Is(err, wantErr) { 139 t.Fatalf("session.Put(...): unexpected error: %v", err) 140 } 141 } 142 143 err = session.Cleanup() 144 if err != nil { 145 t.Fatalf("session.Cleanup(): unexpected error: %v", err) 146 } 147 148 verifyChunks(t, lstore.Storage(), chunks, false) 149 }) 150 151 t.Run("context cancellation", func(t *testing.T) { 152 t.Parallel() 153 154 chunks := chunktesting.GenerateTestRandomChunks(10) 155 156 lstore, err := newStorer(nil) 157 if err != nil { 158 t.Fatal(err) 159 } 160 161 session := lstore.DirectUpload() 162 163 ctx, cancel := context.WithCancel(context.Background()) 164 165 count := 0 166 go func() { 167 <-lstore.PusherFeed() 168 count++ 169 cancel() 170 }() 171 172 for _, ch := range chunks { 173 err := session.Put(ctx, ch) 174 if err != nil && !errors.Is(err, context.Canceled) { 175 t.Fatalf("session.Put(...): unexpected error: have %v", err) 176 } 177 } 178 179 err = session.Cleanup() 180 if err != nil { 181 t.Fatalf("session.Cleanup(): unexpected error: %v", err) 182 } 183 184 if count != 1 { 185 t.Fatalf("unexpected no of pusher ops want 5 have %d", count) 186 } 187 188 verifyChunks(t, lstore.Storage(), chunks, false) 189 }) 190 191 t.Run("shallow receipt retry", func(t *testing.T) { 192 t.Parallel() 193 194 chunk := chunktesting.GenerateTestRandomChunk() 195 196 lstore, err := newStorer(nil) 197 if err != nil { 198 t.Fatal(err) 199 } 200 201 count := 3 202 go func() { 203 for op := range lstore.PusherFeed() { 204 if !op.Chunk.Equal(chunk) { 205 op.Err <- fmt.Errorf("incorrect chunk for push: have %s", op.Chunk.Address()) 206 continue 207 } 208 if count > 0 { 209 count-- 210 op.Err <- pushsync.ErrShallowReceipt 211 } else { 212 op.Err <- nil 213 } 214 } 215 }() 216 217 session := lstore.DirectUpload() 218 219 err = session.Put(context.Background(), chunk) 220 if err != nil { 221 t.Fatalf("session.Put(...): unexpected error: %v", err) 222 } 223 224 err = session.Done(chunk.Address()) 225 if err != nil { 226 t.Fatalf("session.Done(): unexpected error: %v", err) 227 } 228 229 if count != 0 { 230 t.Fatalf("unexpected no of pusher ops want 0 have %d", count) 231 } 232 }) 233 234 t.Run("download", func(t *testing.T) { 235 t.Parallel() 236 237 t.Run("with cache", func(t *testing.T) { 238 t.Parallel() 239 240 chunks := chunktesting.GenerateTestRandomChunks(10) 241 242 lstore, err := newStorer(&testRetrieval{fn: func(address swarm.Address) (swarm.Chunk, error) { 243 for _, ch := range chunks[5:] { 244 if ch.Address().Equal(address) { 245 return ch, nil 246 } 247 } 248 return nil, storage.ErrNotFound 249 }}) 250 if err != nil { 251 t.Fatal(err) 252 } 253 254 // Add some chunks to Cache to simulate local retrieval. 255 for idx, ch := range chunks { 256 if idx < 5 { 257 err := lstore.Cache().Put(context.TODO(), ch) 258 if err != nil { 259 t.Fatalf("cache.Put(...): unexpected error: %v", err) 260 } 261 } else { 262 break 263 } 264 } 265 266 getter := lstore.Download(true) 267 268 for idx, ch := range chunks { 269 readCh, err := getter.Get(context.TODO(), ch.Address()) 270 if err != nil { 271 t.Fatalf("download.Get(...): unexpected error: %v idx %d", err, idx) 272 } 273 if !readCh.Equal(ch) { 274 t.Fatalf("incorrect chunk read: address %s", readCh.Address()) 275 } 276 } 277 278 t.Cleanup(lstore.WaitForBgCacheWorkers()) 279 280 // After download is complete all chunks should be in the local storage. 281 verifyChunks(t, lstore.Storage(), chunks, true) 282 }) 283 }) 284 285 t.Run("no cache", func(t *testing.T) { 286 t.Parallel() 287 288 chunks := chunktesting.GenerateTestRandomChunks(10) 289 290 lstore, err := newStorer(&testRetrieval{fn: func(address swarm.Address) (swarm.Chunk, error) { 291 for _, ch := range chunks[5:] { 292 if ch.Address().Equal(address) { 293 return ch, nil 294 } 295 } 296 return nil, storage.ErrNotFound 297 }}) 298 if err != nil { 299 t.Fatal(err) 300 } 301 302 // Add some chunks to Cache to simulate local retrieval. 303 for idx, ch := range chunks { 304 if idx < 5 { 305 err := lstore.Cache().Put(context.TODO(), ch) 306 if err != nil { 307 t.Fatalf("cache.Put(...): unexpected error: %v", err) 308 } 309 } else { 310 break 311 } 312 } 313 314 getter := lstore.Download(false) 315 316 for _, ch := range chunks { 317 readCh, err := getter.Get(context.TODO(), ch.Address()) 318 if err != nil { 319 t.Fatalf("download.Get(...): unexpected error: %v", err) 320 } 321 if !readCh.Equal(ch) { 322 t.Fatalf("incorrect chunk read: address %s", readCh.Address()) 323 } 324 } 325 326 // only the chunks that were already in cache should be present 327 verifyChunks(t, lstore.Storage(), chunks[:5], true) 328 verifyChunks(t, lstore.Storage(), chunks[5:], false) 329 }) 330 }) 331 } 332 333 func TestNetStore(t *testing.T) { 334 t.Parallel() 335 336 t.Run("inmem", func(t *testing.T) { 337 t.Parallel() 338 339 testNetStore(t, func(r retrieval.Interface) (*storer.DB, error) { 340 341 opts := dbTestOps(swarm.RandAddress(t), 0, nil, nil, time.Second) 342 opts.CacheCapacity = 100 343 344 db, err := storer.New(context.Background(), "", opts) 345 if err == nil { 346 db.SetRetrievalService(r) 347 } 348 return db, err 349 }) 350 }) 351 t.Run("disk", func(t *testing.T) { 352 t.Parallel() 353 354 testNetStore(t, func(r retrieval.Interface) (*storer.DB, error) { 355 opts := dbTestOps(swarm.RandAddress(t), 0, nil, nil, time.Second) 356 357 db, err := diskStorer(t, opts)() 358 if err == nil { 359 db.SetRetrievalService(r) 360 } 361 return db, err 362 }) 363 }) 364 }