kevo/pkg/wal/batch_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

188 lines
4.4 KiB
Go

package wal
import (
"bytes"
"fmt"
"os"
"testing"
)
func TestBatchOperations(t *testing.T) {
batch := NewBatch()
// Test initially empty
if batch.Count() != 0 {
t.Errorf("Expected empty batch, got count %d", batch.Count())
}
// Add operations
batch.Put([]byte("key1"), []byte("value1"))
batch.Put([]byte("key2"), []byte("value2"))
batch.Delete([]byte("key3"))
// Check count
if batch.Count() != 3 {
t.Errorf("Expected batch with 3 operations, got %d", batch.Count())
}
// Check size calculation
expectedSize := BatchHeaderSize // count + seq
expectedSize += 1 + 4 + 4 + len("key1") + len("value1") // type + keylen + vallen + key + value
expectedSize += 1 + 4 + 4 + len("key2") + len("value2") // type + keylen + vallen + key + value
expectedSize += 1 + 4 + len("key3") // type + keylen + key (no value for delete)
if batch.Size() != expectedSize {
t.Errorf("Expected batch size %d, got %d", expectedSize, batch.Size())
}
// Test reset
batch.Reset()
if batch.Count() != 0 {
t.Errorf("Expected empty batch after reset, got count %d", batch.Count())
}
}
func TestBatchEncoding(t *testing.T) {
dir := createTempDir(t)
defer os.RemoveAll(dir)
cfg := createTestConfig()
wal, err := NewWAL(cfg, dir)
if err != nil {
t.Fatalf("Failed to create WAL: %v", err)
}
// Create and write a batch
batch := NewBatch()
batch.Put([]byte("key1"), []byte("value1"))
batch.Put([]byte("key2"), []byte("value2"))
batch.Delete([]byte("key3"))
if err := batch.Write(wal); err != nil {
t.Fatalf("Failed to write batch: %v", err)
}
// Check sequence
if batch.Seq == 0 {
t.Errorf("Batch sequence number not set")
}
// Close WAL
if err := wal.Close(); err != nil {
t.Fatalf("Failed to close WAL: %v", err)
}
// Replay and decode
var decodedBatch *Batch
err = ReplayWALDir(dir, func(entry *Entry) error {
if entry.Type == OpTypeBatch {
var err error
decodedBatch, err = DecodeBatch(entry)
if err != nil {
return err
}
}
return nil
})
if err != nil {
t.Fatalf("Failed to replay WAL: %v", err)
}
if decodedBatch == nil {
t.Fatal("No batch found in replay")
}
// Verify decoded batch
if decodedBatch.Count() != 3 {
t.Errorf("Expected 3 operations, got %d", decodedBatch.Count())
}
if decodedBatch.Seq != batch.Seq {
t.Errorf("Expected sequence %d, got %d", batch.Seq, decodedBatch.Seq)
}
// Verify operations
ops := decodedBatch.Operations
if ops[0].Type != OpTypePut || !bytes.Equal(ops[0].Key, []byte("key1")) || !bytes.Equal(ops[0].Value, []byte("value1")) {
t.Errorf("First operation mismatch")
}
if ops[1].Type != OpTypePut || !bytes.Equal(ops[1].Key, []byte("key2")) || !bytes.Equal(ops[1].Value, []byte("value2")) {
t.Errorf("Second operation mismatch")
}
if ops[2].Type != OpTypeDelete || !bytes.Equal(ops[2].Key, []byte("key3")) {
t.Errorf("Third operation mismatch")
}
}
func TestEmptyBatch(t *testing.T) {
dir := createTempDir(t)
defer os.RemoveAll(dir)
cfg := createTestConfig()
wal, err := NewWAL(cfg, dir)
if err != nil {
t.Fatalf("Failed to create WAL: %v", err)
}
// Create empty batch
batch := NewBatch()
// Try to write empty batch
err = batch.Write(wal)
if err != ErrEmptyBatch {
t.Errorf("Expected ErrEmptyBatch, got: %v", err)
}
// Close WAL
if err := wal.Close(); err != nil {
t.Fatalf("Failed to close WAL: %v", err)
}
}
func TestLargeBatch(t *testing.T) {
dir := createTempDir(t)
defer os.RemoveAll(dir)
cfg := createTestConfig()
wal, err := NewWAL(cfg, dir)
if err != nil {
t.Fatalf("Failed to create WAL: %v", err)
}
// Create a batch that will exceed the maximum record size
batch := NewBatch()
// Add many large key-value pairs
largeValue := make([]byte, 4096) // 4KB
for i := 0; i < 20; i++ {
key := []byte(fmt.Sprintf("key%d", i))
batch.Put(key, largeValue)
}
// Verify the batch is too large
if batch.Size() <= MaxRecordSize {
t.Fatalf("Expected batch size > %d, got %d", MaxRecordSize, batch.Size())
}
// Try to write the large batch
err = batch.Write(wal)
if err == nil {
t.Error("Expected error when writing large batch")
}
// Check that the error is ErrBatchTooLarge
if err != nil && !bytes.Contains([]byte(err.Error()), []byte("batch too large")) {
t.Errorf("Expected ErrBatchTooLarge, got: %v", err)
}
// Close WAL
if err := wal.Close(); err != nil {
t.Fatalf("Failed to close WAL: %v", err)
}
}