github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/internal/pinning/pinning_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 pinstore_test
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"math"
    12  	"testing"
    13  
    14  	storage "github.com/ethersphere/bee/v2/pkg/storage"
    15  	"github.com/ethersphere/bee/v2/pkg/storer/internal/transaction"
    16  
    17  	storagetest "github.com/ethersphere/bee/v2/pkg/storage/storagetest"
    18  	chunktest "github.com/ethersphere/bee/v2/pkg/storage/testing"
    19  	"github.com/ethersphere/bee/v2/pkg/storer/internal"
    20  	pinstore "github.com/ethersphere/bee/v2/pkg/storer/internal/pinning"
    21  	"github.com/ethersphere/bee/v2/pkg/swarm"
    22  )
    23  
    24  type pinningCollection struct {
    25  	root         swarm.Chunk
    26  	uniqueChunks []swarm.Chunk
    27  	dupChunks    []swarm.Chunk
    28  }
    29  
    30  func newTestStorage(t *testing.T) transaction.Storage {
    31  	t.Helper()
    32  	storg := internal.NewInmemStorage()
    33  	return storg
    34  }
    35  
    36  func TestPinStore(t *testing.T) {
    37  
    38  	tests := make([]pinningCollection, 0, 3)
    39  
    40  	for _, tc := range []struct {
    41  		dupChunks    int
    42  		uniqueChunks int
    43  	}{
    44  		{
    45  			dupChunks:    5,
    46  			uniqueChunks: 10,
    47  		},
    48  		{
    49  			dupChunks:    10,
    50  			uniqueChunks: 20,
    51  		},
    52  		{
    53  			dupChunks:    15,
    54  			uniqueChunks: 130,
    55  		},
    56  	} {
    57  		var c pinningCollection
    58  		c.root = chunktest.GenerateTestRandomChunk()
    59  		c.uniqueChunks = chunktest.GenerateTestRandomChunks(tc.uniqueChunks)
    60  		dupChunk := chunktest.GenerateTestRandomChunk()
    61  		for i := 0; i < tc.dupChunks; i++ {
    62  			c.dupChunks = append(c.dupChunks, dupChunk)
    63  		}
    64  		tests = append(tests, c)
    65  	}
    66  
    67  	st := newTestStorage(t)
    68  
    69  	t.Run("create new collections", func(t *testing.T) {
    70  		for tCount, tc := range tests {
    71  			t.Run(fmt.Sprintf("create collection %d", tCount), func(t *testing.T) {
    72  
    73  				var putter internal.PutterCloserWithReference
    74  				var err error
    75  				err = st.Run(context.Background(), func(s transaction.Store) error {
    76  					putter, err = pinstore.NewCollection(s.IndexStore())
    77  					return err
    78  				})
    79  				if err != nil {
    80  					t.Fatal(err)
    81  				}
    82  
    83  				for _, ch := range append(tc.uniqueChunks, tc.root) {
    84  					if err := st.Run(context.Background(), func(s transaction.Store) error {
    85  						return putter.Put(context.Background(), s, ch)
    86  					}); err != nil {
    87  						t.Fatal(err)
    88  					}
    89  				}
    90  				for _, ch := range tc.dupChunks {
    91  					if err := st.Run(context.Background(), func(s transaction.Store) error {
    92  						return putter.Put(context.Background(), s, ch)
    93  					}); err != nil {
    94  						t.Fatal(err)
    95  					}
    96  				}
    97  
    98  				if err := st.Run(context.Background(), func(s transaction.Store) error {
    99  					return putter.Close(s.IndexStore(), tc.root.Address())
   100  				}); err != nil {
   101  					t.Fatal(err)
   102  				}
   103  			})
   104  		}
   105  	})
   106  
   107  	t.Run("verify all collection data", func(t *testing.T) {
   108  		for tCount, tc := range tests {
   109  			t.Run(fmt.Sprintf("verify collection %d", tCount), func(t *testing.T) {
   110  				allChunks := append(tc.uniqueChunks, tc.root)
   111  				allChunks = append(allChunks, tc.dupChunks...)
   112  				for _, ch := range allChunks {
   113  					exists, err := st.ChunkStore().Has(context.TODO(), ch.Address())
   114  					if err != nil {
   115  						t.Fatal(err)
   116  					}
   117  					if !exists {
   118  						t.Fatal("chunk should exist")
   119  					}
   120  					rch, err := st.ChunkStore().Get(context.TODO(), ch.Address())
   121  					if err != nil {
   122  						t.Fatal(err)
   123  					}
   124  					if !ch.Equal(rch) {
   125  						t.Fatal("read chunk not equal")
   126  					}
   127  				}
   128  			})
   129  		}
   130  	})
   131  
   132  	t.Run("verify root pins", func(t *testing.T) {
   133  		pins, err := pinstore.Pins(st.IndexStore())
   134  		if err != nil {
   135  			t.Fatal(err)
   136  		}
   137  		if len(pins) != 3 {
   138  			t.Fatalf("incorrect no of root pins, expected 3 found %d", len(pins))
   139  		}
   140  		for _, tc := range tests {
   141  			found := false
   142  			for _, f := range pins {
   143  				if f.Equal(tc.root.Address()) {
   144  					found = true
   145  					break
   146  				}
   147  			}
   148  			if !found {
   149  				t.Fatalf("pin %s not found", tc.root.Address())
   150  			}
   151  		}
   152  	})
   153  
   154  	t.Run("has pin", func(t *testing.T) {
   155  		for _, tc := range tests {
   156  			found, err := pinstore.HasPin(st.IndexStore(), tc.root.Address())
   157  			if err != nil {
   158  				t.Fatal(err)
   159  			}
   160  			if !found {
   161  				t.Fatalf("expected the pin %s to be found", tc.root.Address())
   162  			}
   163  		}
   164  	})
   165  
   166  	t.Run("verify internal state", func(t *testing.T) {
   167  		for _, tc := range tests {
   168  			count := 0
   169  			err := pinstore.IterateCollection(st.IndexStore(), tc.root.Address(), func(addr swarm.Address) (bool, error) {
   170  				count++
   171  				return false, nil
   172  			})
   173  			if err != nil {
   174  				t.Fatal(err)
   175  			}
   176  			if count != len(tc.uniqueChunks)+2 {
   177  				t.Fatalf("incorrect no of chunks in collection, expected %d found %d", len(tc.uniqueChunks)+2, count)
   178  			}
   179  			stat, err := pinstore.GetStat(st.IndexStore(), tc.root.Address())
   180  			if err != nil {
   181  				t.Fatal(err)
   182  			}
   183  			if stat.Total != uint64(len(tc.uniqueChunks)+len(tc.dupChunks)+1) {
   184  				t.Fatalf("incorrect no of chunks, expected %d found %d", len(tc.uniqueChunks)+len(tc.dupChunks)+1, stat.Total)
   185  			}
   186  			if stat.DupInCollection != uint64(len(tc.dupChunks)-1) {
   187  				t.Fatalf("incorrect no of duplicate chunks, expected %d found %d", len(tc.dupChunks)-1, stat.DupInCollection)
   188  			}
   189  		}
   190  	})
   191  
   192  	t.Run("iterate stats", func(t *testing.T) {
   193  		count, total, dup := 0, 0, 0
   194  		err := pinstore.IterateCollectionStats(st.IndexStore(), func(stat pinstore.CollectionStat) (bool, error) {
   195  			count++
   196  			total += int(stat.Total)
   197  			dup += int(stat.DupInCollection)
   198  
   199  			return false, nil
   200  		})
   201  		if err != nil {
   202  			t.Fatalf("IterateCollectionStats: unexpected error: %v", err)
   203  		}
   204  
   205  		wantTotal, wantDup := 0, 0
   206  		for _, tc := range tests {
   207  			wantTotal += len(tc.uniqueChunks) + len(tc.dupChunks) + 1
   208  			wantDup += len(tc.dupChunks) - 1
   209  		}
   210  
   211  		if count != len(tests) {
   212  			t.Fatalf("unexpected collection count: want %d have: %d", len(tests), count)
   213  		}
   214  		if wantTotal != total {
   215  			t.Fatalf("unexpected total count: want %d have: %d", wantTotal, total)
   216  		}
   217  		if wantDup != dup {
   218  			t.Fatalf("unexpected dup count: want %d have: %d", wantDup, dup)
   219  		}
   220  	})
   221  
   222  	t.Run("delete collection", func(t *testing.T) {
   223  		err := pinstore.DeletePin(context.TODO(), st, tests[0].root.Address())
   224  		if err != nil {
   225  			t.Fatal(err)
   226  		}
   227  
   228  		found, err := pinstore.HasPin(st.IndexStore(), tests[0].root.Address())
   229  		if err != nil {
   230  			t.Fatal(err)
   231  		}
   232  		if found {
   233  			t.Fatal("expected pin to not be found")
   234  		}
   235  
   236  		pins, err := pinstore.Pins(st.IndexStore())
   237  		if err != nil {
   238  			t.Fatal(err)
   239  		}
   240  		if len(pins) != 2 {
   241  			t.Fatalf("incorrect no of root pins, expected 2 found %d", len(pins))
   242  		}
   243  
   244  		allChunks := append(tests[0].uniqueChunks, tests[0].root)
   245  		allChunks = append(allChunks, tests[0].dupChunks...)
   246  		for _, ch := range allChunks {
   247  			exists, err := st.ChunkStore().Has(context.TODO(), ch.Address())
   248  			if err != nil {
   249  				t.Fatal(err)
   250  			}
   251  			if exists {
   252  				t.Fatal("chunk should not exist")
   253  			}
   254  			_, err = st.ChunkStore().Get(context.TODO(), ch.Address())
   255  			if !errors.Is(err, storage.ErrNotFound) {
   256  				t.Fatal(err)
   257  			}
   258  		}
   259  	})
   260  
   261  	t.Run("error after close", func(t *testing.T) {
   262  		root := chunktest.GenerateTestRandomChunk()
   263  
   264  		var (
   265  			putter internal.PutterCloserWithReference
   266  			err    error
   267  		)
   268  		err = st.Run(context.Background(), func(s transaction.Store) error {
   269  			putter, err = pinstore.NewCollection(s.IndexStore())
   270  			return err
   271  		})
   272  		if err != nil {
   273  			t.Fatal(err)
   274  		}
   275  
   276  		err = st.Run(context.Background(), func(s transaction.Store) error {
   277  			return putter.Put(context.Background(), s, root)
   278  		})
   279  		if err != nil {
   280  			t.Fatal(err)
   281  		}
   282  
   283  		err = st.Run(context.Background(), func(s transaction.Store) error {
   284  			return putter.Close(s.IndexStore(), root.Address())
   285  		})
   286  		if err != nil {
   287  			t.Fatal(err)
   288  		}
   289  
   290  		err = st.Run(context.Background(), func(s transaction.Store) error {
   291  			return putter.Put(context.Background(), s, chunktest.GenerateTestRandomChunk())
   292  		})
   293  		if !errors.Is(err, pinstore.ErrPutterAlreadyClosed) {
   294  			t.Fatalf("unexpected error during Put, want: %v, got: %v", pinstore.ErrPutterAlreadyClosed, err)
   295  		}
   296  	})
   297  
   298  	t.Run("duplicate collection", func(t *testing.T) {
   299  		root := chunktest.GenerateTestRandomChunk()
   300  
   301  		var (
   302  			putter internal.PutterCloserWithReference
   303  			err    error
   304  		)
   305  		err = st.Run(context.Background(), func(s transaction.Store) error {
   306  			putter, err = pinstore.NewCollection(s.IndexStore())
   307  			return err
   308  		})
   309  		if err != nil {
   310  			t.Fatal(err)
   311  		}
   312  
   313  		err = st.Run(context.Background(), func(s transaction.Store) error {
   314  			return putter.Put(context.Background(), s, root)
   315  		})
   316  		if err != nil {
   317  			t.Fatal(err)
   318  		}
   319  
   320  		err = st.Run(context.Background(), func(s transaction.Store) error {
   321  			return putter.Close(s.IndexStore(), root.Address())
   322  		})
   323  		if err != nil {
   324  			t.Fatal(err)
   325  		}
   326  
   327  		err = st.Run(context.Background(), func(s transaction.Store) error {
   328  			return putter.Close(s.IndexStore(), root.Address())
   329  		})
   330  		if err == nil || !errors.Is(err, pinstore.ErrDuplicatePinCollection) {
   331  			t.Fatalf("unexpected error during CLose, want: %v, got: %v", pinstore.ErrDuplicatePinCollection, err)
   332  		}
   333  	})
   334  
   335  	t.Run("zero address close", func(t *testing.T) {
   336  		root := chunktest.GenerateTestRandomChunk()
   337  
   338  		var (
   339  			putter internal.PutterCloserWithReference
   340  			err    error
   341  		)
   342  		err = st.Run(context.Background(), func(s transaction.Store) error {
   343  			putter, err = pinstore.NewCollection(s.IndexStore())
   344  			return err
   345  		})
   346  		if err != nil {
   347  			t.Fatal(err)
   348  		}
   349  
   350  		err = st.Run(context.Background(), func(s transaction.Store) error {
   351  			return putter.Put(context.Background(), s, root)
   352  		})
   353  		if err != nil {
   354  			t.Fatal(err)
   355  		}
   356  
   357  		err = st.Run(context.Background(), func(s transaction.Store) error {
   358  			return putter.Close(s.IndexStore(), swarm.ZeroAddress)
   359  		})
   360  		if !errors.Is(err, pinstore.ErrCollectionRootAddressIsZero) {
   361  			t.Fatalf("unexpected error on close, want: %v, got: %v", pinstore.ErrCollectionRootAddressIsZero, err)
   362  		}
   363  	})
   364  }
   365  
   366  func TestCleanup(t *testing.T) {
   367  	t.Parallel()
   368  
   369  	t.Run("cleanup putter", func(t *testing.T) {
   370  		t.Parallel()
   371  
   372  		st := newTestStorage(t)
   373  		chunks := chunktest.GenerateTestRandomChunks(5)
   374  
   375  		var (
   376  			putter internal.PutterCloserWithReference
   377  			err    error
   378  		)
   379  		err = st.Run(context.Background(), func(s transaction.Store) error {
   380  			putter, err = pinstore.NewCollection(s.IndexStore())
   381  			return err
   382  		})
   383  		if err != nil {
   384  			t.Fatal(err)
   385  		}
   386  
   387  		for _, ch := range chunks {
   388  			err = st.Run(context.Background(), func(s transaction.Store) error {
   389  				return putter.Put(context.Background(), s, ch)
   390  			})
   391  			if err != nil {
   392  				t.Fatal(err)
   393  			}
   394  		}
   395  
   396  		err = putter.Cleanup(st)
   397  		if err != nil {
   398  			t.Fatal(err)
   399  		}
   400  
   401  		for _, ch := range chunks {
   402  			exists, err := st.ChunkStore().Has(context.Background(), ch.Address())
   403  			if err != nil {
   404  				t.Fatal(err)
   405  			}
   406  			if exists {
   407  				t.Fatal("chunk should not exist")
   408  			}
   409  		}
   410  	})
   411  
   412  	t.Run("cleanup dirty", func(t *testing.T) {
   413  		t.Parallel()
   414  
   415  		st := newTestStorage(t)
   416  		chunks := chunktest.GenerateTestRandomChunks(5)
   417  
   418  		var (
   419  			putter internal.PutterCloserWithReference
   420  			err    error
   421  		)
   422  		err = st.Run(context.Background(), func(s transaction.Store) error {
   423  			putter, err = pinstore.NewCollection(s.IndexStore())
   424  			return err
   425  		})
   426  		if err != nil {
   427  			t.Fatal(err)
   428  		}
   429  
   430  		for _, ch := range chunks {
   431  			err = st.Run(context.Background(), func(s transaction.Store) error {
   432  				return putter.Put(context.Background(), s, ch)
   433  			})
   434  			if err != nil {
   435  				t.Fatal(err)
   436  			}
   437  		}
   438  
   439  		err = pinstore.CleanupDirty(st)
   440  		if err != nil {
   441  			t.Fatal(err)
   442  		}
   443  
   444  		for _, ch := range chunks {
   445  			exists, err := st.ChunkStore().Has(context.Background(), ch.Address())
   446  			if err != nil {
   447  				t.Fatal(err)
   448  			}
   449  			if exists {
   450  				t.Fatal("chunk should not exist")
   451  			}
   452  		}
   453  	})
   454  }
   455  
   456  func TestPinCollectionItem(t *testing.T) {
   457  	t.Parallel()
   458  
   459  	tests := []struct {
   460  		name string
   461  		test *storagetest.ItemMarshalAndUnmarshalTest
   462  	}{{
   463  		name: "zero values",
   464  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   465  			Item:       &pinstore.PinCollectionItem{},
   466  			Factory:    func() storage.Item { return new(pinstore.PinCollectionItem) },
   467  			MarshalErr: pinstore.ErrInvalidPinCollectionItemAddr,
   468  		},
   469  	}, {
   470  		name: "zero address",
   471  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   472  			Item: &pinstore.PinCollectionItem{
   473  				Addr: swarm.ZeroAddress,
   474  			},
   475  			Factory:    func() storage.Item { return new(pinstore.PinCollectionItem) },
   476  			MarshalErr: pinstore.ErrInvalidPinCollectionItemAddr,
   477  		},
   478  	}, {
   479  		name: "zero UUID",
   480  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   481  			Item: &pinstore.PinCollectionItem{
   482  				Addr: swarm.NewAddress(storagetest.MinAddressBytes[:]),
   483  			},
   484  			Factory:    func() storage.Item { return new(pinstore.PinCollectionItem) },
   485  			MarshalErr: pinstore.ErrInvalidPinCollectionItemUUID,
   486  		},
   487  	}, {
   488  		name: "valid values",
   489  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   490  			Item: &pinstore.PinCollectionItem{
   491  				Addr: swarm.NewAddress(storagetest.MinAddressBytes[:]),
   492  				UUID: pinstore.NewUUID(),
   493  			},
   494  			Factory: func() storage.Item { return new(pinstore.PinCollectionItem) },
   495  		},
   496  	}, {
   497  		name: "max values",
   498  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   499  			Item: &pinstore.PinCollectionItem{
   500  				Addr: swarm.NewAddress(storagetest.MaxEncryptedRefBytes[:]),
   501  				UUID: pinstore.NewUUID(),
   502  				Stat: pinstore.CollectionStat{
   503  					Total:           math.MaxUint64,
   504  					DupInCollection: math.MaxUint64,
   505  				},
   506  			},
   507  			Factory: func() storage.Item { return new(pinstore.PinCollectionItem) },
   508  		},
   509  	}, {
   510  		name: "invalid size",
   511  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   512  			Item: &storagetest.ItemStub{
   513  				MarshalBuf:   []byte{0xFF},
   514  				UnmarshalBuf: []byte{0xFF},
   515  			},
   516  			Factory:      func() storage.Item { return new(pinstore.PinCollectionItem) },
   517  			UnmarshalErr: pinstore.ErrInvalidPinCollectionItemSize,
   518  		},
   519  	}}
   520  
   521  	for _, tc := range tests {
   522  		tc := tc
   523  
   524  		t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) {
   525  			t.Parallel()
   526  
   527  			storagetest.TestItemMarshalAndUnmarshal(t, tc.test)
   528  		})
   529  
   530  		t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) {
   531  			t.Parallel()
   532  
   533  			storagetest.TestItemClone(t, &storagetest.ItemCloneTest{
   534  				Item:    tc.test.Item,
   535  				CmpOpts: tc.test.CmpOpts,
   536  			})
   537  		})
   538  	}
   539  }
   540  
   541  func TestPinChunkItem(t *testing.T) {
   542  	t.Parallel()
   543  
   544  	storagetest.TestItemClone(t, &storagetest.ItemCloneTest{
   545  		Item: &pinstore.PinChunkItem{
   546  			UUID: pinstore.NewUUID(),
   547  			Addr: swarm.RandAddress(t),
   548  		},
   549  	})
   550  }
   551  
   552  func TestDirtyCollectionsItem(t *testing.T) {
   553  	t.Parallel()
   554  
   555  	storagetest.TestItemClone(t, &storagetest.ItemCloneTest{
   556  		Item: &pinstore.DirtyCollection{
   557  			UUID: pinstore.NewUUID(),
   558  		},
   559  	})
   560  }