Ensure proper Lamport clock integration across batch operations and sequence number handling: - Add Lamport clock support to Batch.Write for consistent replication timestamps - Fix potential sequence number inconsistencies in WAL operations - Update WAL tests to properly verify sync mode behaviors - Fix Sequence numbering to appropriately handle Lamport clock timestamps
192 lines
4.5 KiB
Go
192 lines
4.5 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()
|
|
|
|
// Create a mock Lamport clock for the test
|
|
clock := &MockLamportClock{counter: 0}
|
|
|
|
wal, err := NewWALWithReplication(cfg, dir, clock, nil)
|
|
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)
|
|
}
|
|
}
|