forked from TrueCloudLab/neoneo-go
17a43b19e0
We never use it for real underlying stores, so these implementations are useless (everything goes though PutChangeSet now).
293 lines
7.5 KiB
Go
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())
|
|
}
|
|
}
|
|
}
|