Add full test coverage suite for walletdb package.
This commit is contained in:
parent
e8b4de9379
commit
d7c00f48d7
6 changed files with 2023 additions and 0 deletions
201
walletdb/bdb/driver_test.go
Normal file
201
walletdb/bdb/driver_test.go
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bdb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
|
_ "github.com/conformal/btcwallet/walletdb/bdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dbType is the database type name for this driver.
|
||||||
|
const dbType = "bdb"
|
||||||
|
|
||||||
|
// TestCreateOpenFail ensures that errors related to creating and opening a
|
||||||
|
// database are handled properly.
|
||||||
|
func TestCreateOpenFail(t *testing.T) {
|
||||||
|
// Ensure that attempting to open a database that doesn't exist returns
|
||||||
|
// the expected error.
|
||||||
|
wantErr := walletdb.ErrDbDoesNotExist
|
||||||
|
if _, err := walletdb.Open(dbType, "noexist.db"); err != wantErr {
|
||||||
|
t.Errorf("Open: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting to open a database with the wrong number of
|
||||||
|
// parameters returns the expected error.
|
||||||
|
wantErr = fmt.Errorf("invalid arguments to %s.Open -- expected "+
|
||||||
|
"database path", dbType)
|
||||||
|
if _, err := walletdb.Open(dbType, 1, 2, 3); err.Error() != wantErr.Error() {
|
||||||
|
t.Errorf("Open: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting to open a database with an invalid type for
|
||||||
|
// the first parameter returns the expected error.
|
||||||
|
wantErr = fmt.Errorf("first argument to %s.Open is invalid -- "+
|
||||||
|
"expected database path string", dbType)
|
||||||
|
if _, err := walletdb.Open(dbType, 1); err.Error() != wantErr.Error() {
|
||||||
|
t.Errorf("Open: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting to create a database with the wrong number of
|
||||||
|
// parameters returns the expected error.
|
||||||
|
wantErr = fmt.Errorf("invalid arguments to %s.Create -- expected "+
|
||||||
|
"database path", dbType)
|
||||||
|
if _, err := walletdb.Create(dbType, 1, 2, 3); err.Error() != wantErr.Error() {
|
||||||
|
t.Errorf("Create: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting to open a database with an invalid type for
|
||||||
|
// the first parameter returns the expected error.
|
||||||
|
wantErr = fmt.Errorf("first argument to %s.Create is invalid -- "+
|
||||||
|
"expected database path string", dbType)
|
||||||
|
if _, err := walletdb.Create(dbType, 1); err.Error() != wantErr.Error() {
|
||||||
|
t.Errorf("Create: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure operations against a closed database return the expected
|
||||||
|
// error.
|
||||||
|
dbPath := "createfail.db"
|
||||||
|
db, err := walletdb.Create(dbType, dbPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Create: unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(dbPath)
|
||||||
|
db.Close()
|
||||||
|
|
||||||
|
wantErr = walletdb.ErrDbNotOpen
|
||||||
|
if _, err := db.Namespace([]byte("ns1")); err != wantErr {
|
||||||
|
t.Errorf("Namespace: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPersistence ensures that values stored are still valid after closing and
|
||||||
|
// reopening the database.
|
||||||
|
func TestPersistence(t *testing.T) {
|
||||||
|
// Create a new database to run tests against.
|
||||||
|
dbPath := "persistencetest.db"
|
||||||
|
db, err := walletdb.Create(dbType, dbPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to create test database (%s) %v", dbType, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(dbPath)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Create a namespace and put some values into it so they can be tested
|
||||||
|
// for existence on re-open.
|
||||||
|
storeValues := map[string]string{
|
||||||
|
"ns1key1": "foo1",
|
||||||
|
"ns1key2": "foo2",
|
||||||
|
"ns1key3": "foo3",
|
||||||
|
}
|
||||||
|
ns1Key := []byte("ns1")
|
||||||
|
ns1, err := db.Namespace(ns1Key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Namespace: unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = ns1.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range storeValues {
|
||||||
|
if err := rootBucket.Put([]byte(k), []byte(v)); err != nil {
|
||||||
|
return fmt.Errorf("Put: unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ns1 Update: unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close and reopen the database to ensure the values persist.
|
||||||
|
db.Close()
|
||||||
|
db, err = walletdb.Open(dbType, dbPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to open test database (%s) %v", dbType, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Ensure the values previously stored in the 3rd namespace still exist
|
||||||
|
// and are correct.
|
||||||
|
ns1, err = db.Namespace(ns1Key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Namespace: unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = ns1.View(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range storeValues {
|
||||||
|
gotVal := rootBucket.Get([]byte(k))
|
||||||
|
if !reflect.DeepEqual(gotVal, []byte(v)) {
|
||||||
|
return fmt.Errorf("Get: key '%s' does not "+
|
||||||
|
"match expected value - got %s, want %s",
|
||||||
|
k, gotVal, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ns1 View: unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInterface performs all interfaces tests for this database driver.
|
||||||
|
func TestInterface(t *testing.T) {
|
||||||
|
// Create a new database to run tests against.
|
||||||
|
dbPath := "interfacetest.db"
|
||||||
|
db, err := walletdb.Create(dbType, dbPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to create test database (%s) %v", dbType, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(dbPath)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Run all of the interface tests against the database.
|
||||||
|
testInterface(t, db)
|
||||||
|
}
|
817
walletdb/bdb/interface_test.go
Normal file
817
walletdb/bdb/interface_test.go
Normal file
|
@ -0,0 +1,817 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file intended to be copied into each backend driver directory. Each
|
||||||
|
// driver should have their own driver_test.go file which creates a database and
|
||||||
|
// invokes the testInterface function in this file to ensure the driver properly
|
||||||
|
// implements the interface. See the bdb backend driver for a working example.
|
||||||
|
//
|
||||||
|
// NOTE: When copying this file into the backend driver folder, the package name
|
||||||
|
// will need to be changed accordingly.
|
||||||
|
|
||||||
|
package bdb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// subTestFailError is used to signal that a sub test returned false.
|
||||||
|
var subTestFailError = fmt.Errorf("sub test failure")
|
||||||
|
|
||||||
|
// testContext is used to store context information about a running test which
|
||||||
|
// is passed into helper functions.
|
||||||
|
type testContext struct {
|
||||||
|
t *testing.T
|
||||||
|
db walletdb.DB
|
||||||
|
bucketDepth int
|
||||||
|
isWritable bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollbackValues returns a copy of the provided map with all values set to an
|
||||||
|
// empty string. This is used to test that values are properly rolled back.
|
||||||
|
func rollbackValues(values map[string]string) map[string]string {
|
||||||
|
retMap := make(map[string]string, len(values))
|
||||||
|
for k := range values {
|
||||||
|
retMap[k] = ""
|
||||||
|
}
|
||||||
|
return retMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// testGetValues checks that all of the provided key/value pairs can be
|
||||||
|
// retrieved from the database and the retrieved values match the provided
|
||||||
|
// values.
|
||||||
|
func testGetValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool {
|
||||||
|
for k, v := range values {
|
||||||
|
var vBytes []byte
|
||||||
|
if v != "" {
|
||||||
|
vBytes = []byte(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotValue := bucket.Get([]byte(k))
|
||||||
|
if !reflect.DeepEqual(gotValue, vBytes) {
|
||||||
|
tc.t.Errorf("Get: unexpected value - got %s, want %s",
|
||||||
|
gotValue, vBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testPutValues stores all of the provided key/value pairs in the provided
|
||||||
|
// bucket while checking for errors.
|
||||||
|
func testPutValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool {
|
||||||
|
for k, v := range values {
|
||||||
|
var vBytes []byte
|
||||||
|
if v != "" {
|
||||||
|
vBytes = []byte(v)
|
||||||
|
}
|
||||||
|
if err := bucket.Put([]byte(k), vBytes); err != nil {
|
||||||
|
tc.t.Errorf("Put: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testDeleteValues removes all of the provided key/value pairs from the
|
||||||
|
// provided bucket.
|
||||||
|
func testDeleteValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool {
|
||||||
|
for k := range values {
|
||||||
|
if err := bucket.Delete([]byte(k)); err != nil {
|
||||||
|
tc.t.Errorf("Delete: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testNestedBucket reruns the testBucketInterface against a nested bucket along
|
||||||
|
// with a counter to only test a couple of level deep.
|
||||||
|
func testNestedBucket(tc *testContext, testBucket walletdb.Bucket) bool {
|
||||||
|
// Don't go more than 2 nested level deep.
|
||||||
|
if tc.bucketDepth > 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.bucketDepth++
|
||||||
|
defer func() {
|
||||||
|
tc.bucketDepth--
|
||||||
|
}()
|
||||||
|
if !testBucketInterface(tc, testBucket) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBucketInterface ensures the bucket interface is working properly by
|
||||||
|
// exercising all of its functions.
|
||||||
|
func testBucketInterface(tc *testContext, bucket walletdb.Bucket) bool {
|
||||||
|
if bucket.Writable() != tc.isWritable {
|
||||||
|
tc.t.Errorf("Bucket writable state does not match.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.isWritable {
|
||||||
|
// keyValues holds the keys and values to use when putting
|
||||||
|
// values into the bucket.
|
||||||
|
var keyValues = map[string]string{
|
||||||
|
"bucketkey1": "foo1",
|
||||||
|
"bucketkey2": "foo2",
|
||||||
|
"bucketkey3": "foo3",
|
||||||
|
}
|
||||||
|
if !testPutValues(tc, bucket, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testGetValues(tc, bucket, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate all of the keys using ForEach while making sure the
|
||||||
|
// stored values are the expected values.
|
||||||
|
keysFound := make(map[string]struct{}, len(keyValues))
|
||||||
|
err := bucket.ForEach(func(k, v []byte) error {
|
||||||
|
kString := string(k)
|
||||||
|
wantV, ok := keyValues[kString]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("ForEach: key '%s' should "+
|
||||||
|
"exist", kString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(v, []byte(wantV)) {
|
||||||
|
return fmt.Errorf("ForEach: value for key '%s' "+
|
||||||
|
"does not match - got %s, want %s",
|
||||||
|
kString, v, wantV)
|
||||||
|
}
|
||||||
|
|
||||||
|
keysFound[kString] = struct{}{}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all keys were iterated.
|
||||||
|
for k := range keyValues {
|
||||||
|
if _, ok := keysFound[k]; !ok {
|
||||||
|
tc.t.Errorf("ForEach: key '%s' was not iterated "+
|
||||||
|
"when it should have been", k)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the keys and ensure they were deleted.
|
||||||
|
if !testDeleteValues(tc, bucket, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !testGetValues(tc, bucket, rollbackValues(keyValues)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure creating a new bucket works as expected.
|
||||||
|
testBucketName := []byte("testbucket")
|
||||||
|
testBucket, err := bucket.CreateBucket(testBucketName)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("CreateBucket: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !testNestedBucket(tc, testBucket) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure creating a bucket that already exists fails with the
|
||||||
|
// expected error.
|
||||||
|
wantErr := walletdb.ErrBucketExists
|
||||||
|
if _, err := bucket.CreateBucket(testBucketName); err != wantErr {
|
||||||
|
tc.t.Errorf("CreateBucket: unexpected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure CreateBucketIfNotExists returns an existing bucket.
|
||||||
|
testBucket, err = bucket.CreateBucketIfNotExists(testBucketName)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("CreateBucketIfNotExists: unexpected "+
|
||||||
|
"error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !testNestedBucket(tc, testBucket) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure retrieving and existing bucket works as expected.
|
||||||
|
testBucket = bucket.Bucket(testBucketName)
|
||||||
|
if !testNestedBucket(tc, testBucket) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure deleting a bucket works as intended.
|
||||||
|
if err := bucket.DeleteBucket(testBucketName); err != nil {
|
||||||
|
tc.t.Errorf("DeleteBucket: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if b := bucket.Bucket(testBucketName); b != nil {
|
||||||
|
tc.t.Errorf("DeleteBucket: bucket '%s' still exists",
|
||||||
|
testBucketName)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure deleting a bucket that doesn't exist returns the
|
||||||
|
// expected error.
|
||||||
|
wantErr = walletdb.ErrBucketNotFound
|
||||||
|
if err := bucket.DeleteBucket(testBucketName); err != wantErr {
|
||||||
|
tc.t.Errorf("DeleteBucket: unexpected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure CreateBucketIfNotExists creates a new bucket when
|
||||||
|
// it doesn't already exist.
|
||||||
|
testBucket, err = bucket.CreateBucketIfNotExists(testBucketName)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("CreateBucketIfNotExists: unexpected "+
|
||||||
|
"error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !testNestedBucket(tc, testBucket) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the test bucket to avoid leaving it around for future
|
||||||
|
// calls.
|
||||||
|
if err := bucket.DeleteBucket(testBucketName); err != nil {
|
||||||
|
tc.t.Errorf("DeleteBucket: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if b := bucket.Bucket(testBucketName); b != nil {
|
||||||
|
tc.t.Errorf("DeleteBucket: bucket '%s' still exists",
|
||||||
|
testBucketName)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Put should fail with bucket that is not writable.
|
||||||
|
wantErr := walletdb.ErrTxNotWritable
|
||||||
|
failBytes := []byte("fail")
|
||||||
|
if err := bucket.Put(failBytes, failBytes); err != wantErr {
|
||||||
|
tc.t.Errorf("Put did not fail with unwritable bucket")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete should fail with bucket that is not writable.
|
||||||
|
if err := bucket.Delete(failBytes); err != wantErr {
|
||||||
|
tc.t.Errorf("Put did not fail with unwritable bucket")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucket should fail with bucket that is not writable.
|
||||||
|
if _, err := bucket.CreateBucket(failBytes); err != wantErr {
|
||||||
|
tc.t.Errorf("CreateBucket did not fail with unwritable " +
|
||||||
|
"bucket")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucketIfNotExists should fail with bucket that is not
|
||||||
|
// writable.
|
||||||
|
if _, err := bucket.CreateBucketIfNotExists(failBytes); err != wantErr {
|
||||||
|
tc.t.Errorf("CreateBucketIfNotExists did not fail with " +
|
||||||
|
"unwritable bucket")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBucket should fail with bucket that is not writable.
|
||||||
|
if err := bucket.DeleteBucket(failBytes); err != wantErr {
|
||||||
|
tc.t.Errorf("DeleteBucket did not fail with unwritable " +
|
||||||
|
"bucket")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testManualTxInterface ensures that manual transactions work as expected.
|
||||||
|
func testManualTxInterface(tc *testContext, namespace walletdb.Namespace) bool {
|
||||||
|
// populateValues tests that populating values works as expected.
|
||||||
|
//
|
||||||
|
// When the writable flag is false, a read-only tranasction is created,
|
||||||
|
// standard bucket tests for read-only transactions are performed, and
|
||||||
|
// the Commit function is checked to ensure it fails as expected.
|
||||||
|
//
|
||||||
|
// Otherwise, a read-write transaction is created, the values are
|
||||||
|
// written, standard bucket tests for read-write transactions are
|
||||||
|
// performed, and then the transaction is either commited or rolled
|
||||||
|
// back depending on the flag.
|
||||||
|
populateValues := func(writable, rollback bool, putValues map[string]string) bool {
|
||||||
|
tx, err := namespace.Begin(writable)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Begin: unexpected error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
tc.t.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.isWritable = writable
|
||||||
|
if !testBucketInterface(tc, rootBucket) {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !writable {
|
||||||
|
// The transaction is not writable, so it should fail
|
||||||
|
// the commit.
|
||||||
|
if err := tx.Commit(); err != walletdb.ErrTxNotWritable {
|
||||||
|
tc.t.Errorf("Commit: unexpected error %v, "+
|
||||||
|
"want %v", err, walletdb.ErrTxNotWritable)
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback the transaction.
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
tc.t.Errorf("Commit: unexpected error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !testPutValues(tc, rootBucket, putValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if rollback {
|
||||||
|
// Rollback the transaction.
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
tc.t.Errorf("Rollback: unexpected "+
|
||||||
|
"error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The commit should succeed.
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
tc.t.Errorf("Commit: unexpected error "+
|
||||||
|
"%v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkValues starts a read-only transaction and checks that all of
|
||||||
|
// the key/value pairs specified in the expectedValues parameter match
|
||||||
|
// what's in the database.
|
||||||
|
checkValues := func(expectedValues map[string]string) bool {
|
||||||
|
// Begin another read-only transaction to ensure...
|
||||||
|
tx, err := namespace.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Begin: unexpected error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
tc.t.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testGetValues(tc, rootBucket, expectedValues) {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback the read-only transaction.
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
tc.t.Errorf("Commit: unexpected error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteValues starts a read-write transaction and deletes the keys
|
||||||
|
// in the passed key/value pairs.
|
||||||
|
deleteValues := func(values map[string]string) bool {
|
||||||
|
tx, err := namespace.Begin(true)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
tc.t.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the keys and ensure they were deleted.
|
||||||
|
if !testDeleteValues(tc, rootBucket, values) {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !testGetValues(tc, rootBucket, rollbackValues(values)) {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the changes and ensure it was successful.
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
tc.t.Errorf("Commit: unexpected error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyValues holds the keys and values to use when putting values
|
||||||
|
// into a bucket.
|
||||||
|
var keyValues = map[string]string{
|
||||||
|
"umtxkey1": "foo1",
|
||||||
|
"umtxkey2": "foo2",
|
||||||
|
"umtxkey3": "foo3",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting populating the values using a read-only
|
||||||
|
// transaction fails as expected.
|
||||||
|
if !populateValues(false, true, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !checkValues(rollbackValues(keyValues)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting populating the values using a read-write
|
||||||
|
// transaction and then rolling it back yields the expected values.
|
||||||
|
if !populateValues(true, true, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !checkValues(rollbackValues(keyValues)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting populating the values using a read-write
|
||||||
|
// transaction and then committing it stores the expected values.
|
||||||
|
if !populateValues(true, false, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !checkValues(keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the keys.
|
||||||
|
if !deleteValues(keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testNamespaceAndTxInterfaces creates a namespace using the provided key and
|
||||||
|
// tests all facets of it interface as well as transaction and bucket
|
||||||
|
// interfaces under it.
|
||||||
|
func testNamespaceAndTxInterfaces(tc *testContext, namespaceKey string) bool {
|
||||||
|
namespaceKeyBytes := []byte(namespaceKey)
|
||||||
|
namespace, err := tc.db.Namespace(namespaceKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Namespace: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// Remove the namespace now that the tests are done for it.
|
||||||
|
if err := tc.db.DeleteNamespace(namespaceKeyBytes); err != nil {
|
||||||
|
tc.t.Errorf("DeleteNamespace: unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if !testManualTxInterface(tc, namespace) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyValues holds the keys and values to use when putting values
|
||||||
|
// into a bucket.
|
||||||
|
var keyValues = map[string]string{
|
||||||
|
"mtxkey1": "foo1",
|
||||||
|
"mtxkey2": "foo2",
|
||||||
|
"mtxkey3": "foo3",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the bucket interface via a managed read-only transaction.
|
||||||
|
err = namespace.View(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.isWritable = false
|
||||||
|
if !testBucketInterface(tc, rootBucket) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure errors returned from the user-supplied View function are
|
||||||
|
// returned.
|
||||||
|
viewError := fmt.Errorf("example view error")
|
||||||
|
err = namespace.View(func(tx walletdb.Tx) error {
|
||||||
|
return viewError
|
||||||
|
})
|
||||||
|
if err != viewError {
|
||||||
|
tc.t.Errorf("View: inner function error not returned - got "+
|
||||||
|
"%v, want %v", err, viewError)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the bucket interface via a managed read-write transaction.
|
||||||
|
// Also, put a series of values and force a rollback so the following
|
||||||
|
// code can ensure the values were not stored.
|
||||||
|
forceRollbackError := fmt.Errorf("force rollback")
|
||||||
|
err = namespace.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.isWritable = true
|
||||||
|
if !testBucketInterface(tc, rootBucket) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testPutValues(tc, rootBucket, keyValues) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an error to force a rollback.
|
||||||
|
return forceRollbackError
|
||||||
|
})
|
||||||
|
if err != forceRollbackError {
|
||||||
|
if err == subTestFailError {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.t.Errorf("Update: inner function error not returned - got "+
|
||||||
|
"%v, want %v", err, forceRollbackError)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the values that should have not been stored due to the forced
|
||||||
|
// rollback above were not actually stored.
|
||||||
|
err = namespace.View(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testGetValues(tc, rootBucket, rollbackValues(keyValues)) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store a series of values via a managed read-write transaction.
|
||||||
|
err = namespace.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testPutValues(tc, rootBucket, keyValues) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the values stored above were committed as expected.
|
||||||
|
err = namespace.View(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testGetValues(tc, rootBucket, keyValues) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the values stored above in a managed read-write transaction.
|
||||||
|
err = namespace.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testDeleteValues(tc, rootBucket, keyValues) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testAdditionalErrors performs some tests for error cases not covered
|
||||||
|
// elsewhere in the tests and therefore improves negative test coverage.
|
||||||
|
func testAdditionalErrors(tc *testContext) bool {
|
||||||
|
// Create a new namespace and then intentionally delete the namespace
|
||||||
|
// bucket out from under it to force errors.
|
||||||
|
ns3Key := []byte("ns3")
|
||||||
|
ns3, err := tc.db.Namespace(ns3Key)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Namespace: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := tc.db.DeleteNamespace(ns3Key); err != nil {
|
||||||
|
tc.t.Errorf("DeleteNamespace: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Begin fails when the namespace bucket does not exist.
|
||||||
|
wantErr := walletdb.ErrBucketNotFound
|
||||||
|
if _, err := ns3.Begin(false); err != wantErr {
|
||||||
|
tc.t.Errorf("Begin: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure View fails when the namespace bucket does not exist.
|
||||||
|
err = ns3.View(func(tx walletdb.Tx) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != wantErr {
|
||||||
|
tc.t.Errorf("View: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Update fails when the namespace bucket does not exist.
|
||||||
|
err = ns3.Update(func(tx walletdb.Tx) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != wantErr {
|
||||||
|
tc.t.Errorf("View: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate the namespace to bring the bucket back.
|
||||||
|
ns3, err = tc.db.Namespace(ns3Key)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Namespace: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// Remove the namespace now that the tests are done for it.
|
||||||
|
if err := tc.db.DeleteNamespace(ns3Key); err != nil {
|
||||||
|
tc.t.Errorf("DeleteNamespace: unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = ns3.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure CreateBucket returns the expected error when no bucket
|
||||||
|
// key is specified.
|
||||||
|
wantErr := walletdb.ErrBucketNameRequired
|
||||||
|
if _, err := rootBucket.CreateBucket(nil); err != wantErr {
|
||||||
|
return fmt.Errorf("CreateBucket: unexpected error - "+
|
||||||
|
"got %v, want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure DeleteBucket returns the expected error when no bucket
|
||||||
|
// key is specified.
|
||||||
|
wantErr = walletdb.ErrIncompatibleValue
|
||||||
|
if err := rootBucket.DeleteBucket(nil); err != wantErr {
|
||||||
|
return fmt.Errorf("DeleteBucket: unexpected error - "+
|
||||||
|
"got %v, want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Put returns the expected error when no key is
|
||||||
|
// specified.
|
||||||
|
wantErr = walletdb.ErrKeyRequired
|
||||||
|
if err := rootBucket.Put(nil, nil); err != wantErr {
|
||||||
|
return fmt.Errorf("Put: unexpected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting to rollback or commit a transaction that is
|
||||||
|
// already closed returns the expected error.
|
||||||
|
tx, err := ns3.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Begin: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
tc.t.Errorf("Rollback: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
wantErr = walletdb.ErrTxClosed
|
||||||
|
if err := tx.Rollback(); err != wantErr {
|
||||||
|
tc.t.Errorf("Rollback: unexpected error - got %v, want %v", err,
|
||||||
|
wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != wantErr {
|
||||||
|
tc.t.Errorf("Commit: unexpected error - got %v, want %v", err,
|
||||||
|
wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testInterface tests performs tests for the various interfaces of walletdb
|
||||||
|
// which require state in the database for the given database type.
|
||||||
|
func testInterface(t *testing.T, db walletdb.DB) {
|
||||||
|
// Create a test context to pass around.
|
||||||
|
context := testContext{t: t, db: db}
|
||||||
|
|
||||||
|
// Create a namespace and test the interface for it.
|
||||||
|
if !testNamespaceAndTxInterfaces(&context, "ns1") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a second namespace and test the interface for it.
|
||||||
|
if !testNamespaceAndTxInterfaces(&context, "ns2") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check a few more error conditions not covered elsewhere.
|
||||||
|
if !testAdditionalErrors(&context) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
7
walletdb/cov_report.sh
Normal file
7
walletdb/cov_report.sh
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script uses go tool cover to generate a test coverage report.
|
||||||
|
go test -coverprofile=cov.out && go tool cover -func=cov.out && rm -f cov.out
|
||||||
|
echo "============================================================"
|
||||||
|
(cd bdb && go test -coverprofile=cov.out && go tool cover -func=cov.out && \
|
||||||
|
rm -f cov.out)
|
142
walletdb/db_test.go
Normal file
142
walletdb/db_test.go
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package walletdb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
|
_ "github.com/conformal/btcwallet/walletdb/bdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ignoreDbTypes are types which should be ignored when running tests
|
||||||
|
// that iterate all supported DB types. This allows some tests to add
|
||||||
|
// bogus drivers for testing purposes while still allowing other tests
|
||||||
|
// to easily iterate all supported drivers.
|
||||||
|
ignoreDbTypes = map[string]bool{"createopenfail": true}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAddDuplicateDriver ensures that adding a duplicate driver does not
|
||||||
|
// overwrite an existing one.
|
||||||
|
func TestAddDuplicateDriver(t *testing.T) {
|
||||||
|
supportedDrivers := walletdb.SupportedDrivers()
|
||||||
|
if len(supportedDrivers) == 0 {
|
||||||
|
t.Errorf("no backends to test")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dbType := supportedDrivers[0]
|
||||||
|
|
||||||
|
// bogusCreateDB is a function which acts as a bogus create and open
|
||||||
|
// driver function and intentionally returns a failure that can be
|
||||||
|
// detected if the interface allows a duplicate driver to overwrite an
|
||||||
|
// existing one.
|
||||||
|
bogusCreateDB := func(args ...interface{}) (walletdb.DB, error) {
|
||||||
|
return nil, fmt.Errorf("duplicate driver allowed for database "+
|
||||||
|
"type [%v]", dbType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a driver that tries to replace an existing one. Set its
|
||||||
|
// create and open functions to a function that causes a test failure if
|
||||||
|
// they are invoked.
|
||||||
|
driver := walletdb.Driver{
|
||||||
|
DbType: dbType,
|
||||||
|
Create: bogusCreateDB,
|
||||||
|
Open: bogusCreateDB,
|
||||||
|
}
|
||||||
|
err := walletdb.RegisterDriver(driver)
|
||||||
|
if err != walletdb.ErrDbTypeRegistered {
|
||||||
|
t.Errorf("unexpected duplicate driver registration error - "+
|
||||||
|
"got %v, want %v", err, walletdb.ErrDbTypeRegistered)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbPath := "dupdrivertest.db"
|
||||||
|
db, err := walletdb.Create(dbType, dbPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to create database: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db.Close()
|
||||||
|
_ = os.Remove(dbPath)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCreateOpenFail ensures that errors which occur while opening or closing
|
||||||
|
// a database are handled properly.
|
||||||
|
func TestCreateOpenFail(t *testing.T) {
|
||||||
|
// bogusCreateDB is a function which acts as a bogus create and open
|
||||||
|
// driver function that intentionally returns a failure which can be
|
||||||
|
// detected.
|
||||||
|
dbType := "createopenfail"
|
||||||
|
openError := fmt.Errorf("failed to create or open database for "+
|
||||||
|
"database type [%v]", dbType)
|
||||||
|
bogusCreateDB := func(args ...interface{}) (walletdb.DB, error) {
|
||||||
|
return nil, openError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and add driver that intentionally fails when created or opened
|
||||||
|
// to ensure errors on database open and create are handled properly.
|
||||||
|
driver := walletdb.Driver{
|
||||||
|
DbType: dbType,
|
||||||
|
Create: bogusCreateDB,
|
||||||
|
Open: bogusCreateDB,
|
||||||
|
}
|
||||||
|
walletdb.RegisterDriver(driver)
|
||||||
|
|
||||||
|
// Ensure creating a database with the new type fails with the expected
|
||||||
|
// error.
|
||||||
|
_, err := walletdb.Create(dbType)
|
||||||
|
if err != openError {
|
||||||
|
t.Errorf("expected error not received - got: %v, want %v", err,
|
||||||
|
openError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure opening a database with the new type fails with the expected
|
||||||
|
// error.
|
||||||
|
_, err = walletdb.Open(dbType)
|
||||||
|
if err != openError {
|
||||||
|
t.Errorf("expected error not received - got: %v, want %v", err,
|
||||||
|
openError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCreateOpenUnsupported ensures that attempting to create or open an
|
||||||
|
// unsupported database type is handled properly.
|
||||||
|
func TestCreateOpenUnsupported(t *testing.T) {
|
||||||
|
// Ensure creating a database with an unsupported type fails with the
|
||||||
|
// expected error.
|
||||||
|
dbType := "unsupported"
|
||||||
|
_, err := walletdb.Create(dbType)
|
||||||
|
if err != walletdb.ErrDbUnknownType {
|
||||||
|
t.Errorf("expected error not received - got: %v, want %v", err,
|
||||||
|
walletdb.ErrDbUnknownType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure opening a database with the an unsupported type fails with the
|
||||||
|
// expected error.
|
||||||
|
_, err = walletdb.Open(dbType)
|
||||||
|
if err != walletdb.ErrDbUnknownType {
|
||||||
|
t.Errorf("expected error not received - got: %v, want %v", err,
|
||||||
|
walletdb.ErrDbUnknownType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
817
walletdb/interface_test.go
Normal file
817
walletdb/interface_test.go
Normal file
|
@ -0,0 +1,817 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file intended to be copied into each backend driver directory. Each
|
||||||
|
// driver should have their own driver_test.go file which creates a database and
|
||||||
|
// invokes the testInterface function in this file to ensure the driver properly
|
||||||
|
// implements the interface. See the bdb backend driver for a working example.
|
||||||
|
//
|
||||||
|
// NOTE: When copying this file into the backend driver folder, the package name
|
||||||
|
// will need to be changed accordingly.
|
||||||
|
|
||||||
|
package walletdb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/conformal/btcwallet/walletdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// subTestFailError is used to signal that a sub test returned false.
|
||||||
|
var subTestFailError = fmt.Errorf("sub test failure")
|
||||||
|
|
||||||
|
// testContext is used to store context information about a running test which
|
||||||
|
// is passed into helper functions.
|
||||||
|
type testContext struct {
|
||||||
|
t *testing.T
|
||||||
|
db walletdb.DB
|
||||||
|
bucketDepth int
|
||||||
|
isWritable bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollbackValues returns a copy of the provided map with all values set to an
|
||||||
|
// empty string. This is used to test that values are properly rolled back.
|
||||||
|
func rollbackValues(values map[string]string) map[string]string {
|
||||||
|
retMap := make(map[string]string, len(values))
|
||||||
|
for k := range values {
|
||||||
|
retMap[k] = ""
|
||||||
|
}
|
||||||
|
return retMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// testGetValues checks that all of the provided key/value pairs can be
|
||||||
|
// retrieved from the database and the retrieved values match the provided
|
||||||
|
// values.
|
||||||
|
func testGetValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool {
|
||||||
|
for k, v := range values {
|
||||||
|
var vBytes []byte
|
||||||
|
if v != "" {
|
||||||
|
vBytes = []byte(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotValue := bucket.Get([]byte(k))
|
||||||
|
if !reflect.DeepEqual(gotValue, vBytes) {
|
||||||
|
tc.t.Errorf("Get: unexpected value - got %s, want %s",
|
||||||
|
gotValue, vBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testPutValues stores all of the provided key/value pairs in the provided
|
||||||
|
// bucket while checking for errors.
|
||||||
|
func testPutValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool {
|
||||||
|
for k, v := range values {
|
||||||
|
var vBytes []byte
|
||||||
|
if v != "" {
|
||||||
|
vBytes = []byte(v)
|
||||||
|
}
|
||||||
|
if err := bucket.Put([]byte(k), vBytes); err != nil {
|
||||||
|
tc.t.Errorf("Put: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testDeleteValues removes all of the provided key/value pairs from the
|
||||||
|
// provided bucket.
|
||||||
|
func testDeleteValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool {
|
||||||
|
for k := range values {
|
||||||
|
if err := bucket.Delete([]byte(k)); err != nil {
|
||||||
|
tc.t.Errorf("Delete: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testNestedBucket reruns the testBucketInterface against a nested bucket along
|
||||||
|
// with a counter to only test a couple of level deep.
|
||||||
|
func testNestedBucket(tc *testContext, testBucket walletdb.Bucket) bool {
|
||||||
|
// Don't go more than 2 nested level deep.
|
||||||
|
if tc.bucketDepth > 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.bucketDepth++
|
||||||
|
defer func() {
|
||||||
|
tc.bucketDepth--
|
||||||
|
}()
|
||||||
|
if !testBucketInterface(tc, testBucket) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBucketInterface ensures the bucket interface is working properly by
|
||||||
|
// exercising all of its functions.
|
||||||
|
func testBucketInterface(tc *testContext, bucket walletdb.Bucket) bool {
|
||||||
|
if bucket.Writable() != tc.isWritable {
|
||||||
|
tc.t.Errorf("Bucket writable state does not match.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.isWritable {
|
||||||
|
// keyValues holds the keys and values to use when putting
|
||||||
|
// values into the bucket.
|
||||||
|
var keyValues = map[string]string{
|
||||||
|
"bucketkey1": "foo1",
|
||||||
|
"bucketkey2": "foo2",
|
||||||
|
"bucketkey3": "foo3",
|
||||||
|
}
|
||||||
|
if !testPutValues(tc, bucket, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testGetValues(tc, bucket, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate all of the keys using ForEach while making sure the
|
||||||
|
// stored values are the expected values.
|
||||||
|
keysFound := make(map[string]struct{}, len(keyValues))
|
||||||
|
err := bucket.ForEach(func(k, v []byte) error {
|
||||||
|
kString := string(k)
|
||||||
|
wantV, ok := keyValues[kString]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("ForEach: key '%s' should "+
|
||||||
|
"exist", kString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(v, []byte(wantV)) {
|
||||||
|
return fmt.Errorf("ForEach: value for key '%s' "+
|
||||||
|
"does not match - got %s, want %s",
|
||||||
|
kString, v, wantV)
|
||||||
|
}
|
||||||
|
|
||||||
|
keysFound[kString] = struct{}{}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all keys were iterated.
|
||||||
|
for k := range keyValues {
|
||||||
|
if _, ok := keysFound[k]; !ok {
|
||||||
|
tc.t.Errorf("ForEach: key '%s' was not iterated "+
|
||||||
|
"when it should have been", k)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the keys and ensure they were deleted.
|
||||||
|
if !testDeleteValues(tc, bucket, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !testGetValues(tc, bucket, rollbackValues(keyValues)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure creating a new bucket works as expected.
|
||||||
|
testBucketName := []byte("testbucket")
|
||||||
|
testBucket, err := bucket.CreateBucket(testBucketName)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("CreateBucket: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !testNestedBucket(tc, testBucket) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure creating a bucket that already exists fails with the
|
||||||
|
// expected error.
|
||||||
|
wantErr := walletdb.ErrBucketExists
|
||||||
|
if _, err := bucket.CreateBucket(testBucketName); err != wantErr {
|
||||||
|
tc.t.Errorf("CreateBucket: unexpected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure CreateBucketIfNotExists returns an existing bucket.
|
||||||
|
testBucket, err = bucket.CreateBucketIfNotExists(testBucketName)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("CreateBucketIfNotExists: unexpected "+
|
||||||
|
"error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !testNestedBucket(tc, testBucket) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure retrieving and existing bucket works as expected.
|
||||||
|
testBucket = bucket.Bucket(testBucketName)
|
||||||
|
if !testNestedBucket(tc, testBucket) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure deleting a bucket works as intended.
|
||||||
|
if err := bucket.DeleteBucket(testBucketName); err != nil {
|
||||||
|
tc.t.Errorf("DeleteBucket: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if b := bucket.Bucket(testBucketName); b != nil {
|
||||||
|
tc.t.Errorf("DeleteBucket: bucket '%s' still exists",
|
||||||
|
testBucketName)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure deleting a bucket that doesn't exist returns the
|
||||||
|
// expected error.
|
||||||
|
wantErr = walletdb.ErrBucketNotFound
|
||||||
|
if err := bucket.DeleteBucket(testBucketName); err != wantErr {
|
||||||
|
tc.t.Errorf("DeleteBucket: unexpected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure CreateBucketIfNotExists creates a new bucket when
|
||||||
|
// it doesn't already exist.
|
||||||
|
testBucket, err = bucket.CreateBucketIfNotExists(testBucketName)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("CreateBucketIfNotExists: unexpected "+
|
||||||
|
"error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !testNestedBucket(tc, testBucket) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the test bucket to avoid leaving it around for future
|
||||||
|
// calls.
|
||||||
|
if err := bucket.DeleteBucket(testBucketName); err != nil {
|
||||||
|
tc.t.Errorf("DeleteBucket: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if b := bucket.Bucket(testBucketName); b != nil {
|
||||||
|
tc.t.Errorf("DeleteBucket: bucket '%s' still exists",
|
||||||
|
testBucketName)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Put should fail with bucket that is not writable.
|
||||||
|
wantErr := walletdb.ErrTxNotWritable
|
||||||
|
failBytes := []byte("fail")
|
||||||
|
if err := bucket.Put(failBytes, failBytes); err != wantErr {
|
||||||
|
tc.t.Errorf("Put did not fail with unwritable bucket")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete should fail with bucket that is not writable.
|
||||||
|
if err := bucket.Delete(failBytes); err != wantErr {
|
||||||
|
tc.t.Errorf("Put did not fail with unwritable bucket")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucket should fail with bucket that is not writable.
|
||||||
|
if _, err := bucket.CreateBucket(failBytes); err != wantErr {
|
||||||
|
tc.t.Errorf("CreateBucket did not fail with unwritable " +
|
||||||
|
"bucket")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucketIfNotExists should fail with bucket that is not
|
||||||
|
// writable.
|
||||||
|
if _, err := bucket.CreateBucketIfNotExists(failBytes); err != wantErr {
|
||||||
|
tc.t.Errorf("CreateBucketIfNotExists did not fail with " +
|
||||||
|
"unwritable bucket")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBucket should fail with bucket that is not writable.
|
||||||
|
if err := bucket.DeleteBucket(failBytes); err != wantErr {
|
||||||
|
tc.t.Errorf("DeleteBucket did not fail with unwritable " +
|
||||||
|
"bucket")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testManualTxInterface ensures that manual transactions work as expected.
|
||||||
|
func testManualTxInterface(tc *testContext, namespace walletdb.Namespace) bool {
|
||||||
|
// populateValues tests that populating values works as expected.
|
||||||
|
//
|
||||||
|
// When the writable flag is false, a read-only tranasction is created,
|
||||||
|
// standard bucket tests for read-only transactions are performed, and
|
||||||
|
// the Commit function is checked to ensure it fails as expected.
|
||||||
|
//
|
||||||
|
// Otherwise, a read-write transaction is created, the values are
|
||||||
|
// written, standard bucket tests for read-write transactions are
|
||||||
|
// performed, and then the transaction is either commited or rolled
|
||||||
|
// back depending on the flag.
|
||||||
|
populateValues := func(writable, rollback bool, putValues map[string]string) bool {
|
||||||
|
tx, err := namespace.Begin(writable)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Begin: unexpected error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
tc.t.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.isWritable = writable
|
||||||
|
if !testBucketInterface(tc, rootBucket) {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !writable {
|
||||||
|
// The transaction is not writable, so it should fail
|
||||||
|
// the commit.
|
||||||
|
if err := tx.Commit(); err != walletdb.ErrTxNotWritable {
|
||||||
|
tc.t.Errorf("Commit: unexpected error %v, "+
|
||||||
|
"want %v", err, walletdb.ErrTxNotWritable)
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback the transaction.
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
tc.t.Errorf("Commit: unexpected error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !testPutValues(tc, rootBucket, putValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if rollback {
|
||||||
|
// Rollback the transaction.
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
tc.t.Errorf("Rollback: unexpected "+
|
||||||
|
"error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The commit should succeed.
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
tc.t.Errorf("Commit: unexpected error "+
|
||||||
|
"%v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkValues starts a read-only transaction and checks that all of
|
||||||
|
// the key/value pairs specified in the expectedValues parameter match
|
||||||
|
// what's in the database.
|
||||||
|
checkValues := func(expectedValues map[string]string) bool {
|
||||||
|
// Begin another read-only transaction to ensure...
|
||||||
|
tx, err := namespace.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Begin: unexpected error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
tc.t.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testGetValues(tc, rootBucket, expectedValues) {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback the read-only transaction.
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
tc.t.Errorf("Commit: unexpected error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteValues starts a read-write transaction and deletes the keys
|
||||||
|
// in the passed key/value pairs.
|
||||||
|
deleteValues := func(values map[string]string) bool {
|
||||||
|
tx, err := namespace.Begin(true)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
tc.t.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the keys and ensure they were deleted.
|
||||||
|
if !testDeleteValues(tc, rootBucket, values) {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !testGetValues(tc, rootBucket, rollbackValues(values)) {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the changes and ensure it was successful.
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
tc.t.Errorf("Commit: unexpected error %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyValues holds the keys and values to use when putting values
|
||||||
|
// into a bucket.
|
||||||
|
var keyValues = map[string]string{
|
||||||
|
"umtxkey1": "foo1",
|
||||||
|
"umtxkey2": "foo2",
|
||||||
|
"umtxkey3": "foo3",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting populating the values using a read-only
|
||||||
|
// transaction fails as expected.
|
||||||
|
if !populateValues(false, true, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !checkValues(rollbackValues(keyValues)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting populating the values using a read-write
|
||||||
|
// transaction and then rolling it back yields the expected values.
|
||||||
|
if !populateValues(true, true, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !checkValues(rollbackValues(keyValues)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting populating the values using a read-write
|
||||||
|
// transaction and then committing it stores the expected values.
|
||||||
|
if !populateValues(true, false, keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !checkValues(keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the keys.
|
||||||
|
if !deleteValues(keyValues) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testNamespaceAndTxInterfaces creates a namespace using the provided key and
|
||||||
|
// tests all facets of it interface as well as transaction and bucket
|
||||||
|
// interfaces under it.
|
||||||
|
func testNamespaceAndTxInterfaces(tc *testContext, namespaceKey string) bool {
|
||||||
|
namespaceKeyBytes := []byte(namespaceKey)
|
||||||
|
namespace, err := tc.db.Namespace(namespaceKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Namespace: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// Remove the namespace now that the tests are done for it.
|
||||||
|
if err := tc.db.DeleteNamespace(namespaceKeyBytes); err != nil {
|
||||||
|
tc.t.Errorf("DeleteNamespace: unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if !testManualTxInterface(tc, namespace) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyValues holds the keys and values to use when putting values
|
||||||
|
// into a bucket.
|
||||||
|
var keyValues = map[string]string{
|
||||||
|
"mtxkey1": "foo1",
|
||||||
|
"mtxkey2": "foo2",
|
||||||
|
"mtxkey3": "foo3",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the bucket interface via a managed read-only transaction.
|
||||||
|
err = namespace.View(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.isWritable = false
|
||||||
|
if !testBucketInterface(tc, rootBucket) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure errors returned from the user-supplied View function are
|
||||||
|
// returned.
|
||||||
|
viewError := fmt.Errorf("example view error")
|
||||||
|
err = namespace.View(func(tx walletdb.Tx) error {
|
||||||
|
return viewError
|
||||||
|
})
|
||||||
|
if err != viewError {
|
||||||
|
tc.t.Errorf("View: inner function error not returned - got "+
|
||||||
|
"%v, want %v", err, viewError)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the bucket interface via a managed read-write transaction.
|
||||||
|
// Also, put a series of values and force a rollback so the following
|
||||||
|
// code can ensure the values were not stored.
|
||||||
|
forceRollbackError := fmt.Errorf("force rollback")
|
||||||
|
err = namespace.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.isWritable = true
|
||||||
|
if !testBucketInterface(tc, rootBucket) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testPutValues(tc, rootBucket, keyValues) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an error to force a rollback.
|
||||||
|
return forceRollbackError
|
||||||
|
})
|
||||||
|
if err != forceRollbackError {
|
||||||
|
if err == subTestFailError {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.t.Errorf("Update: inner function error not returned - got "+
|
||||||
|
"%v, want %v", err, forceRollbackError)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the values that should have not been stored due to the forced
|
||||||
|
// rollback above were not actually stored.
|
||||||
|
err = namespace.View(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testGetValues(tc, rootBucket, rollbackValues(keyValues)) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store a series of values via a managed read-write transaction.
|
||||||
|
err = namespace.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testPutValues(tc, rootBucket, keyValues) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the values stored above were committed as expected.
|
||||||
|
err = namespace.View(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testGetValues(tc, rootBucket, keyValues) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the values stored above in a managed read-write transaction.
|
||||||
|
err = namespace.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !testDeleteValues(tc, rootBucket, keyValues) {
|
||||||
|
return subTestFailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testAdditionalErrors performs some tests for error cases not covered
|
||||||
|
// elsewhere in the tests and therefore improves negative test coverage.
|
||||||
|
func testAdditionalErrors(tc *testContext) bool {
|
||||||
|
// Create a new namespace and then intentionally delete the namespace
|
||||||
|
// bucket out from under it to force errors.
|
||||||
|
ns3Key := []byte("ns3")
|
||||||
|
ns3, err := tc.db.Namespace(ns3Key)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Namespace: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := tc.db.DeleteNamespace(ns3Key); err != nil {
|
||||||
|
tc.t.Errorf("DeleteNamespace: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Begin fails when the namespace bucket does not exist.
|
||||||
|
wantErr := walletdb.ErrBucketNotFound
|
||||||
|
if _, err := ns3.Begin(false); err != wantErr {
|
||||||
|
tc.t.Errorf("Begin: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure View fails when the namespace bucket does not exist.
|
||||||
|
err = ns3.View(func(tx walletdb.Tx) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != wantErr {
|
||||||
|
tc.t.Errorf("View: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Update fails when the namespace bucket does not exist.
|
||||||
|
err = ns3.Update(func(tx walletdb.Tx) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != wantErr {
|
||||||
|
tc.t.Errorf("View: did not receive expected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate the namespace to bring the bucket back.
|
||||||
|
ns3, err = tc.db.Namespace(ns3Key)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Namespace: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// Remove the namespace now that the tests are done for it.
|
||||||
|
if err := tc.db.DeleteNamespace(ns3Key); err != nil {
|
||||||
|
tc.t.Errorf("DeleteNamespace: unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = ns3.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
if rootBucket == nil {
|
||||||
|
return fmt.Errorf("RootBucket: unexpected nil root bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure CreateBucket returns the expected error when no bucket
|
||||||
|
// key is specified.
|
||||||
|
wantErr := walletdb.ErrBucketNameRequired
|
||||||
|
if _, err := rootBucket.CreateBucket(nil); err != wantErr {
|
||||||
|
return fmt.Errorf("CreateBucket: unexpected error - "+
|
||||||
|
"got %v, want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure DeleteBucket returns the expected error when no bucket
|
||||||
|
// key is specified.
|
||||||
|
wantErr = walletdb.ErrIncompatibleValue
|
||||||
|
if err := rootBucket.DeleteBucket(nil); err != wantErr {
|
||||||
|
return fmt.Errorf("DeleteBucket: unexpected error - "+
|
||||||
|
"got %v, want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Put returns the expected error when no key is
|
||||||
|
// specified.
|
||||||
|
wantErr = walletdb.ErrKeyRequired
|
||||||
|
if err := rootBucket.Put(nil, nil); err != wantErr {
|
||||||
|
return fmt.Errorf("Put: unexpected error - got %v, "+
|
||||||
|
"want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err != subTestFailError {
|
||||||
|
tc.t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that attempting to rollback or commit a transaction that is
|
||||||
|
// already closed returns the expected error.
|
||||||
|
tx, err := ns3.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
tc.t.Errorf("Begin: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
tc.t.Errorf("Rollback: unexpected error: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
wantErr = walletdb.ErrTxClosed
|
||||||
|
if err := tx.Rollback(); err != wantErr {
|
||||||
|
tc.t.Errorf("Rollback: unexpected error - got %v, want %v", err,
|
||||||
|
wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != wantErr {
|
||||||
|
tc.t.Errorf("Commit: unexpected error - got %v, want %v", err,
|
||||||
|
wantErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// testInterface tests performs tests for the various interfaces of walletdb
|
||||||
|
// which require state in the database for the given database type.
|
||||||
|
func testInterface(t *testing.T, db walletdb.DB) {
|
||||||
|
// Create a test context to pass around.
|
||||||
|
context := testContext{t: t, db: db}
|
||||||
|
|
||||||
|
// Create a namespace and test the interface for it.
|
||||||
|
if !testNamespaceAndTxInterfaces(&context, "ns1") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a second namespace and test the interface for it.
|
||||||
|
if !testNamespaceAndTxInterfaces(&context, "ns2") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check a few more error conditions not covered elsewhere.
|
||||||
|
if !testAdditionalErrors(&context) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
39
walletdb/test_coverage.txt
Normal file
39
walletdb/test_coverage.txt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
PASS
|
||||||
|
coverage: 100.0% of statements
|
||||||
|
ok github.com/conformal/btcwallet/walletdb 0.130s
|
||||||
|
github.com\conformal\btcwallet\walletdb\interface.go:190: RegisterDriver 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\interface.go:201: SupportedDrivers 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\interface.go:214: Create 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\interface.go:228: Open 100.0%
|
||||||
|
total: (statements) 100.0%
|
||||||
|
============================================================
|
||||||
|
PASS
|
||||||
|
coverage: 91.7% of statements
|
||||||
|
ok github.com/conformal/btcwallet/walletdb/bdb 0.149s
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:28: convertErr 76.9%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:74: Bucket 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:90: CreateBucket 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:103: CreateBucketIfNotExists 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:116: DeleteBucket 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:129: ForEach 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:136: Writable 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:145: Put 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:157: Get 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:166: Delete 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:185: RootBucket 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:193: Commit 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:201: Rollback 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:227: Begin 85.7%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:249: View 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:270: Update 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:294: Namespace 93.3%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:329: DeleteNamespace 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:339: WriteTo 0.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:348: Close 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:353: fileExists 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\db.go:364: openDB 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\driver.go:34: parseArgs 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\driver.go:50: openDBDriver 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\driver.go:60: createDBDriver 100.0%
|
||||||
|
github.com\conformal\btcwallet\walletdb\bdb\driver.go:69: init 66.7%
|
||||||
|
total: (statements) 91.7%
|
Loading…
Add table
Add a link
Reference in a new issue