Merge pull request #5481 from bottlepay/kvdb-tests

kvdb: generalize etcd tests
This commit is contained in:
Oliver Gugger 2021-07-15 11:49:14 +02:00 committed by GitHub
commit b92462edad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 439 additions and 336 deletions

44
kvdb/bolt_fixture.go Normal file

@ -0,0 +1,44 @@
package kvdb
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/stretchr/testify/require"
)
type boltFixture struct {
t *testing.T
tempDir string
}
func NewBoltFixture(t *testing.T) *boltFixture {
tempDir, err := ioutil.TempDir("", "test")
require.NoError(t, err)
return &boltFixture{
t: t,
tempDir: tempDir,
}
}
func (b *boltFixture) Cleanup() {
os.RemoveAll(b.tempDir)
}
func (b *boltFixture) NewBackend() walletdb.DB {
dbPath := filepath.Join(b.tempDir)
db, err := GetBoltBackend(&BoltBackendConfig{
DBPath: dbPath,
DBFileName: "test.db",
NoFreelistSync: true,
DBTimeout: DefaultDBTimeout,
})
require.NoError(b.t, err)
return db
}

80
kvdb/bolt_test.go Normal file

@ -0,0 +1,80 @@
package kvdb
import (
"testing"
"github.com/btcsuite/btcwallet/walletdb"
)
func TestBolt(t *testing.T) {
tests := []struct {
name string
test func(*testing.T, walletdb.DB)
}{
{
name: "read cursor empty interval",
test: testReadCursorEmptyInterval,
},
{
name: "read cursor non empty interval",
test: testReadCursorNonEmptyInterval,
},
{
name: "read write cursor",
test: testReadWriteCursor,
},
{
name: "read write cursor with bucket and value",
test: testReadWriteCursorWithBucketAndValue,
},
{
name: "bucket creation",
test: testBucketCreation,
},
{
name: "bucket deletion",
test: testBucketDeletion,
},
{
name: "bucket for each",
test: testBucketForEach,
},
{
name: "bucket for each with error",
test: testBucketForEachWithError,
},
{
name: "bucket sequence",
test: testBucketSequence,
},
{
name: "key clash",
test: testKeyClash,
},
{
name: "bucket create delete",
test: testBucketCreateDelete,
},
{
name: "tx manual commit",
test: testTxManualCommit,
},
{
name: "tx rollback",
test: testTxRollback,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
f := NewBoltFixture(t)
defer f.Cleanup()
test.test(t, f.NewBackend())
})
}
}

@ -90,3 +90,42 @@ func getKeyVal(kv *KV) ([]byte, []byte) {
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)
expected := map[string]string{
bkey("apple"): bval("apple"),
vkey("key", "apple"): "val",
BucketKey("apple"): BucketVal("apple"),
ValueKey("key", "apple"): "val",
}
require.Equal(t, expected, f.Dump())
}

