kvdb/test: generalize etcd tests

This commit is contained in:
Joost Jager 2021-07-06 16:31:24 +02:00
parent bb5c3f3b51
commit 3c6d35ec41
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
10 changed files with 307 additions and 313 deletions

@ -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

@ -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

@ -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

@ -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
}