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  }