github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/storage/storeandbatch_test.go (about)

     1  package storage
     2  
     3  import (
     4  	"bytes"
     5  	"reflect"
     6  	"runtime"
     7  	"sort"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  type dbSetup struct {
    15  	name   string
    16  	create func(testing.TB) Store
    17  }
    18  
    19  type dbTestFunction func(*testing.T, Store)
    20  
    21  func testStoreGetNonExistent(t *testing.T, s Store) {
    22  	key := []byte("sparse")
    23  
    24  	_, err := s.Get(key)
    25  	assert.Equal(t, err, ErrKeyNotFound)
    26  }
    27  
    28  func pushSeekDataSet(t *testing.T, s Store) []KeyValue {
    29  	// Use the same set of kvs to test Seek with different prefix/start values.
    30  	kvs := []KeyValue{
    31  		{[]byte("10"), []byte("bar")},
    32  		{[]byte("11"), []byte("bara")},
    33  		{[]byte("20"), []byte("barb")},
    34  		{[]byte("21"), []byte("barc")},
    35  		{[]byte("22"), []byte("bard")},
    36  		{[]byte("30"), []byte("bare")},
    37  		{[]byte("31"), []byte("barf")},
    38  	}
    39  	up := NewMemCachedStore(s)
    40  	for _, v := range kvs {
    41  		up.Put(v.Key, v.Value)
    42  	}
    43  	_, err := up.PersistSync()
    44  	require.NoError(t, err)
    45  	return kvs
    46  }
    47  
    48  func testStoreSeek(t *testing.T, s Store) {
    49  	kvs := pushSeekDataSet(t, s)
    50  	check := func(t *testing.T, goodprefix, start []byte, goodkvs []KeyValue, backwards bool, cont func(k, v []byte) bool) {
    51  		// Seek result expected to be sorted in an ascending (for forwards seeking) or descending (for backwards seeking) way.
    52  		cmpFunc := func(i, j int) bool {
    53  			return bytes.Compare(goodkvs[i].Key, goodkvs[j].Key) < 0
    54  		}
    55  		if backwards {
    56  			cmpFunc = func(i, j int) bool {
    57  				return bytes.Compare(goodkvs[i].Key, goodkvs[j].Key) > 0
    58  			}
    59  		}
    60  		sort.Slice(goodkvs, cmpFunc)
    61  
    62  		rng := SeekRange{
    63  			Prefix: goodprefix,
    64  			Start:  start,
    65  		}
    66  		if backwards {
    67  			rng.Backwards = true
    68  		}
    69  		actual := make([]KeyValue, 0, len(goodkvs))
    70  		s.Seek(rng, func(k, v []byte) bool {
    71  			actual = append(actual, KeyValue{
    72  				Key:   bytes.Clone(k),
    73  				Value: bytes.Clone(v),
    74  			})
    75  			if cont == nil {
    76  				return true
    77  			}
    78  			return cont(k, v)
    79  		})
    80  		assert.Equal(t, goodkvs, actual)
    81  	}
    82  
    83  	t.Run("non-empty prefix, empty start", func(t *testing.T) {
    84  		t.Run("forwards", func(t *testing.T) {
    85  			t.Run("good", func(t *testing.T) {
    86  				// Given this prefix...
    87  				goodprefix := []byte("2")
    88  				// and empty start range...
    89  				start := []byte{}
    90  				// these pairs should be found.
    91  				goodkvs := []KeyValue{
    92  					kvs[2], // key = "20"
    93  					kvs[3], // key = "21"
    94  					kvs[4], // key = "22"
    95  				}
    96  				check(t, goodprefix, start, goodkvs, false, nil)
    97  			})
    98  			t.Run("no matching items", func(t *testing.T) {
    99  				goodprefix := []byte("0")
   100  				start := []byte{}
   101  				check(t, goodprefix, start, []KeyValue{}, false, nil)
   102  			})
   103  			t.Run("early stop", func(t *testing.T) {
   104  				// Given this prefix...
   105  				goodprefix := []byte("2")
   106  				// and empty start range...
   107  				start := []byte{}
   108  				// these pairs should be found.
   109  				goodkvs := []KeyValue{
   110  					kvs[2], // key = "20"
   111  					kvs[3], // key = "21"
   112  				}
   113  				check(t, goodprefix, start, goodkvs, false, func(k, v []byte) bool {
   114  					return string(k) < "21"
   115  				})
   116  			})
   117  		})
   118  
   119  		t.Run("backwards", func(t *testing.T) {
   120  			t.Run("good", func(t *testing.T) {
   121  				goodprefix := []byte("2")
   122  				start := []byte{}
   123  				goodkvs := []KeyValue{
   124  					kvs[4], // key = "22"
   125  					kvs[3], // key = "21"
   126  					kvs[2], // key = "20"
   127  				}
   128  				check(t, goodprefix, start, goodkvs, true, nil)
   129  			})
   130  			t.Run("no matching items", func(t *testing.T) {
   131  				goodprefix := []byte("0")
   132  				start := []byte{}
   133  				check(t, goodprefix, start, []KeyValue{}, true, nil)
   134  			})
   135  			t.Run("early stop", func(t *testing.T) {
   136  				goodprefix := []byte("2")
   137  				start := []byte{}
   138  				goodkvs := []KeyValue{
   139  					kvs[4], // key = "22"
   140  					kvs[3], // key = "21"
   141  				}
   142  				check(t, goodprefix, start, goodkvs, true, func(k, v []byte) bool {
   143  					return string(k) > "21"
   144  				})
   145  			})
   146  		})
   147  	})
   148  
   149  	t.Run("non-empty prefix, non-empty start", func(t *testing.T) {
   150  		t.Run("forwards", func(t *testing.T) {
   151  			t.Run("good", func(t *testing.T) {
   152  				goodprefix := []byte("2")
   153  				start := []byte("1") // start will be appended to goodprefix to start seek from
   154  				goodkvs := []KeyValue{
   155  					kvs[3], // key = "21"
   156  					kvs[4], // key = "22"
   157  				}
   158  				check(t, goodprefix, start, goodkvs, false, nil)
   159  			})
   160  			t.Run("no matching items", func(t *testing.T) {
   161  				goodprefix := []byte("2")
   162  				start := []byte("3") // start is more than all keys prefixed by '2'.
   163  				check(t, goodprefix, start, []KeyValue{}, false, nil)
   164  			})
   165  			t.Run("early stop", func(t *testing.T) {
   166  				goodprefix := []byte("2")
   167  				start := []byte("0") // start will be appended to goodprefix to start seek from
   168  				goodkvs := []KeyValue{
   169  					kvs[2], // key = "20"
   170  					kvs[3], // key = "21"
   171  				}
   172  				check(t, goodprefix, start, goodkvs, false, func(k, v []byte) bool {
   173  					return string(k) < "21"
   174  				})
   175  			})
   176  		})
   177  		t.Run("backwards", func(t *testing.T) {
   178  			t.Run("good", func(t *testing.T) {
   179  				goodprefix := []byte("2")
   180  				start := []byte("1") // start will be appended to goodprefix to start seek from
   181  				goodkvs := []KeyValue{
   182  					kvs[3], // key = "21"
   183  					kvs[2], // key = "20"
   184  				}
   185  				check(t, goodprefix, start, goodkvs, true, nil)
   186  			})
   187  			t.Run("no matching items", func(t *testing.T) {
   188  				goodprefix := []byte("2")
   189  				start := []byte(".") // start is less than all keys prefixed by '2'.
   190  				check(t, goodprefix, start, []KeyValue{}, true, nil)
   191  			})
   192  			t.Run("early stop", func(t *testing.T) {
   193  				goodprefix := []byte("2")
   194  				start := []byte("2") // start will be appended to goodprefix to start seek from
   195  				goodkvs := []KeyValue{
   196  					kvs[4], // key = "24"
   197  					kvs[3], // key = "21"
   198  				}
   199  				check(t, goodprefix, start, goodkvs, true, func(k, v []byte) bool {
   200  					return string(k) > "21"
   201  				})
   202  			})
   203  		})
   204  	})
   205  }
   206  
   207  func testStoreSeekGC(t *testing.T, s Store) {
   208  	kvs := pushSeekDataSet(t, s)
   209  	err := s.SeekGC(SeekRange{Prefix: []byte("1")}, func(k, v []byte) bool {
   210  		return true
   211  	})
   212  	require.NoError(t, err)
   213  	for i := range kvs {
   214  		_, err = s.Get(kvs[i].Key)
   215  		require.NoError(t, err)
   216  	}
   217  	err = s.SeekGC(SeekRange{Prefix: []byte("3")}, func(k, v []byte) bool {
   218  		return false
   219  	})
   220  	require.NoError(t, err)
   221  	for i := range kvs[:5] {
   222  		_, err = s.Get(kvs[i].Key)
   223  		require.NoError(t, err)
   224  	}
   225  	for _, kv := range kvs[5:] {
   226  		_, err = s.Get(kv.Key)
   227  		require.Error(t, err)
   228  	}
   229  }
   230  
   231  func TestAllDBs(t *testing.T) {
   232  	var DBs = []dbSetup{
   233  		{"BoltDB", newBoltStoreForTesting},
   234  		{"LevelDB", newLevelDBForTesting},
   235  		{"MemCached", newMemCachedStoreForTesting},
   236  		{"Memory", newMemoryStoreForTesting},
   237  	}
   238  	var tests = []dbTestFunction{testStoreGetNonExistent, testStoreSeek,
   239  		testStoreSeekGC}
   240  	for _, db := range DBs {
   241  		for _, test := range tests {
   242  			s := db.create(t)
   243  			twrapper := func(t *testing.T) {
   244  				test(t, s)
   245  			}
   246  			fname := runtime.FuncForPC(reflect.ValueOf(test).Pointer()).Name()
   247  			t.Run(db.name+"/"+fname, twrapper)
   248  			require.NoError(t, s.Close())
   249  		}
   250  	}
   251  }