feat(wal): implement batch codec, validation, and Arena allocator
- wal/batch.go: WalBatch encode/decode with FragmentCollector state machine - wal/validate.go: ValidateBatchLimits with checked arithmetic - memtable/arena.go: Arena allocator with 8-byte alignment and mutex - Comprehensive tests for all modules, all pass with -race
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package wal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dailz/go-kv/config"
|
||||
)
|
||||
|
||||
func makeEntry(opType uint8, valueKind uint8, keySize, valSize int) *WalEntry {
|
||||
key := make([]byte, keySize)
|
||||
for i := range key {
|
||||
key[i] = byte('a' + i%26)
|
||||
}
|
||||
val := make([]byte, valSize)
|
||||
for i := range val {
|
||||
val[i] = byte('x')
|
||||
}
|
||||
return &WalEntry{OpType: opType, ValueKind: valueKind, Key: key, Value: val}
|
||||
}
|
||||
|
||||
func TestValidateBatchLimitsPass(t *testing.T) {
|
||||
cfg := config.Defaults()
|
||||
entries := make([]*WalEntry, 100)
|
||||
for i := range entries {
|
||||
entries[i] = makeEntry(OpPut, VKInline, 10, 10)
|
||||
}
|
||||
if err := ValidateBatchLimits(entries, &cfg); err != nil {
|
||||
t.Fatalf("expected nil error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEntryCountExceeded(t *testing.T) {
|
||||
cfg := config.Defaults()
|
||||
entries := make([]*WalEntry, 10001)
|
||||
for i := range entries {
|
||||
entries[i] = makeEntry(OpPut, VKInline, 10, 10)
|
||||
}
|
||||
err := ValidateBatchLimits(entries, &cfg)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for entry count exceeded")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "entry count") {
|
||||
t.Fatalf("expected error containing 'entry count', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateKeyTooBig(t *testing.T) {
|
||||
cfg := config.Defaults()
|
||||
// 5KB key exceeds 4KB limit
|
||||
entries := []*WalEntry{makeEntry(OpPut, VKInline, 5*1024, 10)}
|
||||
err := ValidateBatchLimits(entries, &cfg)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for key too big")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "key") {
|
||||
t.Fatalf("expected error containing 'key', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateValueTooBig(t *testing.T) {
|
||||
cfg := config.Defaults()
|
||||
// 5KB inline value exceeds 4KB limit
|
||||
entries := []*WalEntry{makeEntry(OpPut, VKInline, 10, 5*1024)}
|
||||
err := ValidateBatchLimits(entries, &cfg)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for value too big")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "value") {
|
||||
t.Fatalf("expected error containing 'value', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEmptyBatch(t *testing.T) {
|
||||
cfg := config.Defaults()
|
||||
err := ValidateBatchLimits(nil, &cfg)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty batch")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "entry count") && !strings.Contains(err.Error(), "> 0") {
|
||||
t.Fatalf("expected error about empty batch, got: %v", err)
|
||||
}
|
||||
|
||||
err = ValidateBatchLimits([]*WalEntry{}, &cfg)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty batch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTotalSizeExceeded(t *testing.T) {
|
||||
cfg := config.Defaults()
|
||||
// Create entries that collectively exceed MaxBatchSize (4MB).
|
||||
// Each entry with 1000-byte key + 1000-byte value ≈ 2012 bytes encoded.
|
||||
// 2100 entries × ~2012 ≈ ~4.2MB > 4MB
|
||||
entries := make([]*WalEntry, 2100)
|
||||
for i := range entries {
|
||||
entries[i] = makeEntry(OpPut, VKInline, 1000, 1000)
|
||||
}
|
||||
err := ValidateBatchLimits(entries, &cfg)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for total size exceeded")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "batch size") {
|
||||
t.Fatalf("expected error containing 'batch size', got: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user