Files
go-kv/wal/entry_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

115 lines
3.4 KiB
Go

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")
}
}