kaspad/database/transaction_test.go
stasatdaglabs 5fe9dae557
[NOD-863] Write interface tests for the new database (#697)
* [NOD-863] Write TestCursorNext.

* [NOD-863] Write TestCursorFirst.

* [NOD-863] Fix merge errors.

* [NOD-863] Add TestCursorSeek.

* [NOD-863] Add TestCursorCloseErrors.

* [NOD-863] Add TestCursorCloseFirstAndNext.

* [NOD-863] Add TestDataAccessorPut.

* [NOD-863] Add TestDataAccessorGet.

* [NOD-863] Add TestDataAccessorHas.

* [NOD-863] Add TestDatabaseDelete.

* [NOD-863] Add TestDatabaseAppendToStoreAndRetrieveFromStore.

* [NOD-863] Add TestTransactionAppendToStoreAndRetrieveFromStore.

* [NOD-863] Add TestTransactionDelete.

* [NOD-863] Add TestTransactionHas.

* [NOD-863] Add TestTransactionGet.

* [NOD-863] Add TestTransactionPut.

* [NOD-863] Move cursor tests to the bottom of interface_test.go.

* [NOD-863] Move interface_test.go to a database_test package.

* [NOD-863] Make each test in interface_test.go run for every database driver. Currently, only ffldb.

* [NOD-863] Make each cursor test in interface_test.go run for every database driver. Currently, only ffldb.

* [NOD-863] Split interface_test.go into separate files.

* [NOD-863] Rename interface_test.go to common_test.go.

* [NOD-863] Extract testForAllDatabaseTypes to a separate function.

* [NOD-863] Reorganize how test data gets added to the database.

* [NOD-863] Add explanations about testForAllDatabaseTypes.

* [NOD-863] Add tests that make sure that database changes don't affect previously opened transactions.

* [NOD-863] Extract databasePrepareFunc to a type alias.

* [NOD-863] Fix comments.

* [NOD-863] Add cursor exhaustion test to testCursorFirst.

* [NOD-863] Add cursor Next clause to testCursorSeek.

* [NOD-863] Add additional varification to testDatabasePut.

* [NOD-863] Add an additional verification into to testTransactionGet.

* [NOD-863] Add TestTransactionCommit.

* [NOD-863] Add TestTransactionRollback.

* [NOD-863] Add TestTransactionRollbackUnlessClosed.

* [NOD-863] Remove equals sign from databasePrepareFunc declaration.
2020-04-20 12:14:55 +03:00

550 lines
14 KiB
Go

// All tests within this file should call testForAllDatabaseTypes
// over the actual test. This is to make sure that all supported
// database types adhere to the assumptions defined in the
// interfaces in this package.
package database_test
import (
"bytes"
"github.com/kaspanet/kaspad/database"
"strings"
"testing"
)
func TestTransactionPut(t *testing.T) {
testForAllDatabaseTypes(t, "TestTransactionPut", testTransactionPut)
}
func testTransactionPut(t *testing.T, db database.Database, testName string) {
// Begin a new transaction
dbTx, err := db.Begin()
if err != nil {
t.Fatalf("%s: Begin "+
"unexpectedly failed: %s", testName, err)
}
defer func() {
err := dbTx.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
}()
// Put value1 into the transaction
key := database.MakeBucket().Key([]byte("key"))
value1 := []byte("value1")
err = dbTx.Put(key, value1)
if err != nil {
t.Fatalf("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// Put value2 into the transaction with the same key
value2 := []byte("value2")
err = dbTx.Put(key, value2)
if err != nil {
t.Fatalf("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// Commit the transaction
err = dbTx.Commit()
if err != nil {
t.Fatalf("%s: Commit "+
"unexpectedly failed: %s", testName, err)
}
// Make sure that the returned value is value2
returnedValue, err := db.Get(key)
if err != nil {
t.Fatalf("%s: Get "+
"unexpectedly failed: %s", testName, err)
}
if !bytes.Equal(returnedValue, value2) {
t.Fatalf("%s: Get "+
"returned wrong value. Want: %s, got: %s",
testName, string(value2), string(returnedValue))
}
}
func TestTransactionGet(t *testing.T) {
testForAllDatabaseTypes(t, "TestTransactionGet", testTransactionGet)
}
func testTransactionGet(t *testing.T, db database.Database, testName string) {
// 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("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// Begin a new transaction
dbTx, err := db.Begin()
if err != nil {
t.Fatalf("%s: Begin "+
"unexpectedly failed: %s", testName, err)
}
defer func() {
err := dbTx.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
}()
// Get the value back and make sure it's the same one
returnedValue, err := dbTx.Get(key1)
if err != nil {
t.Fatalf("%s: Get "+
"unexpectedly failed: %s", testName, err)
}
if !bytes.Equal(returnedValue, value1) {
t.Fatalf("%s: Get "+
"returned wrong value. Want: %s, got: %s",
testName, string(value1), string(returnedValue))
}
// Try getting a non-existent value and make sure
// the returned error is ErrNotFound
_, err = dbTx.Get(database.MakeBucket().Key([]byte("doesn't exist")))
if err == nil {
t.Fatalf("%s: Get "+
"unexpectedly succeeded", testName)
}
if !database.IsNotFoundError(err) {
t.Fatalf("%s: Get "+
"returned wrong error: %s", testName, err)
}
// Put a new value into the database outside of the transaction
key2 := database.MakeBucket().Key([]byte("key2"))
value2 := []byte("value2")
err = db.Put(key2, value2)
if err != nil {
t.Fatalf("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// Make sure that the new value doesn't exist inside the transaction
_, err = dbTx.Get(key2)
if err == nil {
t.Fatalf("%s: Get "+
"unexpectedly succeeded", testName)
}
if !database.IsNotFoundError(err) {
t.Fatalf("%s: Get "+
"returned wrong error: %s", testName, err)
}
// Put a new value into the transaction
key3 := database.MakeBucket().Key([]byte("key3"))
value3 := []byte("value3")
err = dbTx.Put(key3, value3)
if err != nil {
t.Fatalf("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// Make sure that the new value doesn't exist outside the transaction
_, err = db.Get(key3)
if err == nil {
t.Fatalf("%s: Get "+
"unexpectedly succeeded", testName)
}
if !database.IsNotFoundError(err) {
t.Fatalf("%s: Get "+
"returned wrong error: %s", testName, err)
}
}
func TestTransactionHas(t *testing.T) {
testForAllDatabaseTypes(t, "TestTransactionHas", testTransactionHas)
}
func testTransactionHas(t *testing.T, db database.Database, testName string) {
// 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("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// Begin a new transaction
dbTx, err := db.Begin()
if err != nil {
t.Fatalf("%s: Begin "+
"unexpectedly failed: %s", testName, err)
}
defer func() {
err := dbTx.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
}()
// Make sure that Has returns true for the value we just put
exists, err := dbTx.Has(key1)
if err != nil {
t.Fatalf("%s: Has "+
"unexpectedly failed: %s", testName, err)
}
if !exists {
t.Fatalf("%s: Has "+
"unexpectedly returned that the value does not exist", testName)
}
// Make sure that Has returns false for a non-existent value
exists, err = dbTx.Has(database.MakeBucket().Key([]byte("doesn't exist")))
if err != nil {
t.Fatalf("%s: Has "+
"unexpectedly failed: %s", testName, err)
}
if exists {
t.Fatalf("%s: Has "+
"unexpectedly returned that the value exists", testName)
}
// Put a new value into the database outside of the transaction
key2 := database.MakeBucket().Key([]byte("key2"))
value2 := []byte("value2")
err = db.Put(key2, value2)
if err != nil {
t.Fatalf("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// Make sure that the new value doesn't exist inside the transaction
exists, err = dbTx.Has(key2)
if err != nil {
t.Fatalf("%s: Has "+
"unexpectedly failed: %s", testName, err)
}
if exists {
t.Fatalf("%s: Has "+
"unexpectedly returned that the value exists", testName)
}
}
func TestTransactionDelete(t *testing.T) {
testForAllDatabaseTypes(t, "TestTransactionDelete", testTransactionDelete)
}
func testTransactionDelete(t *testing.T, db database.Database, testName string) {
// Put a value into the database
key := database.MakeBucket().Key([]byte("key"))
value := []byte("value")
err := db.Put(key, value)
if err != nil {
t.Fatalf("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// Begin two new transactions
dbTx1, err := db.Begin()
if err != nil {
t.Fatalf("%s: Begin "+
"unexpectedly failed: %s", testName, err)
}
dbTx2, err := db.Begin()
if err != nil {
t.Fatalf("%s: Begin "+
"unexpectedly failed: %s", testName, err)
}
defer func() {
err := dbTx1.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
err = dbTx2.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
}()
// Delete the value in the first transaction
err = dbTx1.Delete(key)
if err != nil {
t.Fatalf("%s: Delete "+
"unexpectedly failed: %s", testName, err)
}
// Commit the first transaction
err = dbTx1.Commit()
if err != nil {
t.Fatalf("%s: Commit "+
"unexpectedly failed: %s", testName, err)
}
// Make sure that Has returns false for the deleted value
exists, err := db.Has(key)
if err != nil {
t.Fatalf("%s: Has "+
"unexpectedly failed: %s", testName, err)
}
if exists {
t.Fatalf("%s: Has "+
"unexpectedly returned that the value exists", testName)
}
// Make sure that the second transaction was no affected
exists, err = dbTx2.Has(key)
if err != nil {
t.Fatalf("%s: Has "+
"unexpectedly failed: %s", testName, err)
}
if !exists {
t.Fatalf("%s: Has "+
"unexpectedly returned that the value does not exist", testName)
}
}
func TestTransactionAppendToStoreAndRetrieveFromStore(t *testing.T) {
testForAllDatabaseTypes(t, "TestTransactionAppendToStoreAndRetrieveFromStore", testTransactionAppendToStoreAndRetrieveFromStore)
}
func testTransactionAppendToStoreAndRetrieveFromStore(t *testing.T, db database.Database, testName string) {
// Begin a new transaction
dbTx, err := db.Begin()
if err != nil {
t.Fatalf("%s: Begin "+
"unexpectedly failed: %s", testName, err)
}
defer func() {
err := dbTx.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
}()
// Append some data into the store
storeName := "store"
data := []byte("data")
location, err := dbTx.AppendToStore(storeName, data)
if err != nil {
t.Fatalf("%s: AppendToStore "+
"unexpectedly failed: %s", testName, err)
}
// Retrieve the data and make sure it's equal to what was appended
retrievedData, err := dbTx.RetrieveFromStore(storeName, location)
if err != nil {
t.Fatalf("%s: RetrieveFromStore "+
"unexpectedly failed: %s", testName, err)
}
if !bytes.Equal(retrievedData, data) {
t.Fatalf("%s: RetrieveFromStore "+
"returned unexpected data. Want: %s, got: %s",
testName, string(data), string(retrievedData))
}
// Make sure that an invalid location returns ErrNotFound
fakeLocation := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
_, err = dbTx.RetrieveFromStore(storeName, fakeLocation)
if err == nil {
t.Fatalf("%s: RetrieveFromStore "+
"unexpectedly succeeded", testName)
}
if !database.IsNotFoundError(err) {
t.Fatalf("%s: RetrieveFromStore "+
"returned wrong error: %s", testName, err)
}
}
func TestTransactionCommit(t *testing.T) {
testForAllDatabaseTypes(t, "TestTransactionCommit", testTransactionCommit)
}
func testTransactionCommit(t *testing.T, db database.Database, testName string) {
// Begin a new transaction
dbTx, err := db.Begin()
if err != nil {
t.Fatalf("%s: Begin "+
"unexpectedly failed: %s", testName, err)
}
defer func() {
err := dbTx.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
}()
// Put a value into the transaction
key := database.MakeBucket().Key([]byte("key"))
value := []byte("value")
err = dbTx.Put(key, value)
if err != nil {
t.Fatalf("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// Commit the transaction
err = dbTx.Commit()
if err != nil {
t.Fatalf("%s: Commit "+
"unexpectedly failed: %s", testName, err)
}
// Make sure that the returned value exists and is as expected
returnedValue, err := db.Get(key)
if err != nil {
t.Fatalf("%s: Get "+
"unexpectedly failed: %s", testName, err)
}
if !bytes.Equal(returnedValue, value) {
t.Fatalf("%s: Get "+
"returned wrong value. Want: %s, got: %s",
testName, string(value), string(returnedValue))
}
// Make sure that further operations on the transaction return an error
_, err = dbTx.Get(key)
if err == nil {
t.Fatalf("%s: Get "+
"unexpectedly succeeded", testName)
}
expectedError := "closed transaction"
if !strings.Contains(err.Error(), expectedError) {
t.Fatalf("%s: Get "+
"returned wrong error. Want: %s, got: %s",
testName, expectedError, err)
}
}
func TestTransactionRollback(t *testing.T) {
testForAllDatabaseTypes(t, "TestTransactionRollback", testTransactionRollback)
}
func testTransactionRollback(t *testing.T, db database.Database, testName string) {
// Begin a new transaction
dbTx, err := db.Begin()
if err != nil {
t.Fatalf("%s: Begin "+
"unexpectedly failed: %s", testName, err)
}
defer func() {
err := dbTx.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
}()
// Put a value into the transaction
key := database.MakeBucket().Key([]byte("key"))
value := []byte("value")
err = dbTx.Put(key, value)
if err != nil {
t.Fatalf("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// Rollback the transaction
err = dbTx.Rollback()
if err != nil {
t.Fatalf("%s: Rollback "+
"unexpectedly failed: %s", testName, err)
}
// Make sure that the returned value did not get added to the database
_, err = db.Get(key)
if err == nil {
t.Fatalf("%s: Get "+
"unexpectedly succeeded", testName)
}
if !database.IsNotFoundError(err) {
t.Fatalf("%s: Get "+
"returned wrong error", testName)
}
// Make sure that further operations on the transaction return an error
_, err = dbTx.Get(key)
if err == nil {
t.Fatalf("%s: Get "+
"unexpectedly succeeded", testName)
}
expectedError := "closed transaction"
if !strings.Contains(err.Error(), expectedError) {
t.Fatalf("%s: Get "+
"returned wrong error. Want: %s, got: %s",
testName, expectedError, err)
}
}
func TestTransactionRollbackUnlessClosed(t *testing.T) {
testForAllDatabaseTypes(t, "TestTransactionRollbackUnlessClosed", testTransactionRollbackUnlessClosed)
}
func testTransactionRollbackUnlessClosed(t *testing.T, db database.Database, testName string) {
// Begin a new transaction
dbTx, err := db.Begin()
if err != nil {
t.Fatalf("%s: Begin "+
"unexpectedly failed: %s", testName, err)
}
defer func() {
err := dbTx.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
}()
// Put a value into the transaction
key := database.MakeBucket().Key([]byte("key"))
value := []byte("value")
err = dbTx.Put(key, value)
if err != nil {
t.Fatalf("%s: Put "+
"unexpectedly failed: %s", testName, err)
}
// RollbackUnlessClosed the transaction
err = dbTx.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
// Make sure that the returned value did not get added to the database
_, err = db.Get(key)
if err == nil {
t.Fatalf("%s: Get "+
"unexpectedly succeeded", testName)
}
if !database.IsNotFoundError(err) {
t.Fatalf("%s: Get "+
"returned wrong error", testName)
}
// Make sure that further operations on the transaction return an error
_, err = dbTx.Get(key)
if err == nil {
t.Fatalf("%s: Get "+
"unexpectedly succeeded", testName)
}
expectedError := "closed transaction"
if !strings.Contains(err.Error(), expectedError) {
t.Fatalf("%s: Get "+
"returned wrong error. Want: %s, got: %s",
testName, expectedError, err)
}
// Make sure that further calls to RollbackUnlessClosed don't return an error
err = dbTx.RollbackUnlessClosed()
if err != nil {
t.Fatalf("%s: RollbackUnlessClosed "+
"unexpectedly failed: %s", testName, err)
}
}