Files
go-kv/wal/sequence_test.go
T
dailz cf913b1d52 feat: initialize project structure and error types
- go.mod with github.com/dailz/go-kv, Go 1.26.3, testify
- config/config.go with WalConfig, Validate() with checked arithmetic
- errors.go with sentinel errors (ErrCommitUnknown, ErrWriteStopped, etc.)
- wal/constants.go with all WAL format constants and enums
- wal/header.go with WAL File Header encode/decode (CRC32 IEEE)
- wal/record.go with Physical Record codec, block boundary, SplitIntoRecords
- wal/entry.go with WAL Entry codec (varint keys/values, OpType, ValueKind)
- wal/sequence.go with SequenceManager (atomic, CAS, overflow-safe)
- manifest/manifest.go with MANIFEST stub (Load/Save atomic)
- manifest/current.go with CURRENT file (WriteCurrent/ReadCurrent)
- Comprehensive tests for all modules
- .golangci.yml configuration
2026-06-12 13:23:27 +08:00

155 lines
3.4 KiB
Go

package wal
import (
"errors"
"math"
"sync"
"sync/atomic"
"testing"
"github.com/dailz/go-kv"
)
func TestSequenceAllocation(t *testing.T) {
sm := NewSequenceManager(0)
base, err := sm.AllocateBatch(5)
if err != nil {
t.Fatalf("AllocateBatch(5): %v", err)
}
if base != 0 {
t.Fatalf("expected base=0, got %d", base)
}
base, err = sm.AllocateBatch(3)
if err != nil {
t.Fatalf("AllocateBatch(3): %v", err)
}
if base != 5 {
t.Fatalf("expected base=5, got %d", base)
}
base, err = sm.AllocateBatch(1)
if err != nil {
t.Fatalf("AllocateBatch(1): %v", err)
}
if base != 8 {
t.Fatalf("expected base=8, got %d", base)
}
if sm.NextSequence() != 9 {
t.Fatalf("expected NextSequence=9, got %d", sm.NextSequence())
}
}
func TestSequenceOverflow(t *testing.T) {
nearMax := uint64(math.MaxUint64 - 2)
sm := NewSequenceManager(nearMax)
// Remaining: MaxUint64-2, MaxUint64-1, MaxUint64 = 3 slots.
// Asking for 5 should overflow.
_, err := sm.AllocateBatch(5)
if !errors.Is(err, go_kv.ErrSequenceExhausted) {
t.Fatalf("expected ErrSequenceExhausted, got %v", err)
}
// 3 should still succeed.
base, err := sm.AllocateBatch(3)
if err != nil {
t.Fatalf("AllocateBatch(3): %v", err)
}
if base != nearMax {
t.Fatalf("expected base=%d, got %d", nearMax, base)
}
// Now any further allocation should fail.
_, err = sm.AllocateBatch(1)
if !errors.Is(err, go_kv.ErrSequenceExhausted) {
t.Fatalf("expected ErrSequenceExhausted after exhaustion, got %v", err)
}
}
func TestPublishAdvance(t *testing.T) {
sm := NewSequenceManager(0)
sm.Publish(10)
if sm.Published() != 10 {
t.Fatalf("expected Published=10, got %d", sm.Published())
}
// Publishing a lower value must not decrease the watermark.
sm.Publish(5)
if sm.Published() != 10 {
t.Fatalf("expected Published=10 (no decrease), got %d", sm.Published())
}
sm.Publish(15)
if sm.Published() != 15 {
t.Fatalf("expected Published=15, got %d", sm.Published())
}
}
func TestMarkDurable(t *testing.T) {
sm := NewSequenceManager(0)
sm.MarkDurable(8)
if sm.Durable() != 8 {
t.Fatalf("expected Durable=8, got %d", sm.Durable())
}
// Lower value must not decrease.
sm.MarkDurable(3)
if sm.Durable() != 8 {
t.Fatalf("expected Durable=8 (no decrease), got %d", sm.Durable())
}
}
func TestZeroCountRejected(t *testing.T) {
sm := NewSequenceManager(0)
_, err := sm.AllocateBatch(0)
if !errors.Is(err, go_kv.ErrSequenceExhausted) {
t.Fatalf("expected ErrSequenceExhausted for count=0, got %v", err)
}
}
func TestConcurrentAllocation(t *testing.T) {
const goroutines = 16
const batchSize uint32 = 100
sm := NewSequenceManager(0)
var totalAllocated atomic.Uint64
var wg sync.WaitGroup
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
for j := 0; j < 50; j++ {
base, err := sm.AllocateBatch(batchSize)
if err != nil {
t.Errorf("AllocateBatch failed: %v", err)
return
}
totalAllocated.Add(uint64(batchSize))
// Verify no overlap: base must be aligned to batchSize increments
// and within valid range. The key property is no gaps.
_ = base
}
}()
}
wg.Wait()
expected := uint64(goroutines) * 50 * uint64(batchSize)
if totalAllocated.Load() != expected {
t.Fatalf("expected total allocated=%d, got %d", expected, totalAllocated.Load())
}
if sm.NextSequence() != expected {
t.Fatalf("expected NextSequence=%d, got %d", expected, sm.NextSequence())
}
}