package main import ( "fmt" "sync" "testing" "time" ) func TestBasicPutGet(t *testing.T) { c := NewLRU(2) c.Put("a", 1) if v, ok := c.Get("a"); !ok || v.(int) != 1 { t.Fatalf("want (1,true), got (%v,%v)", v, ok) } if _, ok := c.Get("missing"); ok { t.Fatal("miss reported as hit") } } func TestEvictionOrder(t *testing.T) { c := NewLRU(2) c.Put("a", 1) c.Put("b", 2) c.Put("c", 3) // evicts a (LRU) if _, ok := c.Get("a"); ok { t.Fatal("a should have been evicted") } for _, k := range []string{"b", "c"} { if _, ok := c.Get(k); !ok { t.Fatalf("%s should still be present", k) } } } func TestGetRefreshesRecency(t *testing.T) { c := NewLRU(2) c.Put("a", 1) c.Put("b", 2) c.Get("a") // a becomes MRU c.Put("c", 3) // must evict b, not a if _, ok := c.Get("a"); !ok { t.Fatal("a was evicted despite being MRU — Get must refresh recency") } if _, ok := c.Get("b"); ok { t.Fatal("b should have been evicted") } } func TestUpdateExistingKey(t *testing.T) { c := NewLRU(2) c.Put("a", 1) c.Put("b", 2) c.Put("a", 99) // update, not insert — must NOT evict if c.Len() != 2 { t.Fatalf("update inflated size: len=%d", c.Len()) } if v, _ := c.Get("a"); v.(int) != 99 { t.Fatalf("update lost: got %v", v) } c.Put("c", 3) // evicts b (a was refreshed by the update) if _, ok := c.Get("b"); ok { t.Fatal("b should have been evicted after a's update refreshed it") } } func TestConcurrentAccess(t *testing.T) { c := NewLRU(64) var wg sync.WaitGroup for g := 0; g < 8; g++ { wg.Add(1) go func(g int) { defer wg.Done() for i := 0; i < 500; i++ { k := fmt.Sprintf("k%d", i%100) c.Put(k, i) c.Get(k) } }(g) } wg.Wait() if c.Len() > 64 { t.Fatalf("capacity exceeded under concurrency: %d", c.Len()) } } // TestLenRacesWithPut calls Len concurrently WITH writers, not after them. // TestConcurrentAccess only reads Len after wg.Wait(), which establishes // happens-before with every writer — so an unsynchronized Len can't race // there. The race detector reports only races that actually occur. func TestLenRacesWithPut(t *testing.T) { c := NewLRU(64) done := make(chan struct{}) var wg sync.WaitGroup wg.Add(1) go func() { // writer defer wg.Done() for i := 0; ; i++ { select { case <-done: return default: c.Put(fmt.Sprintf("k%d", i%128), i) } } }() wg.Add(1) go func() { // reader, concurrently defer wg.Done() for i := 0; i < 10000; i++ { if n := c.Len(); n > 64 { t.Errorf("Len observed %d > capacity 64 (mid-Put state leaked)", n) return } } }() go func() { wg.Wait() }() time.Sleep(50 * time.Millisecond) close(done) wg.Wait() }