neoneo-go/pkg/core/storage/storeandbatch_test.go
Roman Khimov 17a43b19e0 storage: remove Batch from Store
We never use it for real underlying stores, so these implementations are
useless (everything goes though PutChangeSet now).
2022-02-16 15:55:48 +03:00

293 lines
7.5 KiB
Go

package storage
import (
"bytes"
"reflect"
"runtime"
"sort"
"testing"
"github.com/nspcc-dev/neo-go/pkg/util/slice"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type dbSetup struct {
name string
create func(testing.TB) Store
}
type dbTestFunction func(*testing.T, Store)
func testStorePutAndGet(t *testing.T, s Store) {
key := []byte("foo")
value := []byte("bar")
require.NoError(t, s.Put(key, value))
result, err := s.Get(key)
assert.Nil(t, err)
require.Equal(t, value, result)
}
func testStoreGetNonExistent(t *testing.T, s Store) {
key := []byte("sparse")
_, err := s.Get(key)
assert.Equal(t, err, ErrKeyNotFound)
}
func testStoreSeek(t *testing.T, s Store) {
// Use the same set of kvs to test Seek with different prefix/start values.
kvs := []KeyValue{
{[]byte("10"), []byte("bar")},
{[]byte("11"), []byte("bara")},
{[]byte("20"), []byte("barb")},
{[]byte("21"), []byte("barc")},
{[]byte("22"), []byte("bard")},
{[]byte("30"), []byte("bare")},
{[]byte("31"), []byte("barf")},
}
for _, v := range kvs {
require.NoError(t, s.Put(v.Key, v.Value))
}
check := func(t *testing.T, goodprefix, start []byte, goodkvs []KeyValue, backwards bool, cont func(k, v []byte) bool) {
// Seek result expected to be sorted in an ascending (for forwards seeking) or descending (for backwards seeking) way.
cmpFunc := func(i, j int) bool {
return bytes.Compare(goodkvs[i].Key, goodkvs[j].Key) < 0
}
if backwards {
cmpFunc = func(i, j int) bool {
return bytes.Compare(goodkvs[i].Key, goodkvs[j].Key) > 0
}
}
sort.Slice(goodkvs, cmpFunc)
rng := SeekRange{
Prefix: goodprefix,
Start: start,
}
if backwards {
rng.Backwards = true
}
actual := make([]KeyValue, 0, len(goodkvs))
s.Seek(rng, func(k, v []byte) bool {
actual = append(actual, KeyValue{
Key: slice.Copy(k),
Value: slice.Copy(v),
})
if cont == nil {
return true
}
return cont(k, v)
})
assert.Equal(t, goodkvs, actual)
}
t.Run("non-empty prefix, empty start", func(t *testing.T) {
t.Run("forwards", func(t *testing.T) {
t.Run("good", func(t *testing.T) {
// Given this prefix...
goodprefix := []byte("2")
// and empty start range...
start := []byte{}
// these pairs should be found.
goodkvs := []KeyValue{
kvs[2], // key = "20"
kvs[3], // key = "21"
kvs[4], // key = "22"
}
check(t, goodprefix, start, goodkvs, false, nil)
})
t.Run("no matching items", func(t *testing.T) {
goodprefix := []byte("0")
start := []byte{}
check(t, goodprefix, start, []KeyValue{}, false, nil)
})
t.Run("early stop", func(t *testing.T) {
// Given this prefix...
goodprefix := []byte("2")
// and empty start range...
start := []byte{}
// these pairs should be found.
goodkvs := []KeyValue{
kvs[2], // key = "20"
kvs[3], // key = "21"
}
check(t, goodprefix, start, goodkvs, false, func(k, v []byte) bool {
return string(k) < "21"
})
})
})
t.Run("backwards", func(t *testing.T) {
t.Run("good", func(t *testing.T) {
goodprefix := []byte("2")
start := []byte{}
goodkvs := []KeyValue{
kvs[4], // key = "22"
kvs[3], // key = "21"
kvs[2], // key = "20"
}
check(t, goodprefix, start, goodkvs, true, nil)
})
t.Run("no matching items", func(t *testing.T) {
goodprefix := []byte("0")
start := []byte{}
check(t, goodprefix, start, []KeyValue{}, true, nil)
})
t.Run("early stop", func(t *testing.T) {
goodprefix := []byte("2")
start := []byte{}
goodkvs := []KeyValue{
kvs[4], // key = "22"
kvs[3], // key = "21"
}
check(t, goodprefix, start, goodkvs, true, func(k, v []byte) bool {
return string(k) > "21"
})
})
})
})
t.Run("non-empty prefix, non-empty start", func(t *testing.T) {
t.Run("forwards", func(t *testing.T) {
t.Run("good", func(t *testing.T) {
goodprefix := []byte("2")
start := []byte("1") // start will be appended to goodprefix to start seek from
goodkvs := []KeyValue{
kvs[3], // key = "21"
kvs[4], // key = "22"
}
check(t, goodprefix, start, goodkvs, false, nil)
})
t.Run("no matching items", func(t *testing.T) {
goodprefix := []byte("2")
start := []byte("3") // start is more than all keys prefixed by '2'.
check(t, goodprefix, start, []KeyValue{}, false, nil)
})
t.Run("early stop", func(t *testing.T) {
goodprefix := []byte("2")
start := []byte("0") // start will be appended to goodprefix to start seek from
goodkvs := []KeyValue{
kvs[2], // key = "20"
kvs[3], // key = "21"
}
check(t, goodprefix, start, goodkvs, false, func(k, v []byte) bool {
return string(k) < "21"
})
})
})
t.Run("backwards", func(t *testing.T) {
t.Run("good", func(t *testing.T) {
goodprefix := []byte("2")
start := []byte("1") // start will be appended to goodprefix to start seek from
goodkvs := []KeyValue{
kvs[3], // key = "21"
kvs[2], // key = "20"
}
check(t, goodprefix, start, goodkvs, true, nil)
})
t.Run("no matching items", func(t *testing.T) {
goodprefix := []byte("2")
start := []byte(".") // start is less than all keys prefixed by '2'.
check(t, goodprefix, start, []KeyValue{}, true, nil)
})
t.Run("early stop", func(t *testing.T) {
goodprefix := []byte("2")
start := []byte("2") // start will be appended to goodprefix to start seek from
goodkvs := []KeyValue{
kvs[4], // key = "24"
kvs[3], // key = "21"
}
check(t, goodprefix, start, goodkvs, true, func(k, v []byte) bool {
return string(k) > "21"
})
})
})
})
}
func testStoreDeleteNonExistent(t *testing.T, s Store) {
key := []byte("sparse")
assert.NoError(t, s.Delete(key))
}
func testStorePutAndDelete(t *testing.T, s Store) {
key := []byte("foo")
value := []byte("bar")
require.NoError(t, s.Put(key, value))
err := s.Delete(key)
assert.Nil(t, err)
_, err = s.Get(key)
assert.NotNil(t, err)
assert.Equal(t, err, ErrKeyNotFound)
// Double delete.
err = s.Delete(key)
assert.Nil(t, err)
}
func testStoreSeekGC(t *testing.T, s Store) {
kvs := []KeyValue{
{[]byte("10"), []byte("bar")},
{[]byte("11"), []byte("bara")},
{[]byte("20"), []byte("barb")},
{[]byte("21"), []byte("barc")},
{[]byte("22"), []byte("bard")},
{[]byte("30"), []byte("bare")},
{[]byte("31"), []byte("barf")},
}
for _, v := range kvs {
require.NoError(t, s.Put(v.Key, v.Value))
}
err := s.SeekGC(SeekRange{Prefix: []byte("1")}, func(k, v []byte) bool {
return true
})
require.NoError(t, err)
for i := range kvs {
_, err = s.Get(kvs[i].Key)
require.NoError(t, err)
}
err = s.SeekGC(SeekRange{Prefix: []byte("3")}, func(k, v []byte) bool {
return false
})
require.NoError(t, err)
for i := range kvs[:5] {
_, err = s.Get(kvs[i].Key)
require.NoError(t, err)
}
for _, kv := range kvs[5:] {
_, err = s.Get(kv.Key)
require.Error(t, err)
}
}
func TestAllDBs(t *testing.T) {
var DBs = []dbSetup{
{"BoltDB", newBoltStoreForTesting},
{"LevelDB", newLevelDBForTesting},
{"MemCached", newMemCachedStoreForTesting},
{"Memory", newMemoryStoreForTesting},
}
var tests = []dbTestFunction{testStorePutAndGet,
testStoreGetNonExistent, testStoreSeek,
testStoreDeleteNonExistent, testStorePutAndDelete,
testStoreSeekGC}
for _, db := range DBs {
for _, test := range tests {
s := db.create(t)
twrapper := func(t *testing.T) {
test(t, s)
}
fname := runtime.FuncForPC(reflect.ValueOf(test).Pointer()).Name()
t.Run(db.name+"/"+fname, twrapper)
require.NoError(t, s.Close())
}
}
}