package memtable import ( "sync" "testing" ) func TestMemTablePendingInvisible(t *testing.T) { mt := NewMemTable(4096) // Put a pending entry — must not be visible. if err := mt.PutPending([]byte("key1"), []byte("val1"), 1); err != nil { t.Fatalf("PutPending: %v", err) } res := mt.Get([]byte("key1")) if res.Found { t.Fatal("pending entry should not be visible") } // Publish sequence 1 — entry becomes visible. mt.Publish(1) res = mt.Get([]byte("key1")) if !res.Found { t.Fatal("published entry should be visible") } if string(res.Value) != "val1" { t.Fatalf("value mismatch: got %q, want %q", res.Value, "val1") } if res.Sequence != 1 { t.Fatalf("sequence mismatch: got %d, want %d", res.Sequence, 1) } } func TestMemTableAbortedInvisible(t *testing.T) { mt := NewMemTable(4096) if err := mt.PutPending([]byte("key1"), []byte("val1"), 1); err != nil { t.Fatalf("PutPending: %v", err) } // Abort sequence 1 before publishing. mt.Abort(1) // Publish up to sequence 10 — aborted entry should stay invisible. mt.Publish(10) res := mt.Get([]byte("key1")) if res.Found { t.Fatal("aborted entry should not be visible") } } func TestMemTableMultiplePublish(t *testing.T) { mt := NewMemTable(8192) if err := mt.PutPending([]byte("a"), []byte("va"), 1); err != nil { t.Fatalf("PutPending a: %v", err) } if err := mt.PutPending([]byte("b"), []byte("vb"), 2); err != nil { t.Fatalf("PutPending b: %v", err) } // Neither visible before publish. if mt.Get([]byte("a")).Found { t.Fatal("a should not be visible before publish") } if mt.Get([]byte("b")).Found { t.Fatal("b should not be visible before publish") } // Publish both. mt.Publish(2) if !mt.Get([]byte("a")).Found { t.Fatal("a should be visible after publish") } if !mt.Get([]byte("b")).Found { t.Fatal("b should be visible after publish") } if string(mt.Get([]byte("a")).Value) != "va" { t.Fatal("value mismatch for a") } if string(mt.Get([]byte("b")).Value) != "vb" { t.Fatal("value mismatch for b") } } func TestMemTableReserve(t *testing.T) { mt := NewMemTable(4096) // Reserve 10 small entries — should succeed. entries := make([]ReserveEntry, 10) for i := range entries { entries[i] = ReserveEntry{ Key: []byte("k"), Value: []byte("v"), } } total, err := mt.Reserve(entries) if err != nil { t.Fatalf("Reserve 10 entries: %v", err) } if total == 0 { t.Fatal("expected non-zero total size") } // Reserve more than remaining — should fail. big := []ReserveEntry{{ Key: make([]byte, 2048), Value: make([]byte, 2048), }} _, err = mt.Reserve(big) if err == nil { t.Fatal("expected ErrMemTableFull for oversized reserve") } } func TestMemTableConcurrentReads(t *testing.T) { mt := NewMemTable(8192) // Write 10 pending entries. for i := 0; i < 10; i++ { key := []byte{byte('a' + i)} val := []byte{byte(i)} if err := mt.PutPending(key, val, uint64(i+1)); err != nil { t.Fatalf("PutPending %d: %v", i, err) } } // Publish all. mt.Publish(10) // Concurrent reads should all see published values. var wg sync.WaitGroup for g := 0; g < 4; g++ { wg.Add(1) go func() { defer wg.Done() for i := 0; i < 100; i++ { key := []byte{byte('a' + (i % 10))} res := mt.Get(key) if !res.Found { t.Errorf("key %s not found", key) return } } }() } wg.Wait() } func TestMemTableIteratorSkipsPending(t *testing.T) { mt := NewMemTable(4096) _ = mt.PutPending([]byte("pending"), []byte("p"), 1) _ = mt.PutPending([]byte("published"), []byte("pub"), 2) mt.Publish(2) // publishes "published" at seq 2 // Abort seq 1 — stays pending. // Actually we already published up to 2 which would publish seq 1 too. // Let's test differently: insert a pending entry after publish. _ = mt.PutPending([]byte("still_pending"), []byte("sp"), 3) it := mt.NewIterator() count := 0 for it.Valid() { count++ it.Next() } // Should see "pending" (seq 1, now published) and "published" (seq 2), // but not "still_pending" (seq 3, still pending). if count != 2 { t.Fatalf("expected 2 visible entries, got %d", count) } } func TestMemTableDeletePending(t *testing.T) { mt := NewMemTable(4096) // Insert then delete. _ = mt.PutPending([]byte("key1"), []byte("val1"), 1) _ = mt.DeletePending([]byte("key1"), 2) mt.Publish(2) // After publish, the latest entry is a tombstone (nil value). res := mt.Get([]byte("key1")) if !res.Found { t.Fatal("tombstone entry should still be 'found'") } if res.Value != nil { t.Fatalf("expected nil value for tombstone, got %q", res.Value) } } func TestMemTableApproximateSize(t *testing.T) { mt := NewMemTable(4096) if mt.ApproximateSize() != 0 { t.Fatal("new memtable should have zero approximate size") } if mt.UsableCapacity() != 4096 { t.Fatalf("usable capacity: got %d, want 4096", mt.UsableCapacity()) } }