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:
@@ -0,0 +1,114 @@
|
||||
package wal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEntryRoundtrip(t *testing.T) {
|
||||
maxKey := bytes.Repeat([]byte("k"), int(MaxWalKeyBytes))
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
e *WalEntry
|
||||
}{
|
||||
{"put_inline", &WalEntry{OpPut, VKInline, []byte("key1"), []byte("val1")}},
|
||||
{"put_inline_empty_value", &WalEntry{OpPut, VKInline, []byte("key2"), []byte{}}},
|
||||
{"delete", &WalEntry{OpDelete, VKNone, []byte("key3"), nil}},
|
||||
{"put_max_key", &WalEntry{OpPut, VKInline, maxKey, []byte("v")}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
encoded, err := EncodeEntry(tc.e)
|
||||
if err != nil {
|
||||
t.Fatalf("encode: %v", err)
|
||||
}
|
||||
|
||||
got, consumed, err := DecodeEntry(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if consumed != len(encoded) {
|
||||
t.Fatalf("consumed %d != encoded len %d", consumed, len(encoded))
|
||||
}
|
||||
|
||||
if got.OpType != tc.e.OpType {
|
||||
t.Errorf("OpType: got %d, want %d", got.OpType, tc.e.OpType)
|
||||
}
|
||||
if got.ValueKind != tc.e.ValueKind {
|
||||
t.Errorf("ValueKind: got %d, want %d", got.ValueKind, tc.e.ValueKind)
|
||||
}
|
||||
if !bytes.Equal(got.Key, tc.e.Key) {
|
||||
t.Errorf("Key: got %q, want %q", got.Key, tc.e.Key)
|
||||
}
|
||||
if !bytes.Equal(got.Value, tc.e.Value) {
|
||||
t.Errorf("Value: got %q, want %q", got.Value, tc.e.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntryValidation(t *testing.T) {
|
||||
bigKey := bytes.Repeat([]byte("k"), int(MaxWalKeyBytes)+1)
|
||||
bigVal := bytes.Repeat([]byte("v"), int(MaxWalInlineValueBytes)+1)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
e *WalEntry
|
||||
wantErr bool
|
||||
}{
|
||||
{"op_invalid", &WalEntry{OpInvalid, VKNone, []byte("k"), nil}, true},
|
||||
{"put_vk_none", &WalEntry{OpPut, VKNone, []byte("k"), nil}, true},
|
||||
{"delete_vk_inline", &WalEntry{OpDelete, VKInline, []byte("k"), nil}, true},
|
||||
{"key_empty", &WalEntry{OpPut, VKInline, []byte{}, []byte("v")}, true},
|
||||
{"key_too_big", &WalEntry{OpPut, VKInline, bigKey, []byte("v")}, true},
|
||||
{"put_inline_val_too_big", &WalEntry{OpPut, VKInline, []byte("k"), bigVal}, true},
|
||||
{"put_vlptr_val_empty", &WalEntry{OpPut, VKValueLogPointer, []byte("k"), []byte{}}, true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := EncodeEntry(tc.e)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("EncodeEntry() error = %v, wantErr %v", err, tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntryDecodeTruncated(t *testing.T) {
|
||||
// Build a valid encoded entry, then truncate mid-varint.
|
||||
e := &WalEntry{OpPut, VKInline, []byte("key1"), []byte("value1")}
|
||||
full, err := EncodeEntry(e)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Truncate to just 1 byte — not enough for header.
|
||||
_, _, err = DecodeEntry(full[:1])
|
||||
if err == nil {
|
||||
t.Error("expected error for 1-byte data")
|
||||
}
|
||||
|
||||
// Build data with an incomplete varint: opType + valueKind + start of varint (0xFF means more bytes follow).
|
||||
trunc := []byte{OpPut, VKInline, 0xFF}
|
||||
_, _, err = DecodeEntry(trunc)
|
||||
if err == nil {
|
||||
t.Error("expected error for truncated varint")
|
||||
}
|
||||
|
||||
// Also test: varint specifies more bytes than available.
|
||||
// Encode a large key length varint but don't provide the key bytes.
|
||||
varintBuf := make([]byte, binary.MaxVarintLen64)
|
||||
n := binary.PutUvarint(varintBuf, 1000) // keyLen = 1000
|
||||
data := []byte{OpPut, VKInline}
|
||||
data = append(data, varintBuf[:n]...)
|
||||
data = append(data, varintBuf[:n]...) // valLen varint (also 1000)
|
||||
// Don't append any key/value bytes.
|
||||
_, _, err = DecodeEntry(data)
|
||||
if err == nil {
|
||||
t.Error("expected error for missing key/value bytes")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user