github.com/ethersphere/bee/v2@v2.2.0/pkg/replicas/putter_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 replicas_test 6 7 import ( 8 "context" 9 "crypto/rand" 10 "errors" 11 "fmt" 12 "io" 13 "sync/atomic" 14 "testing" 15 "time" 16 17 "github.com/ethersphere/bee/v2/pkg/cac" 18 "github.com/ethersphere/bee/v2/pkg/file/redundancy" 19 "github.com/ethersphere/bee/v2/pkg/replicas" 20 "github.com/ethersphere/bee/v2/pkg/storage" 21 "github.com/ethersphere/bee/v2/pkg/storage/inmemchunkstore" 22 "github.com/ethersphere/bee/v2/pkg/swarm" 23 ) 24 25 var ( 26 errTestA = errors.New("A") 27 errTestB = errors.New("B") 28 ) 29 30 type testBasePutter struct { 31 getErrors func(context.Context, swarm.Address) error 32 putErrors func(context.Context, swarm.Address) error 33 store storage.ChunkStore 34 } 35 36 func (tbp *testBasePutter) Get(ctx context.Context, addr swarm.Address) (swarm.Chunk, error) { 37 38 g := tbp.getErrors 39 if g != nil { 40 return nil, g(ctx, addr) 41 } 42 return tbp.store.Get(ctx, addr) 43 } 44 45 func (tbp *testBasePutter) Put(ctx context.Context, ch swarm.Chunk) error { 46 47 g := tbp.putErrors 48 if g != nil { 49 return g(ctx, ch.Address()) 50 } 51 return tbp.store.Put(ctx, ch) 52 } 53 54 func TestPutter(t *testing.T) { 55 t.Parallel() 56 tcs := []struct { 57 level redundancy.Level 58 length int 59 }{ 60 {0, 1}, 61 {1, 1}, 62 {2, 1}, 63 {3, 1}, 64 {4, 1}, 65 {0, 4096}, 66 {1, 4096}, 67 {2, 4096}, 68 {3, 4096}, 69 {4, 4096}, 70 } 71 for _, tc := range tcs { 72 t.Run(fmt.Sprintf("redundancy:%d, size:%d", tc.level, tc.length), func(t *testing.T) { 73 buf := make([]byte, tc.length) 74 if _, err := io.ReadFull(rand.Reader, buf); err != nil { 75 t.Fatal(err) 76 } 77 ctx := context.Background() 78 ctx = redundancy.SetLevelInContext(ctx, tc.level) 79 80 ch, err := cac.New(buf) 81 if err != nil { 82 t.Fatal(err) 83 } 84 store := inmemchunkstore.New() 85 defer store.Close() 86 p := replicas.NewPutter(store) 87 88 // original chunk 89 if err := store.Put(ctx, ch); err != nil { 90 t.Fatalf("expected no error. got %v", err) 91 } 92 if err := p.Put(ctx, ch); err != nil { 93 t.Fatalf("expected no error. got %v", err) 94 } 95 var addrs []swarm.Address 96 orig := false 97 _ = store.Iterate(ctx, func(chunk swarm.Chunk) (stop bool, err error) { 98 if ch.Address().Equal(chunk.Address()) { 99 orig = true 100 return false, nil 101 } 102 addrs = append(addrs, chunk.Address()) 103 return false, nil 104 }) 105 if !orig { 106 t.Fatal("original chunk missing") 107 } 108 t.Run("dispersion", func(t *testing.T) { 109 if err := dispersed(tc.level, ch, addrs); err != nil { 110 t.Fatalf("addresses are not dispersed: %v", err) 111 } 112 }) 113 t.Run("attempts", func(t *testing.T) { 114 count := tc.level.GetReplicaCount() 115 if len(addrs) != count { 116 t.Fatalf("incorrect number of attempts. want %v, got %v", count, len(addrs)) 117 } 118 }) 119 120 t.Run("replication", func(t *testing.T) { 121 if err := replicated(store, ch, addrs); err != nil { 122 t.Fatalf("chunks are not replicas: %v", err) 123 } 124 }) 125 }) 126 } 127 t.Run("error handling", func(t *testing.T) { 128 tcs := []struct { 129 name string 130 level redundancy.Level 131 length int 132 f func(*testBasePutter) *testBasePutter 133 err []error 134 }{ 135 {"put errors", 4, 4096, func(tbp *testBasePutter) *testBasePutter { 136 var j int32 137 i := &j 138 atomic.StoreInt32(i, 0) 139 tbp.putErrors = func(ctx context.Context, _ swarm.Address) error { 140 j := atomic.AddInt32(i, 1) 141 if j == 6 { 142 return errTestA 143 } 144 if j == 12 { 145 return errTestB 146 } 147 return nil 148 } 149 return tbp 150 }, []error{errTestA, errTestB}}, 151 {"put latencies", 4, 4096, func(tbp *testBasePutter) *testBasePutter { 152 var j int32 153 i := &j 154 atomic.StoreInt32(i, 0) 155 tbp.putErrors = func(ctx context.Context, _ swarm.Address) error { 156 j := atomic.AddInt32(i, 1) 157 if j == 6 { 158 select { 159 case <-time.After(100 * time.Millisecond): 160 case <-ctx.Done(): 161 return ctx.Err() 162 } 163 } 164 if j == 12 { 165 return errTestA 166 } 167 return nil 168 } 169 return tbp 170 }, []error{errTestA, context.DeadlineExceeded}}, 171 } 172 for _, tc := range tcs { 173 t.Run(tc.name, func(t *testing.T) { 174 buf := make([]byte, tc.length) 175 if _, err := io.ReadFull(rand.Reader, buf); err != nil { 176 t.Fatal(err) 177 } 178 ctx := context.Background() 179 ctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond) 180 defer cancel() 181 ctx = redundancy.SetLevelInContext(ctx, tc.level) 182 ch, err := cac.New(buf) 183 if err != nil { 184 t.Fatal(err) 185 } 186 store := inmemchunkstore.New() 187 defer store.Close() 188 p := replicas.NewPutter(tc.f(&testBasePutter{store: store})) 189 errs := p.Put(ctx, ch) 190 for _, err := range tc.err { 191 if !errors.Is(errs, err) { 192 t.Fatalf("incorrect error. want it to contain %v. got %v.", tc.err, errs) 193 } 194 } 195 }) 196 } 197 }) 198 199 }