diff --git a/wal/entry_test.go b/wal/entry_test.go index 197d92b..7d093ee 100644 --- a/wal/entry_test.go +++ b/wal/entry_test.go @@ -65,7 +65,7 @@ func TestEntryValidation(t *testing.T) { {"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}, + {"put_inline_val_empty", &WalEntry{OpPut, VKInline, []byte("k"), []byte{}}, false}, } for _, tc := range cases { diff --git a/wal/segment_writer_test.go b/wal/segment_writer_test.go index 2423b80..ae48075 100644 --- a/wal/segment_writer_test.go +++ b/wal/segment_writer_test.go @@ -1,6 +1,7 @@ package wal import ( + "fmt" "os" "path/filepath" "testing" @@ -255,13 +256,19 @@ func TestSegmentWriterLargeBatch(t *testing.T) { } // Create a batch larger than one block payload (~32KB - 7 bytes). - largeValue := make([]byte, 40*1024) + largeValue := make([]byte, MaxWalInlineValueBytes) for i := range largeValue { largeValue[i] = byte(i % 256) } - entries := []*WalEntry{ - {OpType: OpPut, ValueKind: VKValueLogPointer, Key: []byte("large-key"), Value: largeValue}, + var entries []*WalEntry + for i := 0; i < 9; i++ { + entries = append(entries, &WalEntry{ + OpType: OpPut, + ValueKind: VKInline, + Key: []byte(fmt.Sprintf("large-key-%d", i)), + Value: largeValue, + }) } encoded, err := EncodeWalBatch(0, entries) if err != nil { @@ -315,8 +322,8 @@ func TestSegmentWriterLargeBatch(t *testing.T) { if err != nil { t.Fatalf("DecodeWalBatch: %v", err) } - if decoded.EntryCount != 1 { - t.Errorf("EntryCount = %d, want 1", decoded.EntryCount) + if decoded.EntryCount != uint32(len(entries)) { + t.Errorf("EntryCount = %d, want %d", decoded.EntryCount, len(entries)) } } diff --git a/wal/writer_test.go b/wal/writer_test.go index 25b758a..023dc42 100644 --- a/wal/writer_test.go +++ b/wal/writer_test.go @@ -1,11 +1,13 @@ package wal import ( + "errors" "fmt" "sync" "testing" "github.com/dailz/go-kv/config" + "github.com/dailz/go-kv/errkit" ) func newTestWalWriter(t *testing.T) *WalWriter { @@ -118,3 +120,47 @@ func TestWalWriterDelete(t *testing.T) { t.Fatalf("Get(k) found deleted key with value %q", got.Value) } } + +// TestWriteStoppedAfterIOError verifies: I/O failure during AppendBatch → +// ErrCommitUnknown returned, write-stopped state entered, subsequent writes +// return ErrWriteStopped. +func TestWriteStoppedAfterIOError(t *testing.T) { + cfg := config.Defaults() + ww, err := NewWalWriter(&cfg, t.TempDir(), 0, 0) + if err != nil { + t.Fatalf("NewWalWriter: %v", err) + } + + if err := ww.Put([]byte("ok-key"), []byte("ok-val")); err != nil { + t.Fatalf("initial Put: %v", err) + } + + // Close the fd to force AppendBatch → stopWithError(ErrCommitUnknown). + if err := ww.segManager.active.fd.Close(); err != nil { + t.Fatalf("close fd: %v", err) + } + + err = ww.Put([]byte("fail-key"), []byte("fail-val")) + if err == nil { + t.Fatal("expected error from Put after I/O failure, got nil") + } + if !errors.Is(err, errkit.ErrCommitUnknown) { + t.Errorf("Put error = %v, want ErrCommitUnknown", err) + } + + if !ww.IsWriteStopped() { + t.Fatal("IsWriteStopped() = false, want true after I/O error") + } + + err = ww.Put([]byte("after-key"), []byte("after-val")) + if !errors.Is(err, errkit.ErrWriteStopped) { + t.Errorf("Put after write-stopped error = %v, want ErrWriteStopped", err) + } + + err = ww.Delete([]byte("after-key")) + if !errors.Is(err, errkit.ErrWriteStopped) { + t.Errorf("Delete after write-stopped error = %v, want ErrWriteStopped", err) + } + + _ = ww.Close() +}