github.com/ethersphere/bee/v2@v2.2.0/pkg/storage/storagetest/chunkstore.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 storagetest
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"testing"
    11  
    12  	postagetesting "github.com/ethersphere/bee/v2/pkg/postage/testing"
    13  	storage "github.com/ethersphere/bee/v2/pkg/storage"
    14  	chunktest "github.com/ethersphere/bee/v2/pkg/storage/testing"
    15  	"github.com/ethersphere/bee/v2/pkg/swarm"
    16  )
    17  
    18  // TestChunkStore runs a correctness test suite on a given ChunkStore.
    19  func TestChunkStore(t *testing.T, st storage.ChunkStore) {
    20  	t.Helper()
    21  
    22  	testChunks := chunktest.GenerateTestRandomChunks(50)
    23  
    24  	t.Run("put chunks", func(t *testing.T) {
    25  		for _, ch := range testChunks {
    26  			err := st.Put(context.TODO(), ch)
    27  			if err != nil {
    28  				t.Fatalf("failed putting new chunk: %v", err)
    29  			}
    30  		}
    31  	})
    32  
    33  	t.Run("put existing chunks", func(t *testing.T) {
    34  		for _, ch := range testChunks {
    35  			err := st.Put(context.TODO(), ch)
    36  			if err != nil {
    37  				t.Fatalf("failed putting new chunk: %v", err)
    38  			}
    39  		}
    40  	})
    41  
    42  	t.Run("get chunks", func(t *testing.T) {
    43  		for _, ch := range testChunks {
    44  			readCh, err := st.Get(context.TODO(), ch.Address())
    45  			if err != nil {
    46  				t.Fatalf("failed getting chunk: %v", err)
    47  			}
    48  			if !readCh.Equal(ch) {
    49  				t.Fatal("read chunk doesn't match")
    50  			}
    51  		}
    52  	})
    53  
    54  	t.Run("get non-existing chunk", func(t *testing.T) {
    55  		stamp := postagetesting.MustNewStamp()
    56  		ch := chunktest.GenerateTestRandomChunk().WithStamp(stamp)
    57  
    58  		_, err := st.Get(context.TODO(), ch.Address())
    59  		if !errors.Is(err, storage.ErrNotFound) {
    60  			t.Fatalf("expected error %v", storage.ErrNotFound)
    61  		}
    62  	})
    63  
    64  	t.Run("has chunks", func(t *testing.T) {
    65  		for _, ch := range testChunks {
    66  			exists, err := st.Has(context.TODO(), ch.Address())
    67  			if err != nil {
    68  				t.Fatalf("failed getting chunk: %v", err)
    69  			}
    70  			if !exists {
    71  				t.Fatalf("chunk not found: %s", ch.Address())
    72  			}
    73  		}
    74  	})
    75  
    76  	t.Run("iterate chunks", func(t *testing.T) {
    77  		count := 0
    78  		err := st.Iterate(context.TODO(), func(_ swarm.Chunk) (bool, error) {
    79  			count++
    80  			return false, nil
    81  		})
    82  		if err != nil {
    83  			t.Fatalf("unexpected error while iteration: %v", err)
    84  		}
    85  		if count != 50 {
    86  			t.Fatalf("unexpected no of chunks, exp: %d, found: %d", 50, count)
    87  		}
    88  	})
    89  
    90  	t.Run("delete chunks", func(t *testing.T) {
    91  		for idx, ch := range testChunks {
    92  			// Delete all even numbered indexes along with 0
    93  			if idx%2 == 0 {
    94  				err := st.Delete(context.TODO(), ch.Address())
    95  				if err != nil {
    96  					t.Fatalf("failed deleting chunk: %v", err)
    97  				}
    98  				_, err = st.Get(context.TODO(), ch.Address())
    99  				if err != nil {
   100  					t.Fatalf("expected no error, found: %v", err)
   101  				}
   102  				// delete twice as it was put twice
   103  				err = st.Delete(context.TODO(), ch.Address())
   104  				if err != nil {
   105  					t.Fatalf("failed deleting chunk: %v", err)
   106  				}
   107  			}
   108  		}
   109  	})
   110  
   111  	t.Run("check deleted chunks", func(t *testing.T) {
   112  		for idx, ch := range testChunks {
   113  			if idx%2 == 0 {
   114  				// Check even numbered indexes are deleted
   115  				_, err := st.Get(context.TODO(), ch.Address())
   116  				if !errors.Is(err, storage.ErrNotFound) {
   117  					t.Fatalf("expected storage not found error found: %v", err)
   118  				}
   119  				found, err := st.Has(context.TODO(), ch.Address())
   120  				if err != nil {
   121  					t.Fatalf("unexpected error in Has: %v", err)
   122  				}
   123  				if found {
   124  					t.Fatal("expected chunk to not be found")
   125  				}
   126  			} else {
   127  				// Check rest of the entries are intact
   128  				readCh, err := st.Get(context.TODO(), ch.Address())
   129  				if err != nil {
   130  					t.Fatalf("failed getting chunk: %v", err)
   131  				}
   132  				if !readCh.Equal(ch) {
   133  					t.Fatal("read chunk doesn't match")
   134  				}
   135  				exists, err := st.Has(context.TODO(), ch.Address())
   136  				if err != nil {
   137  					t.Fatalf("failed getting chunk: %v", err)
   138  				}
   139  				if !exists {
   140  					t.Fatalf("chunk not found: %s", ch.Address())
   141  				}
   142  			}
   143  		}
   144  	})
   145  
   146  	t.Run("iterate chunks after delete", func(t *testing.T) {
   147  		count := 0
   148  		err := st.Iterate(context.TODO(), func(_ swarm.Chunk) (bool, error) {
   149  			count++
   150  			return false, nil
   151  		})
   152  		if err != nil {
   153  			t.Fatalf("unexpected error while iteration: %v", err)
   154  		}
   155  		if count != 25 {
   156  			t.Fatalf("unexpected no of chunks, exp: %d, found: %d", 25, count)
   157  		}
   158  	})
   159  }
   160  
   161  func RunChunkStoreBenchmarkTests(b *testing.B, s storage.ChunkStore) {
   162  	b.Helper()
   163  
   164  	b.Run("WriteSequential", func(b *testing.B) {
   165  		BenchmarkChunkStoreWriteSequential(b, s)
   166  	})
   167  	b.Run("WriteRandom", func(b *testing.B) {
   168  		BenchmarkChunkStoreWriteRandom(b, s)
   169  	})
   170  	b.Run("ReadSequential", func(b *testing.B) {
   171  		BenchmarkChunkStoreReadSequential(b, s)
   172  	})
   173  	b.Run("ReadRandom", func(b *testing.B) {
   174  		BenchmarkChunkStoreReadRandom(b, s)
   175  	})
   176  	b.Run("ReadRandomMissing", func(b *testing.B) {
   177  		BenchmarkChunkStoreReadRandomMissing(b, s)
   178  	})
   179  	b.Run("ReadReverse", func(b *testing.B) {
   180  		BenchmarkChunkStoreReadReverse(b, s)
   181  	})
   182  	b.Run("ReadRedHot", func(b *testing.B) {
   183  		BenchmarkChunkStoreReadHot(b, s)
   184  	})
   185  	b.Run("IterateSequential", func(b *testing.B) {
   186  		BenchmarkChunkStoreIterateSequential(b, s)
   187  	})
   188  	b.Run("IterateReverse", func(b *testing.B) {
   189  		BenchmarkChunkStoreIterateReverse(b, s)
   190  	})
   191  	b.Run("DeleteRandom", func(b *testing.B) {
   192  		BenchmarkChunkStoreDeleteRandom(b, s)
   193  	})
   194  	b.Run("DeleteSequential", func(b *testing.B) {
   195  		BenchmarkChunkStoreDeleteSequential(b, s)
   196  	})
   197  }
   198  
   199  func BenchmarkChunkStoreWriteSequential(b *testing.B, s storage.Putter) {
   200  	b.Helper()
   201  
   202  	doWriteChunk(b, s, newSequentialEntryGenerator(b.N))
   203  }
   204  
   205  func BenchmarkChunkStoreWriteRandom(b *testing.B, s storage.Putter) {
   206  	b.Helper()
   207  
   208  	doWriteChunk(b, s, newFullRandomEntryGenerator(0, b.N))
   209  }
   210  
   211  func BenchmarkChunkStoreReadSequential(b *testing.B, s storage.ChunkStore) {
   212  	b.Helper()
   213  
   214  	g := newSequentialKeyGenerator(b.N)
   215  	doWriteChunk(b, s, newFullRandomEntryGenerator(0, b.N))
   216  	resetBenchmark(b)
   217  	doReadChunk(b, s, g, false)
   218  }
   219  
   220  func BenchmarkChunkStoreReadRandom(b *testing.B, s storage.ChunkStore) {
   221  	b.Helper()
   222  
   223  	g := newRandomKeyGenerator(b.N)
   224  	doWriteChunk(b, s, newFullRandomEntryGenerator(0, b.N))
   225  	resetBenchmark(b)
   226  	doReadChunk(b, s, g, false)
   227  }
   228  
   229  func BenchmarkChunkStoreReadRandomMissing(b *testing.B, s storage.ChunkStore) {
   230  	b.Helper()
   231  
   232  	g := newRandomMissingKeyGenerator(b.N)
   233  	resetBenchmark(b)
   234  	doReadChunk(b, s, g, true)
   235  }
   236  
   237  func BenchmarkChunkStoreReadReverse(b *testing.B, db storage.ChunkStore) {
   238  	b.Helper()
   239  
   240  	g := newReversedKeyGenerator(newSequentialKeyGenerator(b.N))
   241  	doWriteChunk(b, db, newFullRandomEntryGenerator(0, b.N))
   242  	resetBenchmark(b)
   243  	doReadChunk(b, db, g, false)
   244  }
   245  
   246  func BenchmarkChunkStoreReadHot(b *testing.B, s storage.ChunkStore) {
   247  	b.Helper()
   248  
   249  	k := maxInt((b.N+99)/100, 1)
   250  	g := newRoundKeyGenerator(newRandomKeyGenerator(k))
   251  	doWriteChunk(b, s, newFullRandomEntryGenerator(0, b.N))
   252  	resetBenchmark(b)
   253  	doReadChunk(b, s, g, false)
   254  }
   255  
   256  func BenchmarkChunkStoreIterateSequential(b *testing.B, s storage.ChunkStore) {
   257  	b.Helper()
   258  
   259  	var counter int
   260  	_ = s.Iterate(context.Background(), func(c swarm.Chunk) (stop bool, err error) {
   261  		counter++
   262  		if counter > b.N {
   263  			return true, nil
   264  		}
   265  		return false, nil
   266  	})
   267  }
   268  
   269  func BenchmarkChunkStoreIterateReverse(b *testing.B, s storage.ChunkStore) {
   270  	b.Helper()
   271  
   272  	b.Skip("not implemented")
   273  }
   274  
   275  func BenchmarkChunkStoreDeleteRandom(b *testing.B, s storage.ChunkStore) {
   276  	b.Helper()
   277  
   278  	doDeleteChunk(b, s, newFullRandomEntryGenerator(0, b.N))
   279  }
   280  
   281  func BenchmarkChunkStoreDeleteSequential(b *testing.B, s storage.ChunkStore) {
   282  	b.Helper()
   283  
   284  	doDeleteChunk(b, s, newSequentialEntryGenerator(b.N))
   285  }