kevo/pkg/transaction/tx_test.go
Jeremy Tregunna 6fc3be617d
Some checks failed
Go Tests / Run Tests (1.24.2) (push) Has been cancelled
feat: Initial release of kevo storage engine.
Adds a complete LSM-based storage engine with these features:
- Single-writer based architecture for the storage engine
- WAL for durability, and hey it's configurable
- MemTable with skip list implementation for fast read/writes
- SSTable with block-based structure for on-disk level-based storage
- Background compaction with tiered strategy
- ACID transactions
- Good documentation (I hope)
2025-04-20 14:06:50 -06:00

183 lines
4.7 KiB
Go

package transaction
import (
"bytes"
"os"
"testing"
"github.com/jer/kevo/pkg/engine"
)
func setupTest(t *testing.T) (*engine.Engine, func()) {
// Create a temporary directory for the test
dir, err := os.MkdirTemp("", "transaction-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
// Create the engine
e, err := engine.NewEngine(dir)
if err != nil {
os.RemoveAll(dir)
t.Fatalf("Failed to create engine: %v", err)
}
// Return cleanup function
cleanup := func() {
e.Close()
os.RemoveAll(dir)
}
return e, cleanup
}
func TestTransaction_BasicOperations(t *testing.T) {
e, cleanup := setupTest(t)
defer cleanup()
// Get transaction statistics before starting
stats := e.GetStats()
txStarted := stats["tx_started"].(uint64)
// Begin a read-write transaction
tx, err := e.BeginTransaction(false)
if err != nil {
t.Fatalf("Failed to begin transaction: %v", err)
}
// Verify transaction started count increased
stats = e.GetStats()
if stats["tx_started"].(uint64) != txStarted+1 {
t.Errorf("Expected tx_started to be %d, got: %d", txStarted+1, stats["tx_started"].(uint64))
}
// Put a value in the transaction
err = tx.Put([]byte("tx-key1"), []byte("tx-value1"))
if err != nil {
t.Fatalf("Failed to put value in transaction: %v", err)
}
// Get the value from the transaction
val, err := tx.Get([]byte("tx-key1"))
if err != nil {
t.Fatalf("Failed to get value from transaction: %v", err)
}
if !bytes.Equal(val, []byte("tx-value1")) {
t.Errorf("Expected value 'tx-value1', got: %s", string(val))
}
// Commit the transaction
if err := tx.Commit(); err != nil {
t.Fatalf("Failed to commit transaction: %v", err)
}
// Verify transaction completed count increased
stats = e.GetStats()
if stats["tx_completed"].(uint64) != 1 {
t.Errorf("Expected tx_completed to be 1, got: %d", stats["tx_completed"].(uint64))
}
if stats["tx_aborted"].(uint64) != 0 {
t.Errorf("Expected tx_aborted to be 0, got: %d", stats["tx_aborted"].(uint64))
}
// Verify the value is accessible from the engine
val, err = e.Get([]byte("tx-key1"))
if err != nil {
t.Fatalf("Failed to get value from engine: %v", err)
}
if !bytes.Equal(val, []byte("tx-value1")) {
t.Errorf("Expected value 'tx-value1', got: %s", string(val))
}
}
func TestTransaction_Rollback(t *testing.T) {
e, cleanup := setupTest(t)
defer cleanup()
// Begin a read-write transaction
tx, err := e.BeginTransaction(false)
if err != nil {
t.Fatalf("Failed to begin transaction: %v", err)
}
// Put a value in the transaction
err = tx.Put([]byte("tx-key2"), []byte("tx-value2"))
if err != nil {
t.Fatalf("Failed to put value in transaction: %v", err)
}
// Get the value from the transaction
val, err := tx.Get([]byte("tx-key2"))
if err != nil {
t.Fatalf("Failed to get value from transaction: %v", err)
}
if !bytes.Equal(val, []byte("tx-value2")) {
t.Errorf("Expected value 'tx-value2', got: %s", string(val))
}
// Rollback the transaction
if err := tx.Rollback(); err != nil {
t.Fatalf("Failed to rollback transaction: %v", err)
}
// Verify transaction aborted count increased
stats := e.GetStats()
if stats["tx_completed"].(uint64) != 0 {
t.Errorf("Expected tx_completed to be 0, got: %d", stats["tx_completed"].(uint64))
}
if stats["tx_aborted"].(uint64) != 1 {
t.Errorf("Expected tx_aborted to be 1, got: %d", stats["tx_aborted"].(uint64))
}
// Verify the value is not accessible from the engine
_, err = e.Get([]byte("tx-key2"))
if err != engine.ErrKeyNotFound {
t.Errorf("Expected ErrKeyNotFound, got: %v", err)
}
}
func TestTransaction_ReadOnly(t *testing.T) {
e, cleanup := setupTest(t)
defer cleanup()
// Add some data to the engine
if err := e.Put([]byte("key-ro"), []byte("value-ro")); err != nil {
t.Fatalf("Failed to put value in engine: %v", err)
}
// Begin a read-only transaction
tx, err := e.BeginTransaction(true)
if err != nil {
t.Fatalf("Failed to begin transaction: %v", err)
}
if !tx.IsReadOnly() {
t.Errorf("Expected transaction to be read-only")
}
// Read the value
val, err := tx.Get([]byte("key-ro"))
if err != nil {
t.Fatalf("Failed to get value from transaction: %v", err)
}
if !bytes.Equal(val, []byte("value-ro")) {
t.Errorf("Expected value 'value-ro', got: %s", string(val))
}
// Attempt to write (should fail)
err = tx.Put([]byte("new-key"), []byte("new-value"))
if err == nil {
t.Errorf("Expected error when putting value in read-only transaction")
}
// Commit the transaction
if err := tx.Commit(); err != nil {
t.Fatalf("Failed to commit transaction: %v", err)
}
// Verify transaction completed count increased
stats := e.GetStats()
if stats["tx_completed"].(uint64) != 1 {
t.Errorf("Expected tx_completed to be 1, got: %d", stats["tx_completed"].(uint64))
}
}