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
This commit is contained in:
dailz
2026-06-12 13:23:27 +08:00
parent 59de99ca0b
commit cf913b1d52
52 changed files with 4538 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
package wal
import (
"bytes"
"testing"
)
func TestRecordRoundtrip(t *testing.T) {
payload := []byte("hello world")
encoded := EncodePhysicalRecord(RecFull, payload)
rec, consumed, err := DecodePhysicalRecord(encoded)
if err != nil {
t.Fatalf("DecodePhysicalRecord failed: %v", err)
}
if rec.Type != RecFull {
t.Errorf("expected type RecFull(%d), got %d", RecFull, rec.Type)
}
if string(rec.Payload) != "hello world" {
t.Errorf("expected payload 'hello world', got %q", string(rec.Payload))
}
if consumed != 7+len(payload) {
t.Errorf("expected consumed %d, got %d", 7+len(payload), consumed)
}
}
func TestSplitSmallPayload(t *testing.T) {
payload := make([]byte, 100)
for i := range payload {
payload[i] = byte(i)
}
records := SplitIntoRecords(payload)
if len(records) != 1 {
t.Fatalf("expected 1 record, got %d", len(records))
}
rec, _, err := DecodePhysicalRecord(records[0])
if err != nil {
t.Fatalf("DecodePhysicalRecord failed: %v", err)
}
if rec.Type != RecFull {
t.Errorf("expected RecFull, got %d", rec.Type)
}
if !bytes.Equal(rec.Payload, payload) {
t.Error("payload mismatch")
}
}
func TestSplitIntoRecords(t *testing.T) {
// 40 KB payload → needs to split across blocks.
payload := make([]byte, 40*1024)
for i := range payload {
payload[i] = byte(i % 256)
}
records := SplitIntoRecords(payload)
if len(records) < 2 {
t.Fatalf("expected at least 2 records, got %d", len(records))
}
// Verify fragment sequence.
types := make([]uint8, len(records))
var concatenated []byte
for i, enc := range records {
rec, _, err := DecodePhysicalRecord(enc)
if err != nil {
t.Fatalf("DecodePhysicalRecord record %d failed: %v", i, err)
}
types[i] = rec.Type
concatenated = append(concatenated, rec.Payload...)
}
// First record must be RecFirst.
if types[0] != RecFirst {
t.Errorf("first record type: expected RecFirst(%d), got %d", RecFirst, types[0])
}
// Last record must be RecLast.
if types[len(types)-1] != RecLast {
t.Errorf("last record type: expected RecLast(%d), got %d", RecLast, types[len(types)-1])
}
// Middle records must be RecMiddle.
for i := 1; i < len(types)-1; i++ {
if types[i] != RecMiddle {
t.Errorf("record %d type: expected RecMiddle(%d), got %d", i, RecMiddle, types[i])
}
}
// Concatenated payloads must equal original.
if !bytes.Equal(concatenated, payload) {
t.Error("concatenated payloads do not match original")
}
}
func TestBlockPadding(t *testing.T) {
// blockOffset = WalBlockSize - 5 → remaining = 5, which is <= 7 → padding needed = 5.
blockOffset := uint32(WalBlockSize - 5)
padding := PaddingNeeded(blockOffset)
if padding != 5 {
t.Errorf("PaddingNeeded(%d): expected 5, got %d", blockOffset, padding)
}
// Cannot fit a record.
if CanFitRecord(blockOffset, 1) {
t.Error("CanFitRecord should return false when remaining <= 7")
}
// blockOffset = WalBlockSize - 8 → remaining = 8, which is > 7 → no padding needed.
blockOffset2 := uint32(WalBlockSize - 8)
padding2 := PaddingNeeded(blockOffset2)
if padding2 != 0 {
t.Errorf("PaddingNeeded(%d): expected 0, got %d", blockOffset2, padding2)
}
// Can fit a 1-byte payload: remaining=8, header=7, payload=1 → 8 >= 8.
if !CanFitRecord(blockOffset2, 1) {
t.Error("CanFitRecord should return true when remaining=8 and payloadLen=1")
}
// Cannot fit a 2-byte payload: remaining=8, header=7, payload=2 → 8 < 9.
if CanFitRecord(blockOffset2, 2) {
t.Error("CanFitRecord should return false when remaining=8 and payloadLen=2")
}
}
func TestCRCMismatch(t *testing.T) {
encoded := EncodePhysicalRecord(RecFull, []byte("test"))
// Corrupt a payload byte.
encoded[8] ^= 0xFF
_, _, err := DecodePhysicalRecord(encoded)
if err == nil {
t.Error("expected CRC mismatch error")
}
}
func TestDataTooShort(t *testing.T) {
_, _, err := DecodePhysicalRecord([]byte{1, 2, 3})
if err == nil {
t.Error("expected error for data too short")
}
}