kvdb/test: generalize etcd tests
This commit is contained in:
parent
bb5c3f3b51
commit
3c6d35ec41
@ -90,3 +90,42 @@ func getKeyVal(kv *KV) ([]byte, []byte) {
|
|||||||
|
|
||||||
return getKey(kv.key), val
|
return getKey(kv.key), val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BucketKey is a helper functon used in tests to create a bucket key from
|
||||||
|
// passed bucket list.
|
||||||
|
func BucketKey(buckets ...string) string {
|
||||||
|
var bucketKey []byte
|
||||||
|
|
||||||
|
rootID := makeBucketID([]byte(etcdDefaultRootBucketId))
|
||||||
|
parent := rootID[:]
|
||||||
|
|
||||||
|
for _, bucketName := range buckets {
|
||||||
|
bucketKey = makeBucketKey(parent, []byte(bucketName))
|
||||||
|
id := makeBucketID(bucketKey)
|
||||||
|
parent = id[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(bucketKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BucketVal is a helper function used in tests to create a bucket value (the
|
||||||
|
// value for a bucket key) from the passed bucket list.
|
||||||
|
func BucketVal(buckets ...string) string {
|
||||||
|
id := makeBucketID([]byte(BucketKey(buckets...)))
|
||||||
|
return string(id[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueKey is a helper function used in tests to create a value key from the
|
||||||
|
// passed key and bucket list.
|
||||||
|
func ValueKey(key string, buckets ...string) string {
|
||||||
|
rootID := makeBucketID([]byte(etcdDefaultRootBucketId))
|
||||||
|
bucket := rootID[:]
|
||||||
|
|
||||||
|
for _, bucketName := range buckets {
|
||||||
|
bucketKey := makeBucketKey(bucket, []byte(bucketName))
|
||||||
|
id := makeBucketID(bucketKey)
|
||||||
|
bucket = id[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(makeValueKey(bucket, []byte(key)))
|
||||||
|
}
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
// +build kvdb_etcd
|
|
||||||
|
|
||||||
package etcd
|
|
||||||
|
|
||||||
// bkey is a helper functon used in tests to create a bucket key from passed
|
|
||||||
// bucket list.
|
|
||||||
func bkey(buckets ...string) string {
|
|
||||||
var bucketKey []byte
|
|
||||||
|
|
||||||
rootID := makeBucketID([]byte(etcdDefaultRootBucketId))
|
|
||||||
parent := rootID[:]
|
|
||||||
|
|
||||||
for _, bucketName := range buckets {
|
|
||||||
bucketKey = makeBucketKey(parent, []byte(bucketName))
|
|
||||||
id := makeBucketID(bucketKey)
|
|
||||||
parent = id[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(bucketKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bval is a helper function used in tests to create a bucket value (the value
|
|
||||||
// for a bucket key) from the passed bucket list.
|
|
||||||
func bval(buckets ...string) string {
|
|
||||||
id := makeBucketID([]byte(bkey(buckets...)))
|
|
||||||
return string(id[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// vkey is a helper function used in tests to create a value key from the
|
|
||||||
// passed key and bucket list.
|
|
||||||
func vkey(key string, buckets ...string) string {
|
|
||||||
rootID := makeBucketID([]byte(etcdDefaultRootBucketId))
|
|
||||||
bucket := rootID[:]
|
|
||||||
|
|
||||||
for _, bucketName := range buckets {
|
|
||||||
bucketKey := makeBucketKey(bucket, []byte(bucketName))
|
|
||||||
id := makeBucketID(bucketKey)
|
|
||||||
bucket = id[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(makeValueKey(bucket, []byte(key)))
|
|
||||||
}
|
|
@ -38,8 +38,8 @@ func TestCopy(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
expected := map[string]string{
|
expected := map[string]string{
|
||||||
bkey("apple"): bval("apple"),
|
BucketKey("apple"): BucketVal("apple"),
|
||||||
vkey("key", "apple"): "val",
|
ValueKey("key", "apple"): "val",
|
||||||
}
|
}
|
||||||
require.Equal(t, expected, f.Dump())
|
require.Equal(t, expected, f.Dump())
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcwallet/walletdb"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"go.etcd.io/etcd/clientv3"
|
"go.etcd.io/etcd/clientv3"
|
||||||
"go.etcd.io/etcd/clientv3/namespace"
|
"go.etcd.io/etcd/clientv3/namespace"
|
||||||
)
|
)
|
||||||
@ -76,6 +78,13 @@ func NewEtcdTestFixture(t *testing.T) *EtcdTestFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *EtcdTestFixture) NewBackend() walletdb.DB {
|
||||||
|
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
||||||
|
require.NoError(f.t, err)
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
// Put puts a string key/value into the test etcd database.
|
// Put puts a string key/value into the test etcd database.
|
||||||
func (f *EtcdTestFixture) Put(key, value string) {
|
func (f *EtcdTestFixture) Put(key, value string) {
|
||||||
ctx, cancel := context.WithTimeout(context.TODO(), testEtcdTimeout)
|
ctx, cancel := context.WithTimeout(context.TODO(), testEtcdTimeout)
|
@ -10,70 +10,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTxManualCommit(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tx, err := db.BeginReadWriteTx()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, tx)
|
|
||||||
|
|
||||||
committed := false
|
|
||||||
|
|
||||||
tx.OnCommit(func() {
|
|
||||||
committed = true
|
|
||||||
})
|
|
||||||
|
|
||||||
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, apple)
|
|
||||||
require.NoError(t, apple.Put([]byte("testKey"), []byte("testVal")))
|
|
||||||
|
|
||||||
banana, err := tx.CreateTopLevelBucket([]byte("banana"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, banana)
|
|
||||||
require.NoError(t, banana.Put([]byte("testKey"), []byte("testVal")))
|
|
||||||
require.NoError(t, tx.DeleteTopLevelBucket([]byte("banana")))
|
|
||||||
|
|
||||||
require.NoError(t, tx.Commit())
|
|
||||||
require.True(t, committed)
|
|
||||||
|
|
||||||
expected := map[string]string{
|
|
||||||
bkey("apple"): bval("apple"),
|
|
||||||
vkey("testKey", "apple"): "testVal",
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, f.Dump())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTxRollback(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tx, err := db.BeginReadWriteTx()
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.NotNil(t, tx)
|
|
||||||
|
|
||||||
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.NotNil(t, apple)
|
|
||||||
|
|
||||||
require.NoError(t, apple.Put([]byte("testKey"), []byte("testVal")))
|
|
||||||
|
|
||||||
require.NoError(t, tx.Rollback())
|
|
||||||
require.Error(t, walletdb.ErrTxClosed, tx.Commit())
|
|
||||||
require.Equal(t, map[string]string{}, f.Dump())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangeDuringManualTx(t *testing.T) {
|
func TestChangeDuringManualTx(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@ -94,12 +30,12 @@ func TestChangeDuringManualTx(t *testing.T) {
|
|||||||
require.NoError(t, apple.Put([]byte("testKey"), []byte("testVal")))
|
require.NoError(t, apple.Put([]byte("testKey"), []byte("testVal")))
|
||||||
|
|
||||||
// Try overwriting the bucket key.
|
// Try overwriting the bucket key.
|
||||||
f.Put(bkey("apple"), "banana")
|
f.Put(BucketKey("apple"), "banana")
|
||||||
|
|
||||||
// TODO: translate error
|
// TODO: translate error
|
||||||
require.NotNil(t, tx.Commit())
|
require.NotNil(t, tx.Commit())
|
||||||
require.Equal(t, map[string]string{
|
require.Equal(t, map[string]string{
|
||||||
bkey("apple"): "banana",
|
BucketKey("apple"): "banana",
|
||||||
}, f.Dump())
|
}, f.Dump())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,8 +58,8 @@ func TestChangeDuringUpdate(t *testing.T) {
|
|||||||
require.NoError(t, apple.Put([]byte("key"), []byte("value")))
|
require.NoError(t, apple.Put([]byte("key"), []byte("value")))
|
||||||
|
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
f.Put(vkey("key", "apple"), "new_value")
|
f.Put(ValueKey("key", "apple"), "new_value")
|
||||||
f.Put(vkey("key2", "apple"), "value2")
|
f.Put(ValueKey("key2", "apple"), "value2")
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor := apple.ReadCursor()
|
cursor := apple.ReadCursor()
|
||||||
@ -149,9 +85,9 @@ func TestChangeDuringUpdate(t *testing.T) {
|
|||||||
require.Equal(t, count, 2)
|
require.Equal(t, count, 2)
|
||||||
|
|
||||||
expected := map[string]string{
|
expected := map[string]string{
|
||||||
bkey("apple"): bval("apple"),
|
BucketKey("apple"): BucketVal("apple"),
|
||||||
vkey("key", "apple"): "value",
|
ValueKey("key", "apple"): "value",
|
||||||
vkey("key2", "apple"): "value2",
|
ValueKey("key2", "apple"): "value2",
|
||||||
}
|
}
|
||||||
require.Equal(t, expected, f.Dump())
|
require.Equal(t, expected, f.Dump())
|
||||||
}
|
}
|
||||||
|
154
kvdb/etcd_test.go
Normal file
154
kvdb/etcd_test.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// +build kvdb_etcd
|
||||||
|
|
||||||
|
package kvdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcwallet/walletdb"
|
||||||
|
"github.com/lightningnetwork/lnd/kvdb/etcd"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bkey = etcd.BucketKey
|
||||||
|
bval = etcd.BucketVal
|
||||||
|
vkey = etcd.ValueKey
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEtcd(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
test func(*testing.T, walletdb.DB)
|
||||||
|
expectedDb map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "read cursor empty interval",
|
||||||
|
test: testReadCursorEmptyInterval,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read cursor non empty interval",
|
||||||
|
test: testReadCursorNonEmptyInterval,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read write cursor",
|
||||||
|
test: testReadWriteCursor,
|
||||||
|
expectedDb: map[string]string{
|
||||||
|
bkey("apple"): bval("apple"),
|
||||||
|
vkey("a", "apple"): "0",
|
||||||
|
vkey("c", "apple"): "3",
|
||||||
|
vkey("cx", "apple"): "x",
|
||||||
|
vkey("cy", "apple"): "y",
|
||||||
|
vkey("da", "apple"): "3",
|
||||||
|
vkey("f", "apple"): "5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read write cursor with bucket and value",
|
||||||
|
test: testReadWriteCursorWithBucketAndValue,
|
||||||
|
expectedDb: map[string]string{
|
||||||
|
bkey("apple"): bval("apple"),
|
||||||
|
bkey("apple", "banana"): bval("apple", "banana"),
|
||||||
|
bkey("apple", "pear"): bval("apple", "pear"),
|
||||||
|
vkey("key", "apple"): "val",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket creation",
|
||||||
|
test: testBucketCreation,
|
||||||
|
expectedDb: map[string]string{
|
||||||
|
bkey("apple"): bval("apple"),
|
||||||
|
bkey("apple", "banana"): bval("apple", "banana"),
|
||||||
|
bkey("apple", "mango"): bval("apple", "mango"),
|
||||||
|
bkey("apple", "banana", "pear"): bval("apple", "banana", "pear"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket deletion",
|
||||||
|
test: testBucketDeletion,
|
||||||
|
expectedDb: map[string]string{
|
||||||
|
bkey("apple"): bval("apple"),
|
||||||
|
bkey("apple", "banana"): bval("apple", "banana"),
|
||||||
|
vkey("key1", "apple", "banana"): "val1",
|
||||||
|
vkey("key3", "apple", "banana"): "val3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket for each",
|
||||||
|
test: testBucketForEach,
|
||||||
|
expectedDb: map[string]string{
|
||||||
|
bkey("apple"): bval("apple"),
|
||||||
|
bkey("apple", "banana"): bval("apple", "banana"),
|
||||||
|
vkey("key1", "apple"): "val1",
|
||||||
|
vkey("key2", "apple"): "val2",
|
||||||
|
vkey("key3", "apple"): "val3",
|
||||||
|
vkey("key1", "apple", "banana"): "val1",
|
||||||
|
vkey("key2", "apple", "banana"): "val2",
|
||||||
|
vkey("key3", "apple", "banana"): "val3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket for each with error",
|
||||||
|
test: testBucketForEachWithError,
|
||||||
|
expectedDb: map[string]string{
|
||||||
|
bkey("apple"): bval("apple"),
|
||||||
|
bkey("apple", "banana"): bval("apple", "banana"),
|
||||||
|
bkey("apple", "pear"): bval("apple", "pear"),
|
||||||
|
vkey("key1", "apple"): "val1",
|
||||||
|
vkey("key2", "apple"): "val2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket sequence",
|
||||||
|
test: testBucketSequence,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key clash",
|
||||||
|
test: testKeyClash,
|
||||||
|
expectedDb: map[string]string{
|
||||||
|
bkey("apple"): bval("apple"),
|
||||||
|
bkey("apple", "banana"): bval("apple", "banana"),
|
||||||
|
vkey("key", "apple"): "val",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket create delete",
|
||||||
|
test: testBucketCreateDelete,
|
||||||
|
expectedDb: map[string]string{
|
||||||
|
vkey("banana", "apple"): "value",
|
||||||
|
bkey("apple"): bval("apple"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tx manual commit",
|
||||||
|
test: testTxManualCommit,
|
||||||
|
expectedDb: map[string]string{
|
||||||
|
bkey("apple"): bval("apple"),
|
||||||
|
vkey("testKey", "apple"): "testVal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tx rollback",
|
||||||
|
test: testTxRollback,
|
||||||
|
expectedDb: map[string]string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
f := etcd.NewEtcdTestFixture(t)
|
||||||
|
defer f.Cleanup()
|
||||||
|
|
||||||
|
test.test(t, f.NewBackend())
|
||||||
|
|
||||||
|
if test.expectedDb != nil {
|
||||||
|
dump := f.Dump()
|
||||||
|
require.Equal(t, test.expectedDb, dump)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,6 @@
|
|||||||
// +build kvdb_etcd
|
package kvdb
|
||||||
|
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
@ -12,16 +9,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBucketCreation(t *testing.T) {
|
func testBucketCreation(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
err := Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
|
||||||
// empty bucket name
|
// empty bucket name
|
||||||
b, err := tx.CreateTopLevelBucket(nil)
|
b, err := tx.CreateTopLevelBucket(nil)
|
||||||
require.Error(t, walletdb.ErrBucketNameRequired, err)
|
require.Error(t, walletdb.ErrBucketNameRequired, err)
|
||||||
@ -83,26 +72,10 @@ func TestBucketCreation(t *testing.T) {
|
|||||||
}, func() {})
|
}, func() {})
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
expected := map[string]string{
|
|
||||||
bkey("apple"): bval("apple"),
|
|
||||||
bkey("apple", "banana"): bval("apple", "banana"),
|
|
||||||
bkey("apple", "mango"): bval("apple", "mango"),
|
|
||||||
bkey("apple", "banana", "pear"): bval("apple", "banana", "pear"),
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, f.Dump())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBucketDeletion(t *testing.T) {
|
func testBucketDeletion(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
err := Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
|
||||||
// "apple"
|
// "apple"
|
||||||
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -193,26 +166,10 @@ func TestBucketDeletion(t *testing.T) {
|
|||||||
}, func() {})
|
}, func() {})
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
expected := map[string]string{
|
|
||||||
bkey("apple"): bval("apple"),
|
|
||||||
bkey("apple", "banana"): bval("apple", "banana"),
|
|
||||||
vkey("key1", "apple", "banana"): "val1",
|
|
||||||
vkey("key3", "apple", "banana"): "val3",
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, f.Dump())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBucketForEach(t *testing.T) {
|
func testBucketForEach(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
err := Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
|
||||||
// "apple"
|
// "apple"
|
||||||
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -265,30 +222,10 @@ func TestBucketForEach(t *testing.T) {
|
|||||||
}, func() {})
|
}, func() {})
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
expected := map[string]string{
|
|
||||||
bkey("apple"): bval("apple"),
|
|
||||||
bkey("apple", "banana"): bval("apple", "banana"),
|
|
||||||
vkey("key1", "apple"): "val1",
|
|
||||||
vkey("key2", "apple"): "val2",
|
|
||||||
vkey("key3", "apple"): "val3",
|
|
||||||
vkey("key1", "apple", "banana"): "val1",
|
|
||||||
vkey("key2", "apple", "banana"): "val2",
|
|
||||||
vkey("key3", "apple", "banana"): "val3",
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, f.Dump())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBucketForEachWithError(t *testing.T) {
|
func testBucketForEachWithError(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
err := Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
|
||||||
// "apple"
|
// "apple"
|
||||||
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -358,27 +295,10 @@ func TestBucketForEachWithError(t *testing.T) {
|
|||||||
}, func() {})
|
}, func() {})
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
expected := map[string]string{
|
|
||||||
bkey("apple"): bval("apple"),
|
|
||||||
bkey("apple", "banana"): bval("apple", "banana"),
|
|
||||||
bkey("apple", "pear"): bval("apple", "pear"),
|
|
||||||
vkey("key1", "apple"): "val1",
|
|
||||||
vkey("key2", "apple"): "val2",
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, f.Dump())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBucketSequence(t *testing.T) {
|
func testBucketSequence(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
err := Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
|
||||||
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, apple)
|
require.NotNil(t, apple)
|
||||||
@ -408,19 +328,11 @@ func TestBucketSequence(t *testing.T) {
|
|||||||
// TestKeyClash tests that one cannot create a bucket if a value with the same
|
// TestKeyClash tests that one cannot create a bucket if a value with the same
|
||||||
// key exists and the same is true in reverse: that a value cannot be put if
|
// key exists and the same is true in reverse: that a value cannot be put if
|
||||||
// a bucket with the same key exists.
|
// a bucket with the same key exists.
|
||||||
func TestKeyClash(t *testing.T) {
|
func testKeyClash(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// First:
|
// First:
|
||||||
// put: /apple/key -> val
|
// put: /apple/key -> val
|
||||||
// create bucket: /apple/banana
|
// create bucket: /apple/banana
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
err := Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, apple)
|
require.NotNil(t, apple)
|
||||||
@ -439,7 +351,7 @@ func TestKeyClash(t *testing.T) {
|
|||||||
// Next try to:
|
// Next try to:
|
||||||
// put: /apple/banana -> val => will fail (as /apple/banana is a bucket)
|
// put: /apple/banana -> val => will fail (as /apple/banana is a bucket)
|
||||||
// create bucket: /apple/key => will fail (as /apple/key is a value)
|
// create bucket: /apple/key => will fail (as /apple/key is a value)
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
err = Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, apple)
|
require.NotNil(t, apple)
|
||||||
@ -461,31 +373,12 @@ func TestKeyClash(t *testing.T) {
|
|||||||
}, func() {})
|
}, func() {})
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
// Except that the only existing items in the db are:
|
|
||||||
// bucket: /apple
|
|
||||||
// bucket: /apple/banana
|
|
||||||
// value: /apple/key -> val
|
|
||||||
expected := map[string]string{
|
|
||||||
bkey("apple"): bval("apple"),
|
|
||||||
bkey("apple", "banana"): bval("apple", "banana"),
|
|
||||||
vkey("key", "apple"): "val",
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, f.Dump())
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestBucketCreateDelete tests that creating then deleting then creating a
|
// TestBucketCreateDelete tests that creating then deleting then creating a
|
||||||
// bucket suceeds.
|
// bucket suceeds.
|
||||||
func TestBucketCreateDelete(t *testing.T) {
|
func testBucketCreateDelete(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
err := Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
|
||||||
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, apple)
|
require.NotNil(t, apple)
|
||||||
@ -498,7 +391,7 @@ func TestBucketCreateDelete(t *testing.T) {
|
|||||||
}, func() {})
|
}, func() {})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
err = Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
apple := tx.ReadWriteBucket([]byte("apple"))
|
apple := tx.ReadWriteBucket([]byte("apple"))
|
||||||
require.NotNil(t, apple)
|
require.NotNil(t, apple)
|
||||||
require.NoError(t, apple.DeleteNestedBucket([]byte("banana")))
|
require.NoError(t, apple.DeleteNestedBucket([]byte("banana")))
|
||||||
@ -507,7 +400,7 @@ func TestBucketCreateDelete(t *testing.T) {
|
|||||||
}, func() {})
|
}, func() {})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
err = Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
apple := tx.ReadWriteBucket([]byte("apple"))
|
apple := tx.ReadWriteBucket([]byte("apple"))
|
||||||
require.NotNil(t, apple)
|
require.NotNil(t, apple)
|
||||||
require.NoError(t, apple.Put([]byte("banana"), []byte("value")))
|
require.NoError(t, apple.Put([]byte("banana"), []byte("value")))
|
||||||
@ -515,10 +408,4 @@ func TestBucketCreateDelete(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}, func() {})
|
}, func() {})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expected := map[string]string{
|
|
||||||
vkey("banana", "apple"): "value",
|
|
||||||
bkey("apple"): bval("apple"),
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, f.Dump())
|
|
||||||
}
|
}
|
@ -1,25 +1,14 @@
|
|||||||
// +build kvdb_etcd
|
package kvdb
|
||||||
|
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcwallet/walletdb"
|
"github.com/btcsuite/btcwallet/walletdb"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReadCursorEmptyInterval(t *testing.T) {
|
func testReadCursorEmptyInterval(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
err := Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
|
||||||
b, err := tx.CreateTopLevelBucket([]byte("apple"))
|
b, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, b)
|
require.NotNil(t, b)
|
||||||
@ -28,7 +17,7 @@ func TestReadCursorEmptyInterval(t *testing.T) {
|
|||||||
}, func() {})
|
}, func() {})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = db.View(func(tx walletdb.ReadTx) error {
|
err = View(db, func(tx walletdb.ReadTx) error {
|
||||||
b := tx.ReadBucket([]byte("apple"))
|
b := tx.ReadBucket([]byte("apple"))
|
||||||
require.NotNil(t, b)
|
require.NotNil(t, b)
|
||||||
|
|
||||||
@ -54,15 +43,7 @@ func TestReadCursorEmptyInterval(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadCursorNonEmptyInterval(t *testing.T) {
|
func testReadCursorNonEmptyInterval(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
testKeyValues := []KV{
|
testKeyValues := []KV{
|
||||||
{"b", "1"},
|
{"b", "1"},
|
||||||
{"c", "2"},
|
{"c", "2"},
|
||||||
@ -70,7 +51,7 @@ func TestReadCursorNonEmptyInterval(t *testing.T) {
|
|||||||
{"e", "4"},
|
{"e", "4"},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
err := Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
b, err := tx.CreateTopLevelBucket([]byte("apple"))
|
b, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, b)
|
require.NotNil(t, b)
|
||||||
@ -83,7 +64,7 @@ func TestReadCursorNonEmptyInterval(t *testing.T) {
|
|||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = db.View(func(tx walletdb.ReadTx) error {
|
err = View(db, func(tx walletdb.ReadTx) error {
|
||||||
b := tx.ReadBucket([]byte("apple"))
|
b := tx.ReadBucket([]byte("apple"))
|
||||||
require.NotNil(t, b)
|
require.NotNil(t, b)
|
||||||
|
|
||||||
@ -131,14 +112,7 @@ func TestReadCursorNonEmptyInterval(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadWriteCursor(t *testing.T) {
|
func testReadWriteCursor(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
testKeyValues := []KV{
|
testKeyValues := []KV{
|
||||||
{"b", "1"},
|
{"b", "1"},
|
||||||
@ -150,7 +124,7 @@ func TestReadWriteCursor(t *testing.T) {
|
|||||||
count := len(testKeyValues)
|
count := len(testKeyValues)
|
||||||
|
|
||||||
// Pre-store the first half of the interval.
|
// Pre-store the first half of the interval.
|
||||||
require.NoError(t, db.Update(func(tx walletdb.ReadWriteTx) error {
|
require.NoError(t, Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
b, err := tx.CreateTopLevelBucket([]byte("apple"))
|
b, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, b)
|
require.NotNil(t, b)
|
||||||
@ -165,13 +139,13 @@ func TestReadWriteCursor(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}, func() {}))
|
}, func() {}))
|
||||||
|
|
||||||
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
err := Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
b := tx.ReadWriteBucket([]byte("apple"))
|
b := tx.ReadWriteBucket([]byte("apple"))
|
||||||
require.NotNil(t, b)
|
require.NotNil(t, b)
|
||||||
|
|
||||||
// Store the second half of the interval.
|
// Store the second half of the interval.
|
||||||
for i := count / 2; i < count; i++ {
|
for i := count / 2; i < count; i++ {
|
||||||
err = b.Put(
|
err := b.Put(
|
||||||
[]byte(testKeyValues[i].key),
|
[]byte(testKeyValues[i].key),
|
||||||
[]byte(testKeyValues[i].val),
|
[]byte(testKeyValues[i].val),
|
||||||
)
|
)
|
||||||
@ -280,32 +254,14 @@ func TestReadWriteCursor(t *testing.T) {
|
|||||||
}, func() {})
|
}, func() {})
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expected := map[string]string{
|
|
||||||
bkey("apple"): bval("apple"),
|
|
||||||
vkey("a", "apple"): "0",
|
|
||||||
vkey("c", "apple"): "3",
|
|
||||||
vkey("cx", "apple"): "x",
|
|
||||||
vkey("cy", "apple"): "y",
|
|
||||||
vkey("da", "apple"): "3",
|
|
||||||
vkey("f", "apple"): "5",
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, f.Dump())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestReadWriteCursorWithBucketAndValue tests that cursors are able to iterate
|
// testReadWriteCursorWithBucketAndValue tests that cursors are able to iterate
|
||||||
// over both bucket and value keys if both are present in the iterated bucket.
|
// over both bucket and value keys if both are present in the iterated bucket.
|
||||||
func TestReadWriteCursorWithBucketAndValue(t *testing.T) {
|
func testReadWriteCursorWithBucketAndValue(t *testing.T, db walletdb.DB) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
f := NewEtcdTestFixture(t)
|
|
||||||
defer f.Cleanup()
|
|
||||||
|
|
||||||
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Pre-store the first half of the interval.
|
// Pre-store the first half of the interval.
|
||||||
require.NoError(t, db.Update(func(tx walletdb.ReadWriteTx) error {
|
require.NoError(t, Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||||
b, err := tx.CreateTopLevelBucket([]byte("apple"))
|
b, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, b)
|
require.NotNil(t, b)
|
||||||
@ -323,7 +279,7 @@ func TestReadWriteCursorWithBucketAndValue(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}, func() {}))
|
}, func() {}))
|
||||||
|
|
||||||
err = db.View(func(tx walletdb.ReadTx) error {
|
err := View(db, func(tx walletdb.ReadTx) error {
|
||||||
b := tx.ReadBucket([]byte("apple"))
|
b := tx.ReadBucket([]byte("apple"))
|
||||||
require.NotNil(t, b)
|
require.NotNil(t, b)
|
||||||
|
|
||||||
@ -358,12 +314,4 @@ func TestReadWriteCursorWithBucketAndValue(t *testing.T) {
|
|||||||
}, func() {})
|
}, func() {})
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expected := map[string]string{
|
|
||||||
bkey("apple"): bval("apple"),
|
|
||||||
bkey("apple", "banana"): bval("apple", "banana"),
|
|
||||||
bkey("apple", "pear"): bval("apple", "pear"),
|
|
||||||
vkey("key", "apple"): "val",
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, f.Dump())
|
|
||||||
}
|
}
|
49
kvdb/readwrite_tx_test.go
Normal file
49
kvdb/readwrite_tx_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package kvdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcwallet/walletdb"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testTxManualCommit(t *testing.T, db walletdb.DB) {
|
||||||
|
tx, err := db.BeginReadWriteTx()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, tx)
|
||||||
|
|
||||||
|
committed := false
|
||||||
|
|
||||||
|
tx.OnCommit(func() {
|
||||||
|
committed = true
|
||||||
|
})
|
||||||
|
|
||||||
|
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, apple)
|
||||||
|
require.NoError(t, apple.Put([]byte("testKey"), []byte("testVal")))
|
||||||
|
|
||||||
|
banana, err := tx.CreateTopLevelBucket([]byte("banana"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, banana)
|
||||||
|
require.NoError(t, banana.Put([]byte("testKey"), []byte("testVal")))
|
||||||
|
require.NoError(t, tx.DeleteTopLevelBucket([]byte("banana")))
|
||||||
|
|
||||||
|
require.NoError(t, tx.Commit())
|
||||||
|
require.True(t, committed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTxRollback(t *testing.T, db walletdb.DB) {
|
||||||
|
tx, err := db.BeginReadWriteTx()
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotNil(t, tx)
|
||||||
|
|
||||||
|
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotNil(t, apple)
|
||||||
|
|
||||||
|
require.NoError(t, apple.Put([]byte("testKey"), []byte("testVal")))
|
||||||
|
|
||||||
|
require.NoError(t, tx.Rollback())
|
||||||
|
require.Error(t, walletdb.ErrTxClosed, tx.Commit())
|
||||||
|
}
|
14
kvdb/test.go
Normal file
14
kvdb/test.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package kvdb
|
||||||
|
|
||||||
|
type KV struct {
|
||||||
|
key string
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func reverseKVs(a []KV) []KV {
|
||||||
|
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
a[i], a[j] = a[j], a[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user