mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
[NOD-849] Database tests (#695)
* [NOD-849] Cover ffldb/transaction with tests. * [NOD-849] Cover cursor.go with tests. * [NOD-849] Cover ldb/transaction with tests. * [NOD-849] Cover location.go with tests. * [NOD-849] Write TestFlatFileMultiFileRollback. * [NOD-849] Fix merge errors. * [NOD-849] Fix a comment. * [NOD-849] Fix a comment. * [NOD-849] Add a test that makes sure that files get deleted on rollback. * [NOD-849] Add a test that makes sure that serializeLocation serialized to an expected value. * [NOD-849] Improve TestFlatFileLocationDeserializationErrors. * [NOD-849] Fix a copy+paste error. * [NOD-849] Explain maxFileSize = 16. * [NOD-849] Remove redundant RollbackUnlessClosed call. * [NOD-849] Extract bucket to a variable in TestCursorSanity. * [NOD-849] Rename TestKeyValueTransactionCommit to TestTransactionCommitForLevelDBMethods. * [NOD-849] Extract prepareXXX into separate functions. * [NOD-849] Simplify function calls in TestTransactionCloseErrors. * [NOD-849] Extract validateCurrentCursorKeyAndValue to a separate function. * [NOD-849] Add a comment over TestCursorSanity. * [NOD-849] Add a comment over function in TestCursorCloseErrors. * [NOD-849] Add a comment over function in TestTransactionCloseErrors. * [NOD-849] Separate TestTransactionCloseErrors to TestTransactionCommitErrors and TestTransactionRollbackErrors. * [NOD-849] Separate TestTransactionCloseErrors to TestTransactionCommitErrors and TestTransactionRollbackErrors. * [NOD-849] Fix copy+paste error in comments. * [NOD-849] Fix merge errors. * [NOD-849] Merge TestTransactionCommitErrors and TestTransactionRollbackErrors into TestTransactionCloseErrors. * [NOD-849] Move prepareDatabaseForTest into ffldb_test.go. * [NOD-849] Add cursorKey to Value error messages in validateCurrentCursorKeyAndValue.
This commit is contained in:
parent
2ef5c2cbac
commit
2e2492cc5d
@ -16,12 +16,14 @@ const (
|
||||
// cache. Note that this does not include the current/write file, so there
|
||||
// will typically be one more than this value open.
|
||||
maxOpenFiles = 25
|
||||
)
|
||||
|
||||
var (
|
||||
// maxFileSize is the maximum size for each file used to store data.
|
||||
//
|
||||
// NOTE: The current code uses uint32 for all offsets, so this value
|
||||
// must be less than 2^32 (4 GiB). This is also why it's a typed
|
||||
// constant.
|
||||
// must be less than 2^32 (4 GiB).
|
||||
// NOTE: This is a var rather than a const for testing purposes.
|
||||
maxFileSize uint32 = 512 * 1024 * 1024 // 512 MiB
|
||||
)
|
||||
|
||||
|
@ -1,24 +1,40 @@
|
||||
package ff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlatFileStoreSanity(t *testing.T) {
|
||||
// Open a test store
|
||||
path, err := ioutil.TempDir("", "TestFlatFileStoreSanity")
|
||||
func prepareStoreForTest(t *testing.T, testName string) (store *flatFileStore, teardownFunc func()) {
|
||||
// Create a temp db to run tests against
|
||||
path, err := ioutil.TempDir("", testName)
|
||||
if err != nil {
|
||||
t.Fatalf("TestFlatFileStoreSanity: TempDir unexpectedly "+
|
||||
"failed: %s", err)
|
||||
t.Fatalf("%s: TempDir unexpectedly "+
|
||||
"failed: %s", testName, err)
|
||||
}
|
||||
name := "test"
|
||||
store, err := openFlatFileStore(path, name)
|
||||
store, err = openFlatFileStore(path, name)
|
||||
if err != nil {
|
||||
t.Fatalf("TestFlatFileStoreSanity: openFlatFileStore "+
|
||||
"unexpectedly failed: %s", err)
|
||||
t.Fatalf("%s: openFlatFileStore "+
|
||||
"unexpectedly failed: %s", testName, err)
|
||||
}
|
||||
teardownFunc = func() {
|
||||
err = store.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Close unexpectedly "+
|
||||
"failed: %s", testName, err)
|
||||
}
|
||||
}
|
||||
return store, teardownFunc
|
||||
}
|
||||
|
||||
func TestFlatFileStoreSanity(t *testing.T) {
|
||||
store, teardownFunc := prepareStoreForTest(t, "TestFlatFileStoreSanity")
|
||||
defer teardownFunc()
|
||||
|
||||
// Write something to the store
|
||||
writeData := []byte("Hello world!")
|
||||
@ -72,3 +88,88 @@ func TestFlatFilePath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlatFileMultiFileRollback(t *testing.T) {
|
||||
store, teardownFunc := prepareStoreForTest(t, "TestFlatFileMultiFileRollback")
|
||||
defer teardownFunc()
|
||||
|
||||
// Set the maxFileSize to 16 bytes so that we don't have to write
|
||||
// an enormous amount of data to disk to get multiple files, all
|
||||
// for the sake of this test.
|
||||
currentMaxFileSize := maxFileSize
|
||||
maxFileSize = 16
|
||||
defer func() {
|
||||
maxFileSize = currentMaxFileSize
|
||||
}()
|
||||
|
||||
// Write five 8 byte chunks and keep the last location written to
|
||||
var lastWriteLocation1 *flatFileLocation
|
||||
for i := byte(0); i < 5; i++ {
|
||||
writeData := []byte{i, i, i, i, i, i, i, i}
|
||||
var err error
|
||||
lastWriteLocation1, err = store.write(writeData)
|
||||
if err != nil {
|
||||
t.Fatalf("TestFlatFileMultiFileRollback: write returned "+
|
||||
"unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the current location and the current file number
|
||||
currentLocation := store.currentLocation()
|
||||
fileNumberBeforeWriting := store.writeCursor.currentFileNumber
|
||||
|
||||
// Write (2 * maxOpenFiles) more 8 byte chunks and keep the last location written to
|
||||
var lastWriteLocation2 *flatFileLocation
|
||||
for i := byte(0); i < byte(2*maxFileSize); i++ {
|
||||
writeData := []byte{0, 1, 2, 3, 4, 5, 6, 7}
|
||||
var err error
|
||||
lastWriteLocation2, err = store.write(writeData)
|
||||
if err != nil {
|
||||
t.Fatalf("TestFlatFileMultiFileRollback: write returned "+
|
||||
"unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the file number again to later make sure its file no longer exists
|
||||
fileNumberAfterWriting := store.writeCursor.currentFileNumber
|
||||
|
||||
// Rollback
|
||||
err := store.rollback(currentLocation)
|
||||
if err != nil {
|
||||
t.Fatalf("TestFlatFileMultiFileRollback: rollback returned "+
|
||||
"unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Make sure that lastWriteLocation1 still exists
|
||||
expectedData := []byte{4, 4, 4, 4, 4, 4, 4, 4}
|
||||
data, err := store.read(lastWriteLocation1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestFlatFileMultiFileRollback: read returned "+
|
||||
"unexpected error: %s", err)
|
||||
}
|
||||
if !bytes.Equal(data, expectedData) {
|
||||
t.Fatalf("TestFlatFileMultiFileRollback: read returned "+
|
||||
"unexpected data. Want: %s, got: %s", string(expectedData),
|
||||
string(data))
|
||||
}
|
||||
|
||||
// Make sure that lastWriteLocation2 does NOT exist
|
||||
_, err = store.read(lastWriteLocation2)
|
||||
if err == nil {
|
||||
t.Fatalf("TestFlatFileMultiFileRollback: read " +
|
||||
"unexpectedly succeeded")
|
||||
}
|
||||
if !database.IsNotFoundError(err) {
|
||||
t.Fatalf("TestFlatFileMultiFileRollback: read "+
|
||||
"returned unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Make sure that all the appropriate files have been deleted
|
||||
for i := fileNumberAfterWriting; i > fileNumberBeforeWriting; i-- {
|
||||
filePath := flatFilePath(store.basePath, store.storeName, i)
|
||||
if _, err := os.Stat(filePath); err == nil || !os.IsNotExist(err) {
|
||||
t.Fatalf("TestFlatFileMultiFileRollback: file "+
|
||||
"unexpectedly still exists: %s", filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
62
database/ffldb/ff/location_test.go
Normal file
62
database/ffldb/ff/location_test.go
Normal file
@ -0,0 +1,62 @@
|
||||
package ff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlatFileLocationSerialization(t *testing.T) {
|
||||
location := &flatFileLocation{
|
||||
fileNumber: 1,
|
||||
fileOffset: 2,
|
||||
dataLength: 3,
|
||||
}
|
||||
|
||||
serializedLocation := serializeLocation(location)
|
||||
expectedSerializedLocation := []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0}
|
||||
if !bytes.Equal(serializedLocation, expectedSerializedLocation) {
|
||||
t.Fatalf("TestFlatFileLocationSerialization: serializeLocation "+
|
||||
"returned unexpected bytes. Want: %s, got: %s",
|
||||
hex.EncodeToString(expectedSerializedLocation), hex.EncodeToString(serializedLocation))
|
||||
}
|
||||
|
||||
deserializedLocation, err := deserializeLocation(serializedLocation)
|
||||
if err != nil {
|
||||
t.Fatalf("TestFlatFileLocationSerialization: deserializeLocation "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(deserializedLocation, location) {
|
||||
t.Fatalf("TestFlatFileLocationSerialization: original "+
|
||||
"location and deserialized location aren't the same. Want: %v, "+
|
||||
"got: %v", location, deserializedLocation)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlatFileLocationDeserializationErrors(t *testing.T) {
|
||||
expectedError := "unexpected serializedLocation length"
|
||||
|
||||
tooShortSerializedLocation := []byte{0, 1, 2, 3, 4, 5}
|
||||
_, err := deserializeLocation(tooShortSerializedLocation)
|
||||
if err == nil {
|
||||
t.Fatalf("TestFlatFileLocationSerialization: deserializeLocation " +
|
||||
"unexpectedly succeeded")
|
||||
}
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("TestFlatFileLocationSerialization: deserializeLocation "+
|
||||
"returned unexpected error. Want: %s, got: %s", expectedError, err)
|
||||
}
|
||||
|
||||
tooLongSerializedLocation := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}
|
||||
_, err = deserializeLocation(tooLongSerializedLocation)
|
||||
if err == nil {
|
||||
t.Fatalf("TestFlatFileLocationSerialization: deserializeLocation " +
|
||||
"unexpectedly succeeded")
|
||||
}
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("TestFlatFileLocationSerialization: deserializeLocation "+
|
||||
"returned unexpected error. Want: %s, got: %s", expectedError, err)
|
||||
}
|
||||
}
|
@ -170,8 +170,9 @@ func (db *ffldb) Begin() (database.Transaction, error) {
|
||||
}
|
||||
|
||||
transaction := &transaction{
|
||||
ldbTx: ldbTx,
|
||||
ffdb: db.flatFileDB,
|
||||
ldbTx: ldbTx,
|
||||
ffdb: db.flatFileDB,
|
||||
isClosed: false,
|
||||
}
|
||||
return transaction, nil
|
||||
}
|
||||
|
@ -7,6 +7,28 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func prepareDatabaseForTest(t *testing.T, testName string) (db database.Database, teardownFunc func()) {
|
||||
// Create a temp db to run tests against
|
||||
path, err := ioutil.TempDir("", testName)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: TempDir unexpectedly "+
|
||||
"failed: %s", testName, err)
|
||||
}
|
||||
db, err = Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Open unexpectedly "+
|
||||
"failed: %s", testName, err)
|
||||
}
|
||||
teardownFunc = func() {
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Close unexpectedly "+
|
||||
"failed: %s", testName, err)
|
||||
}
|
||||
}
|
||||
return db, teardownFunc
|
||||
}
|
||||
|
||||
func TestRepairFlatFiles(t *testing.T) {
|
||||
// Create a temp db to run tests against
|
||||
path, err := ioutil.TempDir("", "TestRepairFlatFiles")
|
||||
|
246
database/ffldb/ldb/cursor_test.go
Normal file
246
database/ffldb/ldb/cursor_test.go
Normal file
@ -0,0 +1,246 @@
|
||||
package ldb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func validateCurrentCursorKeyAndValue(t *testing.T, testName string, cursor *LevelDBCursor,
|
||||
expectedKey *database.Key, expectedValue []byte) {
|
||||
|
||||
cursorKey, err := cursor.Key()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Key "+
|
||||
"unexpectedly failed: %s", testName, err)
|
||||
}
|
||||
if !reflect.DeepEqual(cursorKey, expectedKey) {
|
||||
t.Fatalf("%s: Key "+
|
||||
"returned wrong key. Want: %s, got: %s",
|
||||
testName, string(expectedKey.Bytes()), string(cursorKey.Bytes()))
|
||||
}
|
||||
cursorValue, err := cursor.Value()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Value "+
|
||||
"unexpectedly failed for key %s: %s",
|
||||
testName, cursorKey, err)
|
||||
}
|
||||
if !bytes.Equal(cursorValue, expectedValue) {
|
||||
t.Fatalf("%s: Value "+
|
||||
"returned wrong value for key %s. Want: %s, got: %s",
|
||||
testName, cursorKey, string(expectedValue), string(cursorValue))
|
||||
}
|
||||
}
|
||||
|
||||
func recoverFromClosedCursorPanic(t *testing.T, testName string) {
|
||||
panicErr := recover()
|
||||
if panicErr == nil {
|
||||
t.Fatalf("%s: cursor unexpectedly "+
|
||||
"didn't panic after being closed", testName)
|
||||
}
|
||||
expectedPanicErr := "closed cursor"
|
||||
if !strings.Contains(fmt.Sprintf("%v", panicErr), expectedPanicErr) {
|
||||
t.Fatalf("%s: cursor panicked "+
|
||||
"with wrong message. Want: %v, got: %s",
|
||||
testName, expectedPanicErr, panicErr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCursorSanity validates typical cursor usage, including
|
||||
// opening a cursor over some existing data, seeking back
|
||||
// and forth over that data, and getting some keys/values out
|
||||
// of the cursor.
|
||||
func TestCursorSanity(t *testing.T) {
|
||||
ldb, teardownFunc := prepareDatabaseForTest(t, "TestCursorSanity")
|
||||
defer teardownFunc()
|
||||
|
||||
// Write some data to the database
|
||||
bucket := database.MakeBucket([]byte("bucket"))
|
||||
for i := 0; i < 10; i++ {
|
||||
key := fmt.Sprintf("key%d", i)
|
||||
value := fmt.Sprintf("value%d", i)
|
||||
err := ldb.Put(bucket.Key([]byte(key)), []byte(value))
|
||||
if err != nil {
|
||||
t.Fatalf("TestCursorSanity: Put "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Open a new cursor
|
||||
cursor := ldb.Cursor(bucket)
|
||||
defer func() {
|
||||
err := cursor.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("TestCursorSanity: Close "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Seek to first key and make sure its key and value are correct
|
||||
hasNext := cursor.First()
|
||||
if !hasNext {
|
||||
t.Fatalf("TestCursorSanity: First " +
|
||||
"unexpectedly returned non-existance")
|
||||
}
|
||||
expectedKey := bucket.Key([]byte("key0"))
|
||||
expectedValue := []byte("value0")
|
||||
validateCurrentCursorKeyAndValue(t, "TestCursorSanity", cursor, expectedKey, expectedValue)
|
||||
|
||||
// Seek to a non-existant key
|
||||
err := cursor.Seek(database.MakeBucket().Key([]byte("doesn't exist")))
|
||||
if err == nil {
|
||||
t.Fatalf("TestCursorSanity: Seek " +
|
||||
"unexpectedly succeeded")
|
||||
}
|
||||
if !database.IsNotFoundError(err) {
|
||||
t.Fatalf("TestCursorSanity: Seek "+
|
||||
"returned wrong error: %s", err)
|
||||
}
|
||||
|
||||
// Seek to the last key
|
||||
err = cursor.Seek(bucket.Key([]byte("key9")))
|
||||
if err != nil {
|
||||
t.Fatalf("TestCursorSanity: Seek "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
expectedKey = bucket.Key([]byte("key9"))
|
||||
expectedValue = []byte("value9")
|
||||
validateCurrentCursorKeyAndValue(t, "TestCursorSanity", cursor, expectedKey, expectedValue)
|
||||
|
||||
// Call Next to get to the end of the cursor. This should
|
||||
// return false to signify that there are no items after that.
|
||||
// Key and Value calls should return ErrNotFound.
|
||||
hasNext = cursor.Next()
|
||||
if hasNext {
|
||||
t.Fatalf("TestCursorSanity: Next " +
|
||||
"after last value is unexpectedly not done")
|
||||
}
|
||||
_, err = cursor.Key()
|
||||
if err == nil {
|
||||
t.Fatalf("TestCursorSanity: Key " +
|
||||
"unexpectedly succeeded")
|
||||
}
|
||||
if !database.IsNotFoundError(err) {
|
||||
t.Fatalf("TestCursorSanity: Key "+
|
||||
"returned wrong error: %s", err)
|
||||
}
|
||||
_, err = cursor.Value()
|
||||
if err == nil {
|
||||
t.Fatalf("TestCursorSanity: Value " +
|
||||
"unexpectedly succeeded")
|
||||
}
|
||||
if !database.IsNotFoundError(err) {
|
||||
t.Fatalf("TestCursorSanity: Value "+
|
||||
"returned wrong error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursorCloseErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
// function is the LevelDBCursor function that we're
|
||||
// verifying returns an error after the cursor had
|
||||
// been closed.
|
||||
function func(dbTx *LevelDBCursor) error
|
||||
}{
|
||||
{
|
||||
name: "Seek",
|
||||
function: func(cursor *LevelDBCursor) error {
|
||||
return cursor.Seek(database.MakeBucket().Key([]byte{}))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Key",
|
||||
function: func(cursor *LevelDBCursor) error {
|
||||
_, err := cursor.Key()
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
function: func(cursor *LevelDBCursor) error {
|
||||
_, err := cursor.Value()
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Close",
|
||||
function: func(cursor *LevelDBCursor) error {
|
||||
return cursor.Close()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
func() {
|
||||
ldb, teardownFunc := prepareDatabaseForTest(t, "TestCursorCloseErrors")
|
||||
defer teardownFunc()
|
||||
|
||||
// Open a new cursor
|
||||
cursor := ldb.Cursor(database.MakeBucket())
|
||||
|
||||
// Close the cursor
|
||||
err := cursor.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("TestCursorCloseErrors: Close "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
expectedErrContainsString := "closed cursor"
|
||||
|
||||
// Make sure that the test function returns a "closed transaction" error
|
||||
err = test.function(cursor)
|
||||
if err == nil {
|
||||
t.Fatalf("TestCursorCloseErrors: %s "+
|
||||
"unexpectedly succeeded", test.name)
|
||||
}
|
||||
if !strings.Contains(err.Error(), expectedErrContainsString) {
|
||||
t.Fatalf("TestCursorCloseErrors: %s "+
|
||||
"returned wrong error. Want: %s, got: %s",
|
||||
test.name, expectedErrContainsString, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursorCloseFirstAndNext(t *testing.T) {
|
||||
ldb, teardownFunc := prepareDatabaseForTest(t, "TestCursorCloseFirstAndNext")
|
||||
defer teardownFunc()
|
||||
|
||||
// Write some data to the database
|
||||
for i := 0; i < 10; i++ {
|
||||
key := fmt.Sprintf("key%d", i)
|
||||
value := fmt.Sprintf("value%d", i)
|
||||
err := ldb.Put(database.MakeBucket([]byte("bucket")).Key([]byte(key)), []byte(value))
|
||||
if err != nil {
|
||||
t.Fatalf("TestCursorCloseFirstAndNext: Put "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Open a new cursor
|
||||
cursor := ldb.Cursor(database.MakeBucket([]byte("bucket")))
|
||||
|
||||
// Close the cursor
|
||||
err := cursor.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("TestCursorCloseFirstAndNext: Close "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// We expect First to panic
|
||||
func() {
|
||||
defer recoverFromClosedCursorPanic(t, "TestCursorCloseFirstAndNext")
|
||||
cursor.First()
|
||||
}()
|
||||
|
||||
// We expect Next to panic
|
||||
func() {
|
||||
defer recoverFromClosedCursorPanic(t, "TestCursorCloseFirstAndNext")
|
||||
cursor.Next()
|
||||
}()
|
||||
}
|
@ -7,30 +7,36 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLevelDBSanity(t *testing.T) {
|
||||
// Open a test db
|
||||
path, err := ioutil.TempDir("", "TestLevelDBSanity")
|
||||
func prepareDatabaseForTest(t *testing.T, testName string) (ldb *LevelDB, teardownFunc func()) {
|
||||
// Create a temp db to run tests against
|
||||
path, err := ioutil.TempDir("", testName)
|
||||
if err != nil {
|
||||
t.Fatalf("TestLevelDBSanity: TempDir unexpectedly "+
|
||||
"failed: %s", err)
|
||||
t.Fatalf("%s: TempDir unexpectedly "+
|
||||
"failed: %s", testName, err)
|
||||
}
|
||||
ldb, err := NewLevelDB(path)
|
||||
ldb, err = NewLevelDB(path)
|
||||
if err != nil {
|
||||
t.Fatalf("TestLevelDBSanity: NewLevelDB "+
|
||||
"unexpectedly failed: %s", err)
|
||||
t.Fatalf("%s: NewLevelDB unexpectedly "+
|
||||
"failed: %s", testName, err)
|
||||
}
|
||||
defer func() {
|
||||
err := ldb.Close()
|
||||
teardownFunc = func() {
|
||||
err = ldb.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("TestLevelDBSanity: Close "+
|
||||
"unexpectedly failed: %s", err)
|
||||
t.Fatalf("%s: Close unexpectedly "+
|
||||
"failed: %s", testName, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return ldb, teardownFunc
|
||||
}
|
||||
|
||||
func TestLevelDBSanity(t *testing.T) {
|
||||
ldb, teardownFunc := prepareDatabaseForTest(t, "TestLevelDBSanity")
|
||||
defer teardownFunc()
|
||||
|
||||
// Put something into the db
|
||||
key := database.MakeBucket().Key([]byte("key"))
|
||||
putData := []byte("Hello world!")
|
||||
err = ldb.Put(key, putData)
|
||||
err := ldb.Put(key, putData)
|
||||
if err != nil {
|
||||
t.Fatalf("TestLevelDBSanity: Put returned "+
|
||||
"unexpected error: %s", err)
|
||||
@ -52,24 +58,8 @@ func TestLevelDBSanity(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLevelDBTransactionSanity(t *testing.T) {
|
||||
// Open a test db
|
||||
path, err := ioutil.TempDir("", "TestLevelDBTransactionSanity")
|
||||
if err != nil {
|
||||
t.Fatalf("TestLevelDBTransactionSanity: TempDir unexpectedly "+
|
||||
"failed: %s", err)
|
||||
}
|
||||
ldb, err := NewLevelDB(path)
|
||||
if err != nil {
|
||||
t.Fatalf("TestLevelDBTransactionSanity: NewLevelDB "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err := ldb.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("TestLevelDBTransactionSanity: Close "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}()
|
||||
ldb, teardownFunc := prepareDatabaseForTest(t, "TestLevelDBTransactionSanity")
|
||||
defer teardownFunc()
|
||||
|
||||
// Case 1. Write in tx and then read directly from the DB
|
||||
// Begin a new transaction
|
||||
|
146
database/ffldb/ldb/transaction_test.go
Normal file
146
database/ffldb/ldb/transaction_test.go
Normal file
@ -0,0 +1,146 @@
|
||||
package ldb
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTransactionCloseErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
// function is the LevelDBTransaction function that
|
||||
// we're verifying whether it returns an error after
|
||||
// the transaction had been closed.
|
||||
function func(dbTx *LevelDBTransaction) error
|
||||
shouldReturnError bool
|
||||
}{
|
||||
{
|
||||
name: "Put",
|
||||
function: func(dbTx *LevelDBTransaction) error {
|
||||
return dbTx.Put(database.MakeBucket().Key([]byte("key")), []byte("value"))
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Get",
|
||||
function: func(dbTx *LevelDBTransaction) error {
|
||||
_, err := dbTx.Get(database.MakeBucket().Key([]byte("key")))
|
||||
return err
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Has",
|
||||
function: func(dbTx *LevelDBTransaction) error {
|
||||
_, err := dbTx.Has(database.MakeBucket().Key([]byte("key")))
|
||||
return err
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Delete",
|
||||
function: func(dbTx *LevelDBTransaction) error {
|
||||
return dbTx.Delete(database.MakeBucket().Key([]byte("key")))
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Cursor",
|
||||
function: func(dbTx *LevelDBTransaction) error {
|
||||
_, err := dbTx.Cursor(database.MakeBucket([]byte("bucket")))
|
||||
return err
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Rollback",
|
||||
function: (*LevelDBTransaction).Rollback,
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Commit",
|
||||
function: (*LevelDBTransaction).Commit,
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "RollbackUnlessClosed",
|
||||
function: (*LevelDBTransaction).RollbackUnlessClosed,
|
||||
shouldReturnError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
func() {
|
||||
ldb, teardownFunc := prepareDatabaseForTest(t, "TestTransactionCloseErrors")
|
||||
defer teardownFunc()
|
||||
|
||||
// Begin a new transaction to test Commit
|
||||
commitTx, err := ldb.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: Begin "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err := commitTx.RollbackUnlessClosed()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: RollbackUnlessClosed "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Commit the Commit test transaction
|
||||
err = commitTx.Commit()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: Commit "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Begin a new transaction to test Rollback
|
||||
rollbackTx, err := ldb.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: Begin "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err := rollbackTx.RollbackUnlessClosed()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: RollbackUnlessClosed "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Rollback the Rollback test transaction
|
||||
err = rollbackTx.Rollback()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: Rollback "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
expectedErrContainsString := "closed transaction"
|
||||
|
||||
// Make sure that the test function returns a "closed transaction" error
|
||||
// for both the commitTx and the rollbackTx
|
||||
for _, closedTx := range []*LevelDBTransaction{commitTx, rollbackTx} {
|
||||
err = test.function(closedTx)
|
||||
if test.shouldReturnError {
|
||||
if err == nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: %s "+
|
||||
"unexpectedly succeeded", test.name)
|
||||
}
|
||||
if !strings.Contains(err.Error(), expectedErrContainsString) {
|
||||
t.Fatalf("TestTransactionCloseErrors: %s "+
|
||||
"returned wrong error. Want: %s, got: %s",
|
||||
test.name, expectedErrContainsString, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: %s "+
|
||||
"unexpectedly failed: %s", test.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/database/ffldb/ff"
|
||||
"github.com/kaspanet/kaspad/database/ffldb/ldb"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// transaction is an ffldb transaction.
|
||||
@ -13,14 +14,19 @@ import (
|
||||
// NO guarantee that if one puts data into the transaction then
|
||||
// it will be available to get within the same transaction.
|
||||
type transaction struct {
|
||||
ldbTx *ldb.LevelDBTransaction
|
||||
ffdb *ff.FlatFileDB
|
||||
ldbTx *ldb.LevelDBTransaction
|
||||
ffdb *ff.FlatFileDB
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
// Put sets the value for the given key. It overwrites
|
||||
// any previous value for that key.
|
||||
// This method is part of the DataAccessor interface.
|
||||
func (tx *transaction) Put(key *database.Key, value []byte) error {
|
||||
if tx.isClosed {
|
||||
return errors.New("cannot put into a closed transaction")
|
||||
}
|
||||
|
||||
return tx.ldbTx.Put(key, value)
|
||||
}
|
||||
|
||||
@ -28,6 +34,10 @@ func (tx *transaction) Put(key *database.Key, value []byte) error {
|
||||
// ErrNotFound if the given key does not exist.
|
||||
// This method is part of the DataAccessor interface.
|
||||
func (tx *transaction) Get(key *database.Key) ([]byte, error) {
|
||||
if tx.isClosed {
|
||||
return nil, errors.New("cannot get from a closed transaction")
|
||||
}
|
||||
|
||||
return tx.ldbTx.Get(key)
|
||||
}
|
||||
|
||||
@ -35,6 +45,10 @@ func (tx *transaction) Get(key *database.Key) ([]byte, error) {
|
||||
// given key.
|
||||
// This method is part of the DataAccessor interface.
|
||||
func (tx *transaction) Has(key *database.Key) (bool, error) {
|
||||
if tx.isClosed {
|
||||
return false, errors.New("cannot has from a closed transaction")
|
||||
}
|
||||
|
||||
return tx.ldbTx.Has(key)
|
||||
}
|
||||
|
||||
@ -42,6 +56,10 @@ func (tx *transaction) Has(key *database.Key) (bool, error) {
|
||||
// return an error if the key doesn't exist.
|
||||
// This method is part of the DataAccessor interface.
|
||||
func (tx *transaction) Delete(key *database.Key) error {
|
||||
if tx.isClosed {
|
||||
return errors.New("cannot delete from a closed transaction")
|
||||
}
|
||||
|
||||
return tx.ldbTx.Delete(key)
|
||||
}
|
||||
|
||||
@ -52,6 +70,10 @@ func (tx *transaction) Delete(key *database.Key) error {
|
||||
// that has just now been inserted.
|
||||
// This method is part of the DataAccessor interface.
|
||||
func (tx *transaction) AppendToStore(storeName string, data []byte) ([]byte, error) {
|
||||
if tx.isClosed {
|
||||
return nil, errors.New("cannot append to store on a closed transaction")
|
||||
}
|
||||
|
||||
return appendToStore(tx, tx.ffdb, storeName, data)
|
||||
}
|
||||
|
||||
@ -61,12 +83,20 @@ func (tx *transaction) AppendToStore(storeName string, data []byte) ([]byte, err
|
||||
// AppendToStore for further details.
|
||||
// This method is part of the DataAccessor interface.
|
||||
func (tx *transaction) RetrieveFromStore(storeName string, location []byte) ([]byte, error) {
|
||||
if tx.isClosed {
|
||||
return nil, errors.New("cannot retrieve from store on a closed transaction")
|
||||
}
|
||||
|
||||
return tx.ffdb.Read(storeName, location)
|
||||
}
|
||||
|
||||
// Cursor begins a new cursor over the given bucket.
|
||||
// This method is part of the DataAccessor interface.
|
||||
func (tx *transaction) Cursor(bucket *database.Bucket) (database.Cursor, error) {
|
||||
if tx.isClosed {
|
||||
return nil, errors.New("cannot open a cursor from a closed transaction")
|
||||
}
|
||||
|
||||
return tx.ldbTx.Cursor(bucket)
|
||||
}
|
||||
|
||||
@ -74,6 +104,11 @@ func (tx *transaction) Cursor(bucket *database.Bucket) (database.Cursor, error)
|
||||
// database within this transaction.
|
||||
// This method is part of the Transaction interface.
|
||||
func (tx *transaction) Rollback() error {
|
||||
if tx.isClosed {
|
||||
return errors.New("cannot rollback a closed transaction")
|
||||
}
|
||||
tx.isClosed = true
|
||||
|
||||
return tx.ldbTx.Rollback()
|
||||
}
|
||||
|
||||
@ -81,6 +116,11 @@ func (tx *transaction) Rollback() error {
|
||||
// within this transaction.
|
||||
// This method is part of the Transaction interface.
|
||||
func (tx *transaction) Commit() error {
|
||||
if tx.isClosed {
|
||||
return errors.New("cannot commit a closed transaction")
|
||||
}
|
||||
tx.isClosed = true
|
||||
|
||||
return tx.ldbTx.Commit()
|
||||
}
|
||||
|
||||
@ -88,5 +128,10 @@ func (tx *transaction) Commit() error {
|
||||
// the database within the transaction, unless the transaction
|
||||
// had already been closed using either Rollback or Commit.
|
||||
func (tx *transaction) RollbackUnlessClosed() error {
|
||||
if tx.isClosed {
|
||||
return nil
|
||||
}
|
||||
tx.isClosed = true
|
||||
|
||||
return tx.ldbTx.RollbackUnlessClosed()
|
||||
}
|
||||
|
500
database/ffldb/transaction_test.go
Normal file
500
database/ffldb/transaction_test.go
Normal file
@ -0,0 +1,500 @@
|
||||
package ffldb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTransactionCommitForLevelDBMethods(t *testing.T) {
|
||||
db, teardownFunc := prepareDatabaseForTest(t, "TestTransactionCommitForLevelDBMethods")
|
||||
defer teardownFunc()
|
||||
|
||||
// Put a value into the database
|
||||
key1 := database.MakeBucket().Key([]byte("key1"))
|
||||
value1 := []byte("value1")
|
||||
err := db.Put(key1, value1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Put "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Begin a new transaction
|
||||
dbTx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Begin "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err := dbTx.RollbackUnlessClosed()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: RollbackUnlessClosed "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Make sure that Has returns that the original value exists
|
||||
exists, err := dbTx.Has(key1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Has "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !exists {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Has " +
|
||||
"unexpectedly returned that the value does not exist")
|
||||
}
|
||||
|
||||
// Get the existing value and make sure it's equal to the original
|
||||
existingValue, err := dbTx.Get(key1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Get "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !bytes.Equal(existingValue, value1) {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Get "+
|
||||
"returned unexpected value. Want: %s, got: %s",
|
||||
string(value1), string(existingValue))
|
||||
}
|
||||
|
||||
// Delete the existing value
|
||||
err = dbTx.Delete(key1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Delete "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Try to get a value that does not exist and make sure it returns ErrNotFound
|
||||
_, err = dbTx.Get(database.MakeBucket().Key([]byte("doesn't exist")))
|
||||
if err == nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Get " +
|
||||
"unexpectedly succeeded")
|
||||
}
|
||||
if !database.IsNotFoundError(err) {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Get "+
|
||||
"returned unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Put a new value
|
||||
key2 := database.MakeBucket().Key([]byte("key2"))
|
||||
value2 := []byte("value2")
|
||||
err = dbTx.Put(key2, value2)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Put "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
err = dbTx.Commit()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Commit "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Make sure that Has returns that the original value does NOT exist
|
||||
exists, err = db.Has(key1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Has "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if exists {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Has " +
|
||||
"unexpectedly returned that the value exists")
|
||||
}
|
||||
|
||||
// Try to Get the existing value and make sure an ErrNotFound is returned
|
||||
_, err = db.Get(key1)
|
||||
if err == nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Get " +
|
||||
"unexpectedly succeeded")
|
||||
}
|
||||
if !database.IsNotFoundError(err) {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Get "+
|
||||
"returned unexpected err: %s", err)
|
||||
}
|
||||
|
||||
// Make sure that Has returns that the new value exists
|
||||
exists, err = db.Has(key2)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Has "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !exists {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Has " +
|
||||
"unexpectedly returned that the value does not exist")
|
||||
}
|
||||
|
||||
// Get the new value and make sure it's equal to the original
|
||||
existingValue, err = db.Get(key2)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Get "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !bytes.Equal(existingValue, value2) {
|
||||
t.Fatalf("TestTransactionCommitForLevelDBMethods: Get "+
|
||||
"returned unexpected value. Want: %s, got: %s",
|
||||
string(value2), string(existingValue))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionRollbackForLevelDBMethods(t *testing.T) {
|
||||
db, teardownFunc := prepareDatabaseForTest(t, "TestTransactionRollbackForLevelDBMethods")
|
||||
defer teardownFunc()
|
||||
|
||||
// Put a value into the database
|
||||
key1 := database.MakeBucket().Key([]byte("key1"))
|
||||
value1 := []byte("value1")
|
||||
err := db.Put(key1, value1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Put "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Begin a new transaction
|
||||
dbTx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Begin "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err := dbTx.RollbackUnlessClosed()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: RollbackUnlessClosed "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Make sure that Has returns that the original value exists
|
||||
exists, err := dbTx.Has(key1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Has "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !exists {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Has " +
|
||||
"unexpectedly returned that the value does not exist")
|
||||
}
|
||||
|
||||
// Get the existing value and make sure it's equal to the original
|
||||
existingValue, err := dbTx.Get(key1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Get "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !bytes.Equal(existingValue, value1) {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Get "+
|
||||
"returned unexpected value. Want: %s, got: %s",
|
||||
string(value1), string(existingValue))
|
||||
}
|
||||
|
||||
// Delete the existing value
|
||||
err = dbTx.Delete(key1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Delete "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Put a new value
|
||||
key2 := database.MakeBucket().Key([]byte("key2"))
|
||||
value2 := []byte("value2")
|
||||
err = dbTx.Put(key2, value2)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Put "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Rollback the transaction
|
||||
err = dbTx.Rollback()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Rollback "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Make sure that Has returns that the original value still exists
|
||||
exists, err = db.Has(key1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Has "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !exists {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Has " +
|
||||
"unexpectedly returned that the value does not exist")
|
||||
}
|
||||
|
||||
// Get the existing value and make sure it is still returned
|
||||
existingValue, err = db.Get(key1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Get "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !bytes.Equal(existingValue, value1) {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Get "+
|
||||
"returned unexpected value. Want: %s, got: %s",
|
||||
string(value1), string(existingValue))
|
||||
}
|
||||
|
||||
// Make sure that Has returns that the new value does NOT exist
|
||||
exists, err = db.Has(key2)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Has "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if exists {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Has " +
|
||||
"unexpectedly returned that the value exists")
|
||||
}
|
||||
|
||||
// Try to Get the new value and make sure it returns an ErrNotFound
|
||||
_, err = db.Get(key2)
|
||||
if err == nil {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Get " +
|
||||
"unexpectedly succeeded")
|
||||
}
|
||||
if !database.IsNotFoundError(err) {
|
||||
t.Fatalf("TestTransactionRollbackForLevelDBMethods: Get "+
|
||||
"returned unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionCloseErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
function func(dbTx database.Transaction) error
|
||||
shouldReturnError bool
|
||||
}{
|
||||
{
|
||||
name: "Put",
|
||||
function: func(dbTx database.Transaction) error {
|
||||
return dbTx.Put(database.MakeBucket().Key([]byte("key")), []byte("value"))
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Get",
|
||||
function: func(dbTx database.Transaction) error {
|
||||
_, err := dbTx.Get(database.MakeBucket().Key([]byte("key")))
|
||||
return err
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Has",
|
||||
function: func(dbTx database.Transaction) error {
|
||||
_, err := dbTx.Has(database.MakeBucket().Key([]byte("key")))
|
||||
return err
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Delete",
|
||||
function: func(dbTx database.Transaction) error {
|
||||
return dbTx.Delete(database.MakeBucket().Key([]byte("key")))
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Cursor",
|
||||
function: func(dbTx database.Transaction) error {
|
||||
_, err := dbTx.Cursor(database.MakeBucket([]byte("bucket")))
|
||||
return err
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "AppendToStore",
|
||||
function: func(dbTx database.Transaction) error {
|
||||
_, err := dbTx.AppendToStore("store", []byte("data"))
|
||||
return err
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "RetrieveFromStore",
|
||||
function: func(dbTx database.Transaction) error {
|
||||
_, err := dbTx.RetrieveFromStore("store", []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
return err
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Rollback",
|
||||
function: func(dbTx database.Transaction) error {
|
||||
return dbTx.Rollback()
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "Commit",
|
||||
function: func(dbTx database.Transaction) error {
|
||||
return dbTx.Commit()
|
||||
},
|
||||
shouldReturnError: true,
|
||||
},
|
||||
{
|
||||
name: "RollbackUnlessClosed",
|
||||
function: func(dbTx database.Transaction) error {
|
||||
return dbTx.RollbackUnlessClosed()
|
||||
},
|
||||
shouldReturnError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
func() {
|
||||
db, teardownFunc := prepareDatabaseForTest(t, "TestTransactionCloseErrors")
|
||||
defer teardownFunc()
|
||||
|
||||
// Begin a new transaction to test Commit
|
||||
commitTx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: Begin "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err := commitTx.RollbackUnlessClosed()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: RollbackUnlessClosed "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Commit the Commit test transaction
|
||||
err = commitTx.Commit()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: Commit "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Begin a new transaction to test Rollback
|
||||
rollbackTx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: Begin "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err := rollbackTx.RollbackUnlessClosed()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: RollbackUnlessClosed "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Rollback the Rollback test transaction
|
||||
err = rollbackTx.Rollback()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: Rollback "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
expectedErrContainsString := "closed transaction"
|
||||
|
||||
// Make sure that the test function returns a "closed transaction" error
|
||||
// for both the commitTx and the rollbackTx
|
||||
for _, closedTx := range []database.Transaction{commitTx, rollbackTx} {
|
||||
err = test.function(closedTx)
|
||||
if test.shouldReturnError {
|
||||
if err == nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: %s "+
|
||||
"unexpectedly succeeded", test.name)
|
||||
}
|
||||
if !strings.Contains(err.Error(), expectedErrContainsString) {
|
||||
t.Fatalf("TestTransactionCloseErrors: %s "+
|
||||
"returned wrong error. Want: %s, got: %s",
|
||||
test.name, expectedErrContainsString, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCloseErrors: %s "+
|
||||
"unexpectedly failed: %s", test.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionRollbackUnlessClosed(t *testing.T) {
|
||||
db, teardownFunc := prepareDatabaseForTest(t, "TestTransactionRollbackUnlessClosed")
|
||||
defer teardownFunc()
|
||||
|
||||
// Begin a new transaction
|
||||
dbTx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackUnlessClosed: Begin "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Roll it back
|
||||
err = dbTx.RollbackUnlessClosed()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionRollbackUnlessClosed: RollbackUnlessClosed "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionCommitForFlatFileMethods(t *testing.T) {
|
||||
db, teardownFunc := prepareDatabaseForTest(t, "TestTransactionCommitForFlatFileMethods")
|
||||
defer teardownFunc()
|
||||
|
||||
// Put a value into the database
|
||||
store := "store"
|
||||
value1 := []byte("value1")
|
||||
location1, err := db.AppendToStore(store, value1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForFlatFileMethods: AppendToStore "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Begin a new transaction
|
||||
dbTx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForFlatFileMethods: Begin "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err := dbTx.RollbackUnlessClosed()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForFlatFileMethods: RollbackUnlessClosed "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Retrieve the existing value and make sure it's equal to the original
|
||||
existingValue, err := dbTx.RetrieveFromStore(store, location1)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForFlatFileMethods: RetrieveFromStore "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !bytes.Equal(existingValue, value1) {
|
||||
t.Fatalf("TestTransactionCommitForFlatFileMethods: RetrieveFromStore "+
|
||||
"returned unexpected value. Want: %s, got: %s",
|
||||
string(value1), string(existingValue))
|
||||
}
|
||||
|
||||
// Put a new value
|
||||
value2 := []byte("value2")
|
||||
location2, err := dbTx.AppendToStore(store, value2)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForFlatFileMethods: AppendToStore "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
err = dbTx.Commit()
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForFlatFileMethods: Commit "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
// Retrieve the new value and make sure it's equal to the original
|
||||
newValue, err := db.RetrieveFromStore(store, location2)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTransactionCommitForFlatFileMethods: RetrieveFromStore "+
|
||||
"unexpectedly failed: %s", err)
|
||||
}
|
||||
if !bytes.Equal(newValue, value2) {
|
||||
t.Fatalf("TestTransactionCommitForFlatFileMethods: RetrieveFromStore "+
|
||||
"returned unexpected value. Want: %s, got: %s",
|
||||
string(value2), string(newValue))
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user