github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/internal/stampindex/stampindex_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 stampindex_test 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "testing" 12 13 "github.com/ethersphere/bee/v2/pkg/storage" 14 "github.com/ethersphere/bee/v2/pkg/storage/storagetest" 15 "github.com/ethersphere/bee/v2/pkg/storer/internal/transaction" 16 17 chunktest "github.com/ethersphere/bee/v2/pkg/storage/testing" 18 "github.com/ethersphere/bee/v2/pkg/storer/internal" 19 "github.com/ethersphere/bee/v2/pkg/storer/internal/stampindex" 20 "github.com/ethersphere/bee/v2/pkg/swarm" 21 "github.com/google/go-cmp/cmp" 22 "github.com/stretchr/testify/assert" 23 ) 24 25 // newTestStorage is a helper function that creates a new storage. 26 func newTestStorage(t *testing.T) transaction.Storage { 27 t.Helper() 28 inmemStorage := internal.NewInmemStorage() 29 return inmemStorage 30 } 31 32 func TestStampIndexItem(t *testing.T) { 33 t.Parallel() 34 35 tests := []struct { 36 name string 37 test *storagetest.ItemMarshalAndUnmarshalTest 38 }{{ 39 name: "zero namespace", 40 test: &storagetest.ItemMarshalAndUnmarshalTest{ 41 Item: stampindex.NewItemWithKeys("", nil, nil, nil), 42 Factory: func() storage.Item { return new(stampindex.Item) }, 43 MarshalErr: stampindex.ErrStampItemMarshalNamespaceInvalid, 44 CmpOpts: []cmp.Option{cmp.AllowUnexported(stampindex.Item{})}, 45 }, 46 }, { 47 name: "zero batchID", 48 test: &storagetest.ItemMarshalAndUnmarshalTest{ 49 Item: stampindex.NewItemWithKeys("test_namespace", nil, nil, nil), 50 Factory: func() storage.Item { return new(stampindex.Item) }, 51 MarshalErr: stampindex.ErrStampItemMarshalBatchIDInvalid, 52 CmpOpts: []cmp.Option{cmp.AllowUnexported(stampindex.Item{})}, 53 }, 54 }, { 55 name: "zero batchIndex", 56 test: &storagetest.ItemMarshalAndUnmarshalTest{ 57 Item: stampindex.NewItemWithKeys("test_namespace", []byte{swarm.HashSize - 1: 9}, nil, nil), 58 Factory: func() storage.Item { return new(stampindex.Item) }, 59 MarshalErr: stampindex.ErrStampItemMarshalBatchIndexInvalid, 60 CmpOpts: []cmp.Option{cmp.AllowUnexported(stampindex.Item{})}, 61 }, 62 }, { 63 name: "valid values", 64 test: &storagetest.ItemMarshalAndUnmarshalTest{ 65 Item: stampindex.NewItemWithValues([]byte{swarm.StampTimestampSize - 1: 9}, swarm.RandAddress(t)), 66 Factory: func() storage.Item { return new(stampindex.Item) }, 67 CmpOpts: []cmp.Option{cmp.AllowUnexported(stampindex.Item{})}, 68 }, 69 }, { 70 name: "max values", 71 test: &storagetest.ItemMarshalAndUnmarshalTest{ 72 Item: stampindex.NewItemWithValues(storagetest.MaxBatchTimestampBytes[:], swarm.NewAddress(storagetest.MaxAddressBytes[:])), 73 Factory: func() storage.Item { return new(stampindex.Item) }, 74 CmpOpts: []cmp.Option{cmp.AllowUnexported(stampindex.Item{})}, 75 }, 76 }, { 77 name: "invalid size", 78 test: &storagetest.ItemMarshalAndUnmarshalTest{ 79 Item: &storagetest.ItemStub{ 80 MarshalBuf: []byte{0xFF}, 81 UnmarshalBuf: []byte{0xFF}, 82 }, 83 Factory: func() storage.Item { return new(stampindex.Item) }, 84 UnmarshalErr: stampindex.ErrStampItemUnmarshalInvalidSize, 85 CmpOpts: []cmp.Option{cmp.AllowUnexported(stampindex.Item{})}, 86 }, 87 }} 88 89 for _, tc := range tests { 90 tc := tc 91 92 t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) { 93 t.Parallel() 94 95 storagetest.TestItemMarshalAndUnmarshal(t, tc.test) 96 }) 97 98 t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) { 99 t.Parallel() 100 101 storagetest.TestItemClone(t, &storagetest.ItemCloneTest{ 102 Item: tc.test.Item, 103 CmpOpts: tc.test.CmpOpts, 104 }) 105 }) 106 } 107 } 108 109 func TestStoreLoadDeleteWithStamp(t *testing.T) { 110 t.Parallel() 111 112 ts := newTestStorage(t) 113 chunks := chunktest.GenerateTestRandomChunks(10) 114 115 for i, chunk := range chunks { 116 ns := fmt.Sprintf("namespace_%d", i) 117 t.Run(ns, func(t *testing.T) { 118 t.Run("store new stamp index", func(t *testing.T) { 119 120 err := ts.Run(context.Background(), func(s transaction.Store) error { 121 return stampindex.Store(s.IndexStore(), ns, chunk) 122 123 }) 124 if err != nil { 125 t.Fatalf("Store(...): unexpected error: %v", err) 126 } 127 128 stampHash, err := chunk.Stamp().Hash() 129 if err != nil { 130 t.Fatal(err) 131 } 132 want := stampindex.NewItemWithKeys(ns, chunk.Stamp().BatchID(), chunk.Stamp().Index(), stampHash) 133 want.StampTimestamp = chunk.Stamp().Timestamp() 134 want.ChunkAddress = chunk.Address() 135 136 have := stampindex.NewItemWithKeys(ns, chunk.Stamp().BatchID(), chunk.Stamp().Index(), stampHash) 137 err = ts.IndexStore().Get(have) 138 if err != nil { 139 t.Fatalf("Get(...): unexpected error: %v", err) 140 } 141 142 if diff := cmp.Diff(want, have, cmp.AllowUnexported(stampindex.Item{})); diff != "" { 143 t.Fatalf("Get(...): mismatch (-want +have):\n%s", diff) 144 } 145 }) 146 147 t.Run("load stored stamp index", func(t *testing.T) { 148 stampHash, err := chunk.Stamp().Hash() 149 if err != nil { 150 t.Fatal(err) 151 } 152 want := stampindex.NewItemWithKeys(ns, chunk.Stamp().BatchID(), chunk.Stamp().Index(), stampHash) 153 want.StampTimestamp = chunk.Stamp().Timestamp() 154 want.ChunkAddress = chunk.Address() 155 156 have, err := stampindex.Load(ts.IndexStore(), ns, chunk.Stamp()) 157 if err != nil { 158 t.Fatalf("Load(...): unexpected error: %v", err) 159 } 160 161 if diff := cmp.Diff(want, have, cmp.AllowUnexported(stampindex.Item{})); diff != "" { 162 t.Fatalf("Load(...): mismatch (-want +have):\n%s", diff) 163 } 164 }) 165 166 t.Run("delete stored stamp index", func(t *testing.T) { 167 168 err := ts.Run(context.Background(), func(s transaction.Store) error { 169 return stampindex.Delete(s.IndexStore(), ns, chunk.Stamp()) 170 }) 171 if err != nil { 172 t.Fatalf("Delete(...): unexpected error: %v", err) 173 } 174 175 have, err := stampindex.Load(ts.IndexStore(), ns, chunk.Stamp()) 176 if have != nil { 177 t.Fatalf("Load(...): unexpected item %v", have) 178 } 179 if !errors.Is(err, storage.ErrNotFound) { 180 t.Fatalf("Load(...): unexpected error: %v", err) 181 } 182 183 cnt := 0 184 err = ts.IndexStore().Iterate( 185 storage.Query{ 186 Factory: func() storage.Item { 187 return new(stampindex.Item) 188 }, 189 }, 190 func(result storage.Result) (bool, error) { 191 cnt++ 192 return false, nil 193 }, 194 ) 195 if err != nil { 196 t.Fatalf("Store().Iterate(...): unexpected error: %v", err) 197 } 198 if want, have := 0, cnt; want != have { 199 t.Fatalf("Store().Iterate(...): chunk count mismatch:\nwant: %d\nhave: %d", want, have) 200 } 201 }) 202 }) 203 } 204 } 205 206 func TestLoadOrStore(t *testing.T) { 207 t.Parallel() 208 209 ts := newTestStorage(t) 210 chunks := chunktest.GenerateTestRandomChunks(10) 211 212 for i, chunk := range chunks { 213 ns := fmt.Sprintf("namespace_%d", i) 214 t.Run(ns, func(t *testing.T) { 215 stampHash, err := chunk.Stamp().Hash() 216 if err != nil { 217 t.Fatal(err) 218 } 219 want := stampindex.NewItemWithKeys(ns, chunk.Stamp().BatchID(), chunk.Stamp().Index(), stampHash) 220 want.StampTimestamp = chunk.Stamp().Timestamp() 221 want.ChunkAddress = chunk.Address() 222 223 trx, done := ts.NewTransaction(context.Background()) 224 225 have, loaded, err := stampindex.LoadOrStore(trx.IndexStore(), ns, chunk) 226 if err != nil { 227 t.Fatalf("LoadOrStore(...): unexpected error: %v", err) 228 } 229 if loaded { 230 t.Fatalf("LoadOrStore(...): unexpected loaded flag") 231 } 232 if diff := cmp.Diff(want, have, cmp.AllowUnexported(stampindex.Item{})); diff != "" { 233 t.Fatalf("Get(...): mismatch (-want +have):\n%s", diff) 234 } 235 assert.NoError(t, trx.Commit()) 236 done() 237 238 trx, done = ts.NewTransaction(context.Background()) 239 defer done() 240 241 have, loaded, err = stampindex.LoadOrStore(trx.IndexStore(), ns, chunk) 242 if err != nil { 243 t.Fatalf("LoadOrStore(...): unexpected error: %v", err) 244 } 245 if !loaded { 246 t.Fatalf("LoadOrStore(...): unexpected loaded flag") 247 } 248 249 if diff := cmp.Diff(want, have, cmp.AllowUnexported(stampindex.Item{})); diff != "" { 250 t.Fatalf("Get(...): mismatch (-want +have):\n%s", diff) 251 } 252 assert.NoError(t, trx.Commit()) 253 254 cnt := 0 255 err = ts.IndexStore().Iterate( 256 storage.Query{ 257 Factory: func() storage.Item { 258 return new(stampindex.Item) 259 }, 260 }, 261 func(result storage.Result) (bool, error) { 262 cnt++ 263 return false, nil 264 }, 265 ) 266 if err != nil { 267 t.Fatalf("Store().Iterate(...): unexpected error: %v", err) 268 } 269 if want, have := i+1, cnt; want != have { 270 t.Fatalf("Store().Iterate(...): chunk count mismatch:\nwant: %d\nhave: %d", want, have) 271 } 272 }) 273 } 274 }