287 lines
5.0 KiB
Go
287 lines
5.0 KiB
Go
package lru
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewLRUCache(t *testing.T) {
|
|
cache := NewLRUCache(5)
|
|
if cache.capacity != 5 {
|
|
t.Errorf("expected capacity 5, got %d", cache.capacity)
|
|
}
|
|
if cache.Len() != 0 {
|
|
t.Errorf("expected empty cache, got length %d", cache.Len())
|
|
}
|
|
}
|
|
|
|
func TestPutAndGet(t *testing.T) {
|
|
cache := NewLRUCache(3)
|
|
|
|
// Test Put and Get
|
|
cache.Put("a", 1)
|
|
cache.Put("b", 2)
|
|
cache.Put("c", 3)
|
|
|
|
val, exists := cache.Get("a")
|
|
if !exists || val != 1 {
|
|
t.Errorf("expected (1, true), got (%v, %v)", val, exists)
|
|
}
|
|
|
|
val, exists = cache.Get("b")
|
|
if !exists || val != 2 {
|
|
t.Errorf("expected (2, true), got (%v, %v)", val, exists)
|
|
}
|
|
|
|
// Test miss
|
|
val, exists = cache.Get("z")
|
|
if exists {
|
|
t.Errorf("expected (nil, false), got (%v, %v)", val, exists)
|
|
}
|
|
}
|
|
|
|
func TestUpdateExistingKey(t *testing.T) {
|
|
cache := NewLRUCache(2)
|
|
|
|
cache.Put("a", 1)
|
|
cache.Put("a", 10) // Update
|
|
|
|
val, exists := cache.Get("a")
|
|
if !exists || val != 10 {
|
|
t.Errorf("expected (10, true), got (%v, %v)", val, exists)
|
|
}
|
|
|
|
if cache.Len() != 1 {
|
|
t.Errorf("expected length 1, got %d", cache.Len())
|
|
}
|
|
}
|
|
|
|
func TestEviction(t *testing.T) {
|
|
cache := NewLRUCache(3)
|
|
|
|
cache.Put("a", 1)
|
|
cache.Put("b", 2)
|
|
cache.Put("c", 3)
|
|
cache.Put("d", 4) // Should evict "a"
|
|
|
|
_, exists := cache.Get("a")
|
|
if exists {
|
|
t.Error("expected 'a' to be evicted")
|
|
}
|
|
|
|
val, exists := cache.Get("b")
|
|
if !exists || val != 2 {
|
|
t.Errorf("expected (2, true), got (%v, %v)", val, exists)
|
|
}
|
|
}
|
|
|
|
func TestLRUOrdering(t *testing.T) {
|
|
cache := NewLRUCache(3)
|
|
|
|
cache.Put("a", 1)
|
|
cache.Put("b", 2)
|
|
cache.Put("c", 3)
|
|
|
|
// Access "a" to make it recently used
|
|
cache.Get("a")
|
|
|
|
// Add "d", should evict "b" (least recently used)
|
|
cache.Put("d", 4)
|
|
|
|
_, exists := cache.Get("b")
|
|
if exists {
|
|
t.Error("expected 'b' to be evicted")
|
|
}
|
|
|
|
// Verify others still exist
|
|
val, exists := cache.Get("a")
|
|
if !exists || val != 1 {
|
|
t.Errorf("expected (1, true), got (%v, %v)", val, exists)
|
|
}
|
|
|
|
val, exists = cache.Get("c")
|
|
if !exists || val != 3 {
|
|
t.Errorf("expected (3, true), got (%v, %v)", val, exists)
|
|
}
|
|
|
|
val, exists = cache.Get("d")
|
|
if !exists || val != 4 {
|
|
t.Errorf("expected (4, true), got (%v, %v)", val, exists)
|
|
}
|
|
}
|
|
|
|
func TestUpdateMakesRecent(t *testing.T) {
|
|
cache := NewLRUCache(3)
|
|
|
|
cache.Put("a", 1)
|
|
cache.Put("b", 2)
|
|
cache.Put("c", 3)
|
|
|
|
// Update "a" to make it recently used
|
|
cache.Put("a", 10)
|
|
|
|
// Add "d", should evict "b"
|
|
cache.Put("d", 4)
|
|
|
|
_, exists := cache.Get("b")
|
|
if exists {
|
|
t.Error("expected 'b' to be evicted")
|
|
}
|
|
|
|
val, exists := cache.Get("a")
|
|
if !exists || val != 10 {
|
|
t.Errorf("expected (10, true), got (%v, %v)", val, exists)
|
|
}
|
|
}
|
|
|
|
func TestClear(t *testing.T) {
|
|
cache := NewLRUCache(3)
|
|
|
|
cache.Put("a", 1)
|
|
cache.Put("b", 2)
|
|
cache.Put("c", 3)
|
|
|
|
cache.Clear()
|
|
|
|
if cache.Len() != 0 {
|
|
t.Errorf("expected empty cache after clear, got length %d", cache.Len())
|
|
}
|
|
|
|
_, exists := cache.Get("a")
|
|
if exists {
|
|
t.Error("expected cache to be empty after clear")
|
|
}
|
|
}
|
|
|
|
func TestSingleCapacity(t *testing.T) {
|
|
cache := NewLRUCache(1)
|
|
|
|
cache.Put("a", 1)
|
|
cache.Put("b", 2) // Should evict "a"
|
|
|
|
_, exists := cache.Get("a")
|
|
if exists {
|
|
t.Error("expected 'a' to be evicted")
|
|
}
|
|
|
|
val, exists := cache.Get("b")
|
|
if !exists || val != 2 {
|
|
t.Errorf("expected (2, true), got (%v, %v)", val, exists)
|
|
}
|
|
}
|
|
|
|
func TestDifferentTypes(t *testing.T) {
|
|
cache := NewLRUCache(3)
|
|
|
|
// Test with different key/value types
|
|
cache.Put(1, "one")
|
|
cache.Put("two", 2)
|
|
cache.Put(3.14, []int{1, 2, 3})
|
|
|
|
val, exists := cache.Get(1)
|
|
if !exists || val != "one" {
|
|
t.Errorf("expected ('one', true), got (%v, %v)", val, exists)
|
|
}
|
|
|
|
val, exists = cache.Get("two")
|
|
if !exists || val != 2 {
|
|
t.Errorf("expected (2, true), got (%v, %v)", val, exists)
|
|
}
|
|
|
|
val, exists = cache.Get(3.14)
|
|
if !exists {
|
|
t.Errorf("expected slice value, got (%v, %v)", val, exists)
|
|
}
|
|
}
|
|
|
|
func TestConcurrentAccess(t *testing.T) {
|
|
cache := NewLRUCache(100)
|
|
var wg sync.WaitGroup
|
|
|
|
// Concurrent puts
|
|
for i := range 50 {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
cache.Put(i, i*10)
|
|
}(i)
|
|
}
|
|
|
|
// Concurrent gets
|
|
for i := range 50 {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
cache.Get(i)
|
|
}(i)
|
|
}
|
|
|
|
// Concurrent updates
|
|
for i := range 25 {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
cache.Put(i, i*100)
|
|
}(i)
|
|
}
|
|
|
|
// Concurrent len checks
|
|
for range 10 {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
cache.Len()
|
|
}()
|
|
}
|
|
|
|
// Concurrent clear
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
cache.Clear()
|
|
}()
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestRaceCondition(t *testing.T) {
|
|
cache := NewLRUCache(10)
|
|
done := make(chan bool)
|
|
|
|
go func() {
|
|
for i := range 1000 {
|
|
cache.Put(i%10, i)
|
|
}
|
|
done <- true
|
|
}()
|
|
|
|
go func() {
|
|
for i := range 1000 {
|
|
cache.Get(i % 10)
|
|
}
|
|
done <- true
|
|
}()
|
|
|
|
<-done
|
|
<-done
|
|
}
|
|
|
|
func BenchmarkPut(b *testing.B) {
|
|
cache := NewLRUCache(1000)
|
|
|
|
for i := 0; b.Loop(); i++ {
|
|
cache.Put(i%1000, i)
|
|
}
|
|
}
|
|
|
|
func BenchmarkGet(b *testing.B) {
|
|
cache := NewLRUCache(1000)
|
|
for i := range 1000 {
|
|
cache.Put(i, i)
|
|
}
|
|
|
|
for i := 0; b.Loop(); i++ {
|
|
cache.Get(i % 1000)
|
|
}
|
|
}
|