package go_kv import ( "fmt" "os" "path/filepath" "testing" ) func TestDBOpenClose(t *testing.T) { db, err := Open(t.TempDir(), nil) if err != nil { t.Fatalf("Open: %v", err) } if err := db.Put([]byte("k"), []byte("v")); err != nil { t.Fatalf("Put: %v", err) } if err := db.Close(); err != nil { t.Fatalf("Close: %v", err) } } func TestDBPutGet(t *testing.T) { db := openTestDB(t, t.TempDir()) if err := db.Put([]byte("k1"), []byte("v1")); err != nil { t.Fatalf("Put: %v", err) } got := db.Get([]byte("k1")) if !got.Found { t.Fatal("Get(k1) not found") } if string(got.Value) != "v1" { t.Fatalf("Get(k1) value = %q, want %q", got.Value, "v1") } } func TestDBDelete(t *testing.T) { db := openTestDB(t, t.TempDir()) if err := db.Put([]byte("k"), []byte("v")); err != nil { t.Fatalf("Put: %v", err) } if err := db.Delete([]byte("k")); err != nil { t.Fatalf("Delete: %v", err) } got := db.Get([]byte("k")) if got.Found { t.Fatalf("Get(k) found deleted key with value %q", got.Value) } } func TestDBRecovery(t *testing.T) { dir := t.TempDir() db := openTestDB(t, dir) entries := map[string]string{ "k1": "v1", "k2": "v2", "k3": "v3", } for key, value := range entries { if err := db.Put([]byte(key), []byte(value)); err != nil { t.Fatalf("Put(%q): %v", key, err) } } closeTestDB(t, db) reopened := openTestDB(t, dir) for key, want := range entries { got := reopened.Get([]byte(key)) if !got.Found { t.Fatalf("Get(%q) not found after recovery", key) } if string(got.Value) != want { t.Fatalf("Get(%q) value = %q, want %q", key, got.Value, want) } } } func TestDBRecoveryAfterCorruption(t *testing.T) { dir := t.TempDir() db := openTestDB(t, dir) if err := db.Put([]byte("good1"), []byte("v1")); err != nil { t.Fatalf("Put good1: %v", err) } if err := db.Put([]byte("good2"), []byte("v2")); err != nil { t.Fatalf("Put good2: %v", err) } closeTestDB(t, db) appendWalTail(t, dir, []byte{0xde, 0xad, 0xbe, 0xef}) reopened := openTestDB(t, dir) for key, want := range map[string]string{"good1": "v1", "good2": "v2"} { got := reopened.Get([]byte(key)) if !got.Found { t.Fatalf("Get(%q) not found after corrupted-tail recovery", key) } if string(got.Value) != want { t.Fatalf("Get(%q) value = %q, want %q", key, got.Value, want) } } } func TestDBMultiplePuts(t *testing.T) { dir := t.TempDir() db := openTestDB(t, dir) for i := range 50 { key := fmt.Appendf(nil, "k%d", i) value := fmt.Appendf(nil, "v%d", i) if err := db.Put(key, value); err != nil { t.Fatalf("Put(%q): %v", key, err) } } closeTestDB(t, db) reopened := openTestDB(t, dir) for i := range 50 { key := fmt.Appendf(nil, "k%d", i) want := fmt.Sprintf("v%d", i) got := reopened.Get(key) if !got.Found { t.Fatalf("Get(%q) not found after recovery", key) } if string(got.Value) != want { t.Fatalf("Get(%q) value = %q, want %q", key, got.Value, want) } } } // Regression guard for C2+C3: data must survive three Open/Close cycles. // C3's data-loss bug manifests on the SECOND restart after writes — two // opens cannot catch it. // // Single-segment only: do NOT force rotation, because segment_manager has // an unrelated C8 bug (passes byte offset as startSequence) that breaks // multi-segment recovery. That bug is tracked separately. func TestOpenThreeTimesKeepsData(t *testing.T) { dir := t.TempDir() keys := []string{"k1", "k2", "k3", "k4", "k5"} db1, err := Open(dir, nil) if err != nil { t.Fatalf("Open 1: %v", err) } for _, k := range keys { if err := db1.Put([]byte(k), []byte("v-"+k)); err != nil { t.Fatalf("Put %s: %v", k, err) } } if err := db1.Close(); err != nil { t.Fatalf("Close 1: %v", err) } verify := func(label string, db *DB) { t.Helper() for _, k := range keys { r := db.Get([]byte(k)) if !r.Found { t.Errorf("%s: key %s not found", label, k) } } } db2, err := Open(dir, nil) if err != nil { t.Fatalf("Open 2: %v", err) } verify("Open 2", db2) if err := db2.Close(); err != nil { t.Fatalf("Close 2: %v", err) } db3, err := Open(dir, nil) if err != nil { t.Fatalf("Open 3: %v", err) } defer db3.Close() verify("Open 3", db3) } func openTestDB(t *testing.T, dir string) *DB { t.Helper() db, err := Open(dir, nil) if err != nil { t.Fatalf("Open: %v", err) } t.Cleanup(func() { if !db.closed.Load() { if err := db.Close(); err != nil { t.Fatalf("Close cleanup: %v", err) } } }) return db } func closeTestDB(t *testing.T, db *DB) { t.Helper() if err := db.Close(); err != nil { t.Fatalf("Close: %v", err) } } func appendWalTail(t *testing.T, dir string, tail []byte) { t.Helper() path := filepath.Join(dir, "segment-0.wal") f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0) if err != nil { t.Fatalf("OpenFile(%s): %v", path, err) } defer func() { if err := f.Close(); err != nil { t.Fatalf("Close corrupted WAL file: %v", err) } }() if _, err := f.Write(tail); err != nil { t.Fatalf("Write corrupted WAL tail: %v", err) } }