@ -9,6 +9,8 @@ import (
"testing"
"time"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/stretchr/testify/require"
"go.etcd.io/etcd/clientv3"
"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.
func (f *EtcdTestFixture) Put(key, value string) {
ctx, cancel := context.WithTimeout(context.TODO(), testEtcdTimeout)

@ -96,11 +96,6 @@ func (c *readWriteCursor) Prev() (key, value []byte) {
// not exist, the cursor is moved to the next key after seek. Returns
// the new pair.
func (c *readWriteCursor) Seek(seek []byte) (key, value []byte) {
// Return nil if trying to seek to an empty key.
if seek == nil {
return nil, nil
}
// Seek to the first key with prefix + seek. If that key is not present
// STM will seek to the next matching key with prefix.
kv, err := c.bucket.tx.stm.Seek(c.prefix, c.prefix+string(seek))

@ -10,70 +10,6 @@ import (
"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) {
t.Parallel()
@ -94,12 +30,12 @@ func TestChangeDuringManualTx(t *testing.T) {
require.NoError(t, apple.Put([]byte("testKey"), []byte("testVal")))
// Try overwriting the bucket key.
f.Put(bkey("apple"), "banana")
f.Put(BucketKey("apple"), "banana")
// TODO: translate error
require.NotNil(t, tx.Commit())
require.Equal(t, map[string]string{
bkey("apple"): "banana",
BucketKey("apple"): "banana",
}, f.Dump())
}
@ -122,8 +58,8 @@ func TestChangeDuringUpdate(t *testing.T) {
require.NoError(t, apple.Put([]byte("key"), []byte("value")))
if count == 0 {
f.Put(vkey("key", "apple"), "new_value")
f.Put(vkey("key2", "apple"), "value2")
f.Put(ValueKey("key", "apple"), "new_value")
f.Put(ValueKey("key2", "apple"), "value2")
}
cursor := apple.ReadCursor()
@ -149,9 +85,9 @@ func TestChangeDuringUpdate(t *testing.T) {
require.Equal(t, count, 2)
expected := map[string]string{
bkey("apple"): bval("apple"),
vkey("key", "apple"): "value",
vkey("key2", "apple"): "value2",
BucketKey("apple"): BucketVal("apple"),
ValueKey("key", "apple"): "value",
ValueKey("key2", "apple"): "value2",
}
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)
}
})
}
}

@ -7,12 +7,17 @@ github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQY
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcwallet/walletdb v1.3.5-0.20210513043850-3a2f12e3a954 h1:CB6chiHPhZWmbCL7kFCADDf15V6I3EUNDgGC25jbptc=
github.com/btcsuite/btcwallet/walletdb v1.3.5-0.20210513043850-3a2f12e3a954/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
@ -109,7 +114,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -137,6 +141,7 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@ -160,7 +165,6 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
@ -174,7 +178,6 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -217,7 +220,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -233,7 +235,6 @@ google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@ -247,7 +248,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

@ -1,9 +1,6 @@
// +build kvdb_etcd
package etcd
package kvdb
import (
"context"
"fmt"
"math"
"testing"
@ -12,16 +9,8 @@ import (
"github.com/stretchr/testify/require"
)
func TestBucketCreation(t *testing.T) {
t.Parallel()
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 {
func testBucketCreation(t *testing.T, db walletdb.DB) {
err := Update(db, func(tx walletdb.ReadWriteTx) error {
// empty bucket name
b, err := tx.CreateTopLevelBucket(nil)
require.Error(t, walletdb.ErrBucketNameRequired, err)
@ -83,26 +72,10 @@ func TestBucketCreation(t *testing.T) {
}, func() {})
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) {
t.Parallel()
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 {
func testBucketDeletion(t *testing.T, db walletdb.DB) {
err := Update(db, func(tx walletdb.ReadWriteTx) error {
// "apple"
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
require.Nil(t, err)
@ -178,41 +151,16 @@ func TestBucketDeletion(t *testing.T) {
// "apple/pear" deleted
require.Nil(t, apple.NestedReadWriteBucket([]byte("pear")))
// "apple/pear/cherry" deleted
require.Nil(t, pear.NestedReadWriteBucket([]byte("cherry")))
// Values deleted too.
for _, kv := range kvs {
require.Nil(t, pear.Get([]byte(kv.key)))
require.Nil(t, cherry.Get([]byte(kv.key)))
}
// "aple/banana" exists
require.NotNil(t, apple.NestedReadWriteBucket([]byte("banana")))
return nil
}, func() {})
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) {
t.Parallel()
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 {
func testBucketForEach(t *testing.T, db walletdb.DB) {
err := Update(db, func(tx walletdb.ReadWriteTx) error {
// "apple"
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
require.Nil(t, err)
@ -265,30 +213,10 @@ func TestBucketForEach(t *testing.T) {
}, func() {})
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) {
t.Parallel()
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 {
func testBucketForEachWithError(t *testing.T, db walletdb.DB) {
err := Update(db, func(tx walletdb.ReadWriteTx) error {
// "apple"
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
require.Nil(t, err)
@ -358,27 +286,10 @@ func TestBucketForEachWithError(t *testing.T) {
}, func() {})
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) {
t.Parallel()
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 {
func testBucketSequence(t *testing.T, db walletdb.DB) {
err := Update(db, func(tx walletdb.ReadWriteTx) error {
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
require.Nil(t, err)
require.NotNil(t, apple)
@ -408,19 +319,11 @@ func TestBucketSequence(t *testing.T) {
// 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
// a bucket with the same key exists.
func TestKeyClash(t *testing.T) {
t.Parallel()
f := NewEtcdTestFixture(t)
defer f.Cleanup()
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
require.NoError(t, err)
func testKeyClash(t *testing.T, db walletdb.DB) {
// First:
// put: /apple/key -> val
// 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"))
require.Nil(t, err)
require.NotNil(t, apple)
@ -439,7 +342,7 @@ func TestKeyClash(t *testing.T) {
// Next try to:
// put: /apple/banana -> val => will fail (as /apple/banana is a bucket)
// 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"))
require.Nil(t, err)
require.NotNil(t, apple)
@ -461,31 +364,12 @@ func TestKeyClash(t *testing.T) {
}, func() {})
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
// bucket suceeds.
func TestBucketCreateDelete(t *testing.T) {
t.Parallel()
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 {
func testBucketCreateDelete(t *testing.T, db walletdb.DB) {
err := Update(db, func(tx walletdb.ReadWriteTx) error {
apple, err := tx.CreateTopLevelBucket([]byte("apple"))
require.NoError(t, err)
require.NotNil(t, apple)
@ -498,7 +382,7 @@ func TestBucketCreateDelete(t *testing.T) {
}, func() {})
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"))
require.NotNil(t, apple)
require.NoError(t, apple.DeleteNestedBucket([]byte("banana")))
@ -507,7 +391,7 @@ func TestBucketCreateDelete(t *testing.T) {
}, func() {})
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"))
require.NotNil(t, apple)
require.NoError(t, apple.Put([]byte("banana"), []byte("value")))
@ -515,10 +399,4 @@ func TestBucketCreateDelete(t *testing.T) {
return nil
}, func() {})
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 etcd
package kvdb
import (
"context"
"testing"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/stretchr/testify/require"
)
func TestReadCursorEmptyInterval(t *testing.T) {
t.Parallel()
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 {
func testReadCursorEmptyInterval(t *testing.T, db walletdb.DB) {
err := Update(db, func(tx walletdb.ReadWriteTx) error {
b, err := tx.CreateTopLevelBucket([]byte("apple"))
require.NoError(t, err)
require.NotNil(t, b)
@ -28,7 +17,7 @@ func TestReadCursorEmptyInterval(t *testing.T) {
}, func() {})
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"))
require.NotNil(t, b)
@ -54,15 +43,7 @@ func TestReadCursorEmptyInterval(t *testing.T) {
require.NoError(t, err)
}
func TestReadCursorNonEmptyInterval(t *testing.T) {
t.Parallel()
f := NewEtcdTestFixture(t)
defer f.Cleanup()
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
require.NoError(t, err)
func testReadCursorNonEmptyInterval(t *testing.T, db walletdb.DB) {
testKeyValues := []KV{
{"b", "1"},
{"c", "2"},
@ -70,7 +51,7 @@ func TestReadCursorNonEmptyInterval(t *testing.T) {
{"e", "4"},
}
err = db.Update(func(tx walletdb.ReadWriteTx) error {
err := Update(db, func(tx walletdb.ReadWriteTx) error {
b, err := tx.CreateTopLevelBucket([]byte("apple"))
require.NoError(t, err)
require.NotNil(t, b)
@ -83,7 +64,7 @@ func TestReadCursorNonEmptyInterval(t *testing.T) {
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"))
require.NotNil(t, b)
@ -118,8 +99,8 @@ func TestReadCursorNonEmptyInterval(t *testing.T) {
// Seek to nonexisting key.
k, v = cursor.Seek(nil)
require.Nil(t, k)
require.Nil(t, v)
require.Equal(t, "b", string(k))
require.Equal(t, "1", string(v))
k, v = cursor.Seek([]byte("x"))
require.Nil(t, k)
@ -131,15 +112,7 @@ func TestReadCursorNonEmptyInterval(t *testing.T) {
require.NoError(t, err)
}
func TestReadWriteCursor(t *testing.T) {
t.Parallel()
f := NewEtcdTestFixture(t)
defer f.Cleanup()
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
require.NoError(t, err)
func testReadWriteCursor(t *testing.T, db walletdb.DB) {
testKeyValues := []KV{
{"b", "1"},
{"c", "2"},
@ -150,7 +123,7 @@ func TestReadWriteCursor(t *testing.T) {
count := len(testKeyValues)
// 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"))
require.NoError(t, err)
require.NotNil(t, b)
@ -165,13 +138,13 @@ func TestReadWriteCursor(t *testing.T) {
return nil
}, func() {}))
err = db.Update(func(tx walletdb.ReadWriteTx) error {
err := Update(db, func(tx walletdb.ReadWriteTx) error {
b := tx.ReadWriteBucket([]byte("apple"))
require.NotNil(t, b)
// Store the second half of the interval.
for i := count / 2; i < count; i++ {
err = b.Put(
err := b.Put(
[]byte(testKeyValues[i].key),
[]byte(testKeyValues[i].val),
)
@ -280,32 +253,14 @@ func TestReadWriteCursor(t *testing.T) {
}, func() {})
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.
func TestReadWriteCursorWithBucketAndValue(t *testing.T) {
t.Parallel()
f := NewEtcdTestFixture(t)
defer f.Cleanup()
db, err := newEtcdBackend(context.TODO(), f.BackendConfig())
require.NoError(t, err)
func testReadWriteCursorWithBucketAndValue(t *testing.T, db walletdb.DB) {
// 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"))
require.NoError(t, err)
require.NotNil(t, b)
@ -323,7 +278,7 @@ func TestReadWriteCursorWithBucketAndValue(t *testing.T) {
return nil
}, func() {}))
err = db.View(func(tx walletdb.ReadTx) error {
err := View(db, func(tx walletdb.ReadTx) error {
b := tx.ReadBucket([]byte("apple"))
require.NotNil(t, b)
@ -358,12 +313,4 @@ func TestReadWriteCursorWithBucketAndValue(t *testing.T) {
}, func() {})
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
}