package wal import ( "os" "path/filepath" "testing" ) func TestParseBlockSingleRecord(t *testing.T) { payload := []byte("hello world") encoded := EncodePhysicalRecord(RecFull, payload) block := make([]byte, WalBlockSize) copy(block, encoded) recs, err := ParseBlock(block) if err != nil { t.Fatalf("ParseBlock: %v", err) } if len(recs) != 1 { t.Fatalf("expected 1 record, got %d", len(recs)) } if recs[0].Type != RecFull { t.Errorf("Type = %d, want RecFull(%d)", recs[0].Type, RecFull) } if string(recs[0].Payload) != string(payload) { t.Errorf("Payload = %q, want %q", recs[0].Payload, payload) } } func TestParseBlockMultipleRecords(t *testing.T) { payloads := [][]byte{ []byte("first"), []byte("second"), []byte("third"), } block := make([]byte, WalBlockSize) offset := 0 for i, p := range payloads { rec := EncodePhysicalRecord(RecFull, p) copy(block[offset:], rec) offset += len(rec) if offset > WalBlockSize { t.Fatalf("record %d overflows block", i) } } recs, err := ParseBlock(block) if err != nil { t.Fatalf("ParseBlock: %v", err) } if len(recs) != 3 { t.Fatalf("expected 3 records, got %d", len(recs)) } for i, want := range payloads { if string(recs[i].Payload) != string(want) { t.Errorf("rec[%d].Payload = %q, want %q", i, recs[i].Payload, want) } } } func TestParseBlockWithPadding(t *testing.T) { payload := []byte("data") encoded := EncodePhysicalRecord(RecFull, payload) // Place record at offset 0, then fill rest with zeros. block := make([]byte, WalBlockSize) copy(block, encoded) recs, err := ParseBlock(block) if err != nil { t.Fatalf("ParseBlock: %v", err) } if len(recs) != 1 { t.Fatalf("expected 1 record, got %d", len(recs)) } if string(recs[0].Payload) != string(payload) { t.Errorf("Payload = %q, want %q", recs[0].Payload, payload) } } func TestParseBlockCorruptPadding(t *testing.T) { payload := []byte("data") encoded := EncodePhysicalRecord(RecFull, payload) block := make([]byte, WalBlockSize) copy(block, encoded) // Write non-zero byte in the trailing padding area. block[len(encoded)+3] = 0xFF recs, err := ParseBlock(block) if err == nil { t.Fatal("expected tail corruption error") } if !IsTailCorruption(err) { t.Errorf("expected TailCorruptionError, got: %v", err) } // Should still return records parsed before the corruption. if len(recs) != 1 { t.Errorf("expected 1 record before corruption, got %d", len(recs)) } } func TestParseBlockBadCRC(t *testing.T) { payload := []byte("data") encoded := EncodePhysicalRecord(RecFull, payload) block := make([]byte, WalBlockSize) copy(block, encoded) // Corrupt a payload byte. block[PhysicalRecordHeaderSize+1] ^= 0xFF _, err := ParseBlock(block) if err == nil { t.Fatal("expected CRC mismatch error") } if !IsTailCorruption(err) { t.Errorf("expected TailCorruptionError, got: %v", err) } } func TestParseBlockShortBlock(t *testing.T) { payload := []byte("short block data") encoded := EncodePhysicalRecord(RecFull, payload) // Simulate a short last block (less than WalBlockSize). block := make([]byte, len(encoded)+10) // extra trailing zeros copy(block, encoded) recs, err := ParseBlock(block) if err != nil { t.Fatalf("ParseBlock short block: %v", err) } if len(recs) != 1 { t.Fatalf("expected 1 record, got %d", len(recs)) } if string(recs[0].Payload) != string(payload) { t.Errorf("Payload = %q, want %q", recs[0].Payload, payload) } } func TestParseBlockEmpty(t *testing.T) { block := make([]byte, 64) // all zeros recs, err := ParseBlock(block) if err != nil { t.Fatalf("ParseBlock empty: %v", err) } if len(recs) != 0 { t.Fatalf("expected 0 records, got %d", len(recs)) } } func TestParseBlockZeroHeaderWithNonZeroTail(t *testing.T) { block := make([]byte, WalBlockSize) // First 7 bytes are zero (valid zero header), but byte at offset 8 is non-zero. block[8] = 0x42 _, err := ParseBlock(block) if err == nil { t.Fatal("expected tail corruption error for zero header + non-zero tail") } if !IsTailCorruption(err) { t.Errorf("expected TailCorruptionError, got: %v", err) } } func TestIsTailCorruptionFalse(t *testing.T) { if IsTailCorruption(nil) { t.Error("IsTailCorruption(nil) = true, want false") } if IsTailCorruption(os.ErrNotExist) { t.Error("IsTailCorruption(ErrNotExist) = true, want false") } } func TestParseRecordsFromFile(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "segment-0.wal") // Create a segment file with header + one full block containing 2 records. f, err := os.Create(path) if err != nil { t.Fatal(err) } hdr := &WalFileHeader{ SegmentID: 0, StartSequence: 0, BlockSize: WalBlockSize, } encoded := EncodeWalHeader(hdr) if _, err := f.Write(encoded[:]); err != nil { t.Fatal(err) } block := make([]byte, WalBlockSize) payload1 := []byte("record-one") payload2 := []byte("record-two") offset := 0 rec1 := EncodePhysicalRecord(RecFull, payload1) copy(block[offset:], rec1) offset += len(rec1) rec2 := EncodePhysicalRecord(RecFull, payload2) copy(block[offset:], rec2) if _, err := f.Write(block); err != nil { t.Fatal(err) } if err := f.Close(); err != nil { t.Fatal(err) } recs, err := ParseRecordsFromFile(path) if err != nil { t.Fatalf("ParseRecordsFromFile: %v", err) } if len(recs) != 2 { t.Fatalf("expected 2 records, got %d", len(recs)) } if string(recs[0].Payload) != string(payload1) { t.Errorf("rec[0].Payload = %q, want %q", recs[0].Payload, payload1) } if string(recs[1].Payload) != string(payload2) { t.Errorf("rec[1].Payload = %q, want %q", recs[1].Payload, payload2) } } func TestParseRecordsFromFileShortLastBlock(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "segment-0.wal") f, err := os.Create(path) if err != nil { t.Fatal(err) } hdr := &WalFileHeader{ SegmentID: 0, StartSequence: 0, BlockSize: WalBlockSize, } encoded := EncodeWalHeader(hdr) if _, err := f.Write(encoded[:]); err != nil { t.Fatal(err) } // Write a partial block (just one record, no full 32KB). payload := []byte("short") rec := EncodePhysicalRecord(RecFull, payload) if _, err := f.Write(rec); err != nil { t.Fatal(err) } if err := f.Close(); err != nil { t.Fatal(err) } recs, err := ParseRecordsFromFile(path) if err != nil { t.Fatalf("ParseRecordsFromFile: %v", err) } if len(recs) != 1 { t.Fatalf("expected 1 record, got %d", len(recs)) } if string(recs[0].Payload) != string(payload) { t.Errorf("Payload = %q, want %q", recs[0].Payload, payload) } }