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  }