- 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
107 lines
2.9 KiB
Go
107 lines
2.9 KiB
Go
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)
|
||
}
|
||
}
|