2023-12-29 13:25:44 +01:00

260 lines
6.8 KiB
Go

package ldb
import (
"bytes"
"fmt"
"reflect"
"strings"
"testing"
"github.com/fabbez/topiad/infrastructure/db/database"
)
func validateCurrentCursorKeyAndValue(t *testing.T, testName string, cursor database.Cursor,
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, err := ldb.Cursor(bucket)
if err != nil {
t.Fatalf("TestCursorSanity: ldb.Cursor "+
"unexpectedly failed: %s", err)
}
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(nil).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 database.Cursor) error
}{
{
name: "Seek",
function: func(cursor database.Cursor) error {
return cursor.Seek(database.MakeBucket(nil).Key([]byte{}))
},
},
{
name: "Key",
function: func(cursor database.Cursor) error {
_, err := cursor.Key()
return err
},
},
{
name: "Value",
function: func(cursor database.Cursor) error {
_, err := cursor.Value()
return err
},
},
{
name: "Close",
function: func(cursor database.Cursor) error {
return cursor.Close()
},
},
}
for _, test := range tests {
func() {
ldb, teardownFunc := prepareDatabaseForTest(t, "TestCursorCloseErrors")
defer teardownFunc()
// Open a new cursor
cursor, err := ldb.Cursor(database.MakeBucket(nil))
if err != nil {
t.Fatalf("TestCursorCloseErrors: ldb.Cursor "+
"unexpectedly failed: %s", err)
}
// 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, err := ldb.Cursor(database.MakeBucket([]byte("bucket")))
if err != nil {
t.Fatalf("TestCursorCloseFirstAndNext: ldb.Cursor "+
"unexpectedly failed: %s", err)
}
// 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()
}()
}