github.com/ethersphere/bee/v2@v2.2.0/pkg/storage/migration/index_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 migration_test 6 7 import ( 8 "fmt" 9 "reflect" 10 "testing" 11 12 storage "github.com/ethersphere/bee/v2/pkg/storage" 13 "github.com/ethersphere/bee/v2/pkg/storage/inmemstore" 14 "github.com/ethersphere/bee/v2/pkg/storage/migration" 15 ) 16 17 func TestNewStepOnIndex(t *testing.T) { 18 t.Parallel() 19 20 t.Run("noop step", func(t *testing.T) { 21 t.Parallel() 22 23 const populateItemsCount = 100 24 store := inmemstore.New() 25 populateStore(t, store, populateItemsCount) 26 27 stepFn := migration.NewStepOnIndex( 28 store, 29 storage.Query{ 30 Factory: newObjFactory, 31 }, 32 ) 33 34 initialCount, err := store.Count(&obj{}) 35 if err != nil { 36 t.Fatalf("count should succeed: %v", err) 37 } 38 if initialCount != populateItemsCount { 39 t.Fatalf("have %d, want %d", initialCount, populateItemsCount) 40 } 41 42 if err := stepFn(); err != nil { 43 t.Fatalf("step migration should succeed: %v", err) 44 } 45 46 afterStepCount, err := store.Count(&obj{}) 47 if err != nil { 48 t.Fatalf("count should succeed: %v", err) 49 } 50 51 if afterStepCount != initialCount { 52 t.Fatalf("step migration should not affect store") 53 } 54 }) 55 56 t.Run("delete items", func(t *testing.T) { 57 t.Parallel() 58 59 const populateItemsCount = 100 60 store := inmemstore.New() 61 populateStore(t, store, populateItemsCount) 62 63 stepFn := migration.NewStepOnIndex(store, 64 storage.Query{ 65 Factory: newObjFactory, 66 ItemProperty: storage.QueryItem, 67 }, 68 migration.WithItemDeleteFn(func(i storage.Item) bool { 69 o := i.(*obj) 70 return o.val <= 9 71 }), 72 migration.WithOpPerBatch(3), 73 ) 74 75 if err := stepFn(); err != nil { 76 t.Fatalf("step migration should succeed: %v", err) 77 } 78 79 assertItemsInRange(t, store, 10, populateItemsCount) 80 }) 81 82 t.Run("update items", func(t *testing.T) { 83 t.Parallel() 84 85 const populateItemsCount = 100 86 const minVal = 50 87 store := inmemstore.New() 88 populateStore(t, store, populateItemsCount) 89 90 stepFn := migration.NewStepOnIndex(store, 91 storage.Query{ 92 Factory: newObjFactory, 93 ItemProperty: storage.QueryItem, 94 }, 95 // translate values from [0 ... populateItemsCount) to [minVal ... populateItemsCount + minval) 96 migration.WithItemUpdaterFn(func(i storage.Item) (storage.Item, bool) { 97 o := i.(*obj) 98 if o.val < minVal { 99 o.val += populateItemsCount 100 return o, true 101 } 102 103 return nil, false 104 }), 105 migration.WithOpPerBatch(3), 106 ) 107 108 if err := stepFn(); err != nil { 109 t.Fatalf("step migration should succeed: %v", err) 110 } 111 112 assertItemsInRange(t, store, minVal, populateItemsCount+minVal) 113 }) 114 115 t.Run("delete and update items", func(t *testing.T) { 116 t.Parallel() 117 118 const populateItemsCount = 100 119 store := inmemstore.New() 120 populateStore(t, store, populateItemsCount) 121 122 step := migration.NewStepOnIndex( 123 store, 124 storage.Query{ 125 Factory: newObjFactory, 126 ItemProperty: storage.QueryItem, 127 }, 128 // remove first 10 items 129 migration.WithItemDeleteFn(func(i storage.Item) bool { 130 o := i.(*obj) 131 return o.val < 10 132 }), 133 // translate values from [90-100) to [0-10) range 134 migration.WithItemUpdaterFn(func(i storage.Item) (storage.Item, bool) { 135 o := i.(*obj) 136 if o.val >= 90 { 137 o.val -= 90 138 return o, true 139 } 140 141 return nil, false 142 }), 143 migration.WithOpPerBatch(3), 144 ) 145 146 if err := step(); err != nil { 147 t.Fatalf("step migration should succeed: %v", err) 148 } 149 150 assertItemsInRange(t, store, 0, populateItemsCount-10) 151 }) 152 153 t.Run("update with ID change", func(t *testing.T) { 154 t.Parallel() 155 156 const populateItemsCount = 100 157 store := inmemstore.New() 158 populateStore(t, store, populateItemsCount) 159 160 step := migration.NewStepOnIndex( 161 store, 162 storage.Query{ 163 Factory: newObjFactory, 164 ItemProperty: storage.QueryItem, 165 }, 166 migration.WithItemUpdaterFn(func(i storage.Item) (storage.Item, bool) { 167 o := i.(*obj) 168 o.id += 1 169 return o, true 170 }), 171 migration.WithOpPerBatch(3), 172 ) 173 174 if err := step(); err == nil { 175 t.Fatalf("step migration should fail") 176 } 177 178 assertItemsInRange(t, store, 0, populateItemsCount) 179 }) 180 } 181 182 func TestStepIndex_BatchSize(t *testing.T) { 183 t.Parallel() 184 185 const populateItemsCount = 128 186 for i := 1; i <= 2*populateItemsCount; i <<= 1 { 187 i := i 188 t.Run(fmt.Sprintf("callback called once per item with batch size: %d", i), func(t *testing.T) { 189 t.Parallel() 190 191 store := inmemstore.New() 192 populateStore(t, store, populateItemsCount) 193 194 deleteItemCallMap := make(map[int]struct{}) 195 updateItemCallMap := make(map[int]struct{}) 196 197 stepFn := migration.NewStepOnIndex( 198 store, 199 storage.Query{ 200 Factory: newObjFactory, 201 ItemProperty: storage.QueryItem, 202 }, 203 migration.WithItemDeleteFn(func(i storage.Item) bool { 204 o := i.(*obj) 205 if _, ok := deleteItemCallMap[o.id]; ok { 206 t.Fatalf("delete should be called once") 207 } 208 deleteItemCallMap[o.id] = struct{}{} 209 210 return o.id < 10 211 }), 212 migration.WithItemUpdaterFn(func(i storage.Item) (storage.Item, bool) { 213 o := i.(*obj) 214 if _, ok := updateItemCallMap[o.id]; ok { 215 t.Fatalf("update should be called once") 216 } 217 updateItemCallMap[o.id] = struct{}{} 218 219 return o, true 220 }), 221 migration.WithOpPerBatch(i), 222 ) 223 224 if err := stepFn(); err != nil { 225 t.Fatalf("step migration should succeed: %v", err) 226 } 227 228 opsExpected := (2 * populateItemsCount) - 10 229 opsGot := len(updateItemCallMap) + len(deleteItemCallMap) 230 if opsExpected != opsGot { 231 t.Fatalf("updated and deleted items should add up to total: got %d, want %d", opsGot, opsExpected) 232 } 233 }) 234 } 235 } 236 237 func TestOptions(t *testing.T) { 238 t.Parallel() 239 240 items := []*obj{nil, {id: 1}, {id: 2}} 241 242 t.Run("new options", func(t *testing.T) { 243 t.Parallel() 244 245 opts := migration.DefaultOptions() 246 if opts == nil { 247 t.Fatalf("options should not be nil") 248 } 249 250 deleteFn := opts.DeleteFn() 251 if deleteFn == nil { 252 t.Fatalf("options should have deleteFn specified") 253 } 254 255 updateFn := opts.UpdateFn() 256 if updateFn == nil { 257 t.Fatalf("options should have updateFn specified") 258 } 259 260 for _, i := range items { 261 if deleteFn(i) != false { 262 t.Fatalf("deleteFn should always return false") 263 } 264 265 if _, update := updateFn(i); update != false { 266 t.Fatalf("updateFn should always return false") 267 } 268 } 269 270 if opts.OpPerBatch() <= 10 { 271 t.Fatalf("default opPerBatch value is to small") 272 } 273 }) 274 275 t.Run("delete option apply", func(t *testing.T) { 276 t.Parallel() 277 278 itemC := make(chan storage.Item, 1) 279 opts := migration.DefaultOptions() 280 281 deleteFn := func(i storage.Item) bool { 282 itemC <- i 283 return false 284 } 285 opts.ApplyAll(migration.WithItemDeleteFn(deleteFn)) 286 287 for _, i := range items { 288 opts.DeleteFn()(i) 289 if !reflect.DeepEqual(i, <-itemC) { 290 t.Fatalf("expecting applied deleteFn to be called") 291 } 292 } 293 }) 294 295 t.Run("update option apply", func(t *testing.T) { 296 t.Parallel() 297 298 itemC := make(chan storage.Item, 1) 299 opts := migration.DefaultOptions() 300 301 updateFn := func(i storage.Item) (storage.Item, bool) { 302 itemC <- i 303 return i, false 304 } 305 opts.ApplyAll(migration.WithItemUpdaterFn(updateFn)) 306 307 for _, i := range items { 308 opts.UpdateFn()(i) 309 if !reflect.DeepEqual(i, <-itemC) { 310 t.Fatalf("expecting applied updateFn to be called") 311 } 312 } 313 }) 314 315 t.Run("opPerBatch option apply", func(t *testing.T) { 316 t.Parallel() 317 318 const opPerBetch = 3 319 opts := migration.DefaultOptions() 320 opts.ApplyAll(migration.WithOpPerBatch(opPerBetch)) 321 if opts.OpPerBatch() != opPerBetch { 322 t.Fatalf("have %d, want %d", opts.OpPerBatch(), opPerBetch) 323 } 324 }) 325 } 326 327 func populateStore(t *testing.T, s storage.Store, count int) { 328 t.Helper() 329 330 for i := 0; i < count; i++ { 331 item := &obj{id: i, val: i} 332 if err := s.Put(item); err != nil { 333 t.Fatalf("populate store should succeed: %v", err) 334 } 335 } 336 } 337 338 func assertItemsInRange(t *testing.T, s storage.Store, from, to int) { 339 t.Helper() 340 341 count, err := s.Count(&obj{}) 342 if err != nil { 343 t.Fatalf("count should succeed: %v", err) 344 } 345 if count != to-from { 346 t.Fatalf("have %d, want %d", count, (to - from)) 347 } 348 349 err = s.Iterate( 350 storage.Query{ 351 Factory: newObjFactory, 352 ItemProperty: storage.QueryItem, 353 }, 354 func(r storage.Result) (bool, error) { 355 o := r.Entry.(*obj) 356 if o.val < from || o.val >= to { 357 return true, fmt.Errorf("item not in expected range: val %d", o.val) 358 } 359 return false, nil 360 }, 361 ) 362 if err != nil { 363 t.Fatalf("populate store should succeed: %v", err) 364 } 365 366 }