You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
8.0 KiB
368 lines
8.0 KiB
// +build kvdb_etcd |
|
|
|
package etcd |
|
|
|
import ( |
|
"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(f.BackendConfig()) |
|
require.NoError(t, err) |
|
|
|
err = db.Update(func(tx walletdb.ReadWriteTx) error { |
|
b, err := tx.CreateTopLevelBucket([]byte("apple")) |
|
require.NoError(t, err) |
|
require.NotNil(t, b) |
|
|
|
return nil |
|
}) |
|
require.NoError(t, err) |
|
|
|
err = db.View(func(tx walletdb.ReadTx) error { |
|
b := tx.ReadBucket([]byte("apple")) |
|
require.NotNil(t, b) |
|
|
|
cursor := b.ReadCursor() |
|
k, v := cursor.First() |
|
require.Nil(t, k) |
|
require.Nil(t, v) |
|
|
|
k, v = cursor.Next() |
|
require.Nil(t, k) |
|
require.Nil(t, v) |
|
|
|
k, v = cursor.Last() |
|
require.Nil(t, k) |
|
require.Nil(t, v) |
|
|
|
k, v = cursor.Prev() |
|
require.Nil(t, k) |
|
require.Nil(t, v) |
|
|
|
return nil |
|
}, func() {}) |
|
require.NoError(t, err) |
|
} |
|
|
|
func TestReadCursorNonEmptyInterval(t *testing.T) { |
|
t.Parallel() |
|
|
|
f := NewEtcdTestFixture(t) |
|
defer f.Cleanup() |
|
|
|
db, err := newEtcdBackend(f.BackendConfig()) |
|
require.NoError(t, err) |
|
|
|
testKeyValues := []KV{ |
|
{"b", "1"}, |
|
{"c", "2"}, |
|
{"da", "3"}, |
|
{"e", "4"}, |
|
} |
|
|
|
err = db.Update(func(tx walletdb.ReadWriteTx) error { |
|
b, err := tx.CreateTopLevelBucket([]byte("apple")) |
|
require.NoError(t, err) |
|
require.NotNil(t, b) |
|
|
|
for _, kv := range testKeyValues { |
|
require.NoError(t, b.Put([]byte(kv.key), []byte(kv.val))) |
|
} |
|
return nil |
|
}) |
|
|
|
require.NoError(t, err) |
|
|
|
err = db.View(func(tx walletdb.ReadTx) error { |
|
b := tx.ReadBucket([]byte("apple")) |
|
require.NotNil(t, b) |
|
|
|
// Iterate from the front. |
|
var kvs []KV |
|
cursor := b.ReadCursor() |
|
k, v := cursor.First() |
|
|
|
for k != nil && v != nil { |
|
kvs = append(kvs, KV{string(k), string(v)}) |
|
k, v = cursor.Next() |
|
} |
|
require.Equal(t, testKeyValues, kvs) |
|
|
|
// Iterate from the back. |
|
kvs = []KV{} |
|
k, v = cursor.Last() |
|
|
|
for k != nil && v != nil { |
|
kvs = append(kvs, KV{string(k), string(v)}) |
|
k, v = cursor.Prev() |
|
} |
|
require.Equal(t, reverseKVs(testKeyValues), kvs) |
|
|
|
// Random access |
|
perm := []int{3, 0, 2, 1} |
|
for _, i := range perm { |
|
k, v := cursor.Seek([]byte(testKeyValues[i].key)) |
|
require.Equal(t, []byte(testKeyValues[i].key), k) |
|
require.Equal(t, []byte(testKeyValues[i].val), v) |
|
} |
|
|
|
// Seek to nonexisting key. |
|
k, v = cursor.Seek(nil) |
|
require.Nil(t, k) |
|
require.Nil(t, v) |
|
|
|
k, v = cursor.Seek([]byte("x")) |
|
require.Nil(t, k) |
|
require.Nil(t, v) |
|
|
|
return nil |
|
}, func() {}) |
|
|
|
require.NoError(t, err) |
|
} |
|
|
|
func TestReadWriteCursor(t *testing.T) { |
|
t.Parallel() |
|
|
|
f := NewEtcdTestFixture(t) |
|
defer f.Cleanup() |
|
|
|
db, err := newEtcdBackend(f.BackendConfig()) |
|
require.NoError(t, err) |
|
|
|
testKeyValues := []KV{ |
|
{"b", "1"}, |
|
{"c", "2"}, |
|
{"da", "3"}, |
|
{"e", "4"}, |
|
} |
|
|
|
count := len(testKeyValues) |
|
|
|
// Pre-store the first half of the interval. |
|
require.NoError(t, db.Update(func(tx walletdb.ReadWriteTx) error { |
|
b, err := tx.CreateTopLevelBucket([]byte("apple")) |
|
require.NoError(t, err) |
|
require.NotNil(t, b) |
|
|
|
for i := 0; i < count/2; i++ { |
|
err = b.Put( |
|
[]byte(testKeyValues[i].key), |
|
[]byte(testKeyValues[i].val), |
|
) |
|
require.NoError(t, err) |
|
} |
|
return nil |
|
})) |
|
|
|
err = db.Update(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( |
|
[]byte(testKeyValues[i].key), |
|
[]byte(testKeyValues[i].val), |
|
) |
|
require.NoError(t, err) |
|
} |
|
|
|
cursor := b.ReadWriteCursor() |
|
|
|
// First on valid interval. |
|
fk, fv := cursor.First() |
|
require.Equal(t, []byte("b"), fk) |
|
require.Equal(t, []byte("1"), fv) |
|
|
|
// Prev(First()) = nil |
|
k, v := cursor.Prev() |
|
require.Nil(t, k) |
|
require.Nil(t, v) |
|
|
|
// Last on valid interval. |
|
lk, lv := cursor.Last() |
|
require.Equal(t, []byte("e"), lk) |
|
require.Equal(t, []byte("4"), lv) |
|
|
|
// Next(Last()) = nil |
|
k, v = cursor.Next() |
|
require.Nil(t, k) |
|
require.Nil(t, v) |
|
|
|
// Delete first item, then add an item before the |
|
// deleted one. Check that First/Next will "jump" |
|
// over the deleted item and return the new first. |
|
_, _ = cursor.First() |
|
require.NoError(t, cursor.Delete()) |
|
require.NoError(t, b.Put([]byte("a"), []byte("0"))) |
|
fk, fv = cursor.First() |
|
|
|
require.Equal(t, []byte("a"), fk) |
|
require.Equal(t, []byte("0"), fv) |
|
|
|
k, v = cursor.Next() |
|
require.Equal(t, []byte("c"), k) |
|
require.Equal(t, []byte("2"), v) |
|
|
|
// Similarly test that a new end is returned if |
|
// the old end is deleted first. |
|
_, _ = cursor.Last() |
|
require.NoError(t, cursor.Delete()) |
|
require.NoError(t, b.Put([]byte("f"), []byte("5"))) |
|
|
|
lk, lv = cursor.Last() |
|
require.Equal(t, []byte("f"), lk) |
|
require.Equal(t, []byte("5"), lv) |
|
|
|
k, v = cursor.Prev() |
|
require.Equal(t, []byte("da"), k) |
|
require.Equal(t, []byte("3"), v) |
|
|
|
// Overwrite k/v in the middle of the interval. |
|
require.NoError(t, b.Put([]byte("c"), []byte("3"))) |
|
k, v = cursor.Prev() |
|
require.Equal(t, []byte("c"), k) |
|
require.Equal(t, []byte("3"), v) |
|
|
|
// Insert new key/values. |
|
require.NoError(t, b.Put([]byte("cx"), []byte("x"))) |
|
require.NoError(t, b.Put([]byte("cy"), []byte("y"))) |
|
|
|
k, v = cursor.Next() |
|
require.Equal(t, []byte("cx"), k) |
|
require.Equal(t, []byte("x"), v) |
|
|
|
k, v = cursor.Next() |
|
require.Equal(t, []byte("cy"), k) |
|
require.Equal(t, []byte("y"), v) |
|
|
|
expected := []KV{ |
|
{"a", "0"}, |
|
{"c", "3"}, |
|
{"cx", "x"}, |
|
{"cy", "y"}, |
|
{"da", "3"}, |
|
{"f", "5"}, |
|
} |
|
|
|
// Iterate from the front. |
|
var kvs []KV |
|
k, v = cursor.First() |
|
|
|
for k != nil && v != nil { |
|
kvs = append(kvs, KV{string(k), string(v)}) |
|
k, v = cursor.Next() |
|
} |
|
require.Equal(t, expected, kvs) |
|
|
|
// Iterate from the back. |
|
kvs = []KV{} |
|
k, v = cursor.Last() |
|
|
|
for k != nil && v != nil { |
|
kvs = append(kvs, KV{string(k), string(v)}) |
|
k, v = cursor.Prev() |
|
} |
|
require.Equal(t, reverseKVs(expected), kvs) |
|
|
|
return nil |
|
}) |
|
|
|
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 |
|
// 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(f.BackendConfig()) |
|
require.NoError(t, err) |
|
|
|
// Pre-store the first half of the interval. |
|
require.NoError(t, db.Update(func(tx walletdb.ReadWriteTx) error { |
|
b, err := tx.CreateTopLevelBucket([]byte("apple")) |
|
require.NoError(t, err) |
|
require.NotNil(t, b) |
|
|
|
require.NoError(t, b.Put([]byte("key"), []byte("val"))) |
|
|
|
b1, err := b.CreateBucket([]byte("banana")) |
|
require.NoError(t, err) |
|
require.NotNil(t, b1) |
|
|
|
b2, err := b.CreateBucket([]byte("pear")) |
|
require.NoError(t, err) |
|
require.NotNil(t, b2) |
|
|
|
return nil |
|
})) |
|
|
|
err = db.View(func(tx walletdb.ReadTx) error { |
|
b := tx.ReadBucket([]byte("apple")) |
|
require.NotNil(t, b) |
|
|
|
cursor := b.ReadCursor() |
|
|
|
// First on valid interval. |
|
k, v := cursor.First() |
|
require.Equal(t, []byte("banana"), k) |
|
require.Nil(t, v) |
|
|
|
k, v = cursor.Next() |
|
require.Equal(t, []byte("key"), k) |
|
require.Equal(t, []byte("val"), v) |
|
|
|
k, v = cursor.Last() |
|
require.Equal(t, []byte("pear"), k) |
|
require.Nil(t, v) |
|
|
|
k, v = cursor.Seek([]byte("k")) |
|
require.Equal(t, []byte("key"), k) |
|
require.Equal(t, []byte("val"), v) |
|
|
|
k, v = cursor.Seek([]byte("banana")) |
|
require.Equal(t, []byte("banana"), k) |
|
require.Nil(t, v) |
|
|
|
k, v = cursor.Next() |
|
require.Equal(t, []byte("key"), k) |
|
require.Equal(t, []byte("val"), v) |
|
|
|
return nil |
|
}, 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()) |
|
}
|
|
|