1package cache
2
3import (
4 "sync"
5 "testing"
6
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/require"
9)
10
11func TestLRU_BasicOperations(t *testing.T) {
12 cache := NewLRU[string, int](3)
13
14 // Test Set and Get
15 cache.Set("a", 1)
16 cache.Set("b", 2)
17 cache.Set("c", 3)
18
19 val, ok := cache.Get("a")
20 assert.True(t, ok)
21 assert.Equal(t, 1, val)
22
23 val, ok = cache.Get("b")
24 assert.True(t, ok)
25 assert.Equal(t, 2, val)
26
27 // Test not found
28 val, ok = cache.Get("notfound")
29 assert.False(t, ok)
30 assert.Equal(t, 0, val)
31
32 assert.Equal(t, 3, cache.Len())
33}
34
35func TestLRU_Eviction(t *testing.T) {
36 cache := NewLRU[string, int](2)
37
38 cache.Set("a", 1)
39 cache.Set("b", 2)
40 // Cache is now at capacity: [b, a] (b is most recent)
41
42 // Adding "c" should evict "a" (least recently used)
43 cache.Set("c", 3)
44
45 _, ok := cache.Get("a")
46 assert.False(t, ok, "a should have been evicted")
47
48 val, ok := cache.Get("b")
49 assert.True(t, ok)
50 assert.Equal(t, 2, val)
51
52 val, ok = cache.Get("c")
53 assert.True(t, ok)
54 assert.Equal(t, 3, val)
55}
56
57func TestLRU_GetUpdatesRecency(t *testing.T) {
58 cache := NewLRU[string, int](2)
59
60 cache.Set("a", 1)
61 cache.Set("b", 2)
62 // Order: [b, a]
63
64 // Access "a" to make it most recent
65 cache.Get("a")
66 // Order: [a, b]
67
68 // Adding "c" should now evict "b" (least recently used)
69 cache.Set("c", 3)
70
71 val, ok := cache.Get("a")
72 assert.True(t, ok, "a should still exist")
73 assert.Equal(t, 1, val)
74
75 _, ok = cache.Get("b")
76 assert.False(t, ok, "b should have been evicted")
77}
78
79func TestLRU_UpdateExisting(t *testing.T) {
80 cache := NewLRU[string, int](2)
81
82 cache.Set("a", 1)
83 cache.Set("b", 2)
84
85 // Update "a" with new value
86 cache.Set("a", 100)
87
88 val, ok := cache.Get("a")
89 assert.True(t, ok)
90 assert.Equal(t, 100, val)
91
92 // Cache should still have 2 items (no duplicate)
93 assert.Equal(t, 2, cache.Len())
94}
95
96func TestLRU_Remove(t *testing.T) {
97 cache := NewLRU[string, int](3)
98
99 cache.Set("a", 1)
100 cache.Set("b", 2)
101 cache.Set("c", 3)
102
103 cache.Remove("b")
104
105 _, ok := cache.Get("b")
106 assert.False(t, ok)
107 assert.Equal(t, 2, cache.Len())
108
109 // Remove non-existent key should not panic
110 cache.Remove("notfound")
111 assert.Equal(t, 2, cache.Len())
112}
113
114func TestLRU_Clear(t *testing.T) {
115 cache := NewLRU[string, int](3)
116
117 cache.Set("a", 1)
118 cache.Set("b", 2)
119 cache.Set("c", 3)
120
121 cache.Clear()
122
123 assert.Equal(t, 0, cache.Len())
124 _, ok := cache.Get("a")
125 assert.False(t, ok)
126}
127
128func TestLRU_ZeroCapacity(t *testing.T) {
129 // Should default to capacity of 1
130 cache := NewLRU[string, int](0)
131
132 cache.Set("a", 1)
133 val, ok := cache.Get("a")
134 assert.True(t, ok)
135 assert.Equal(t, 1, val)
136
137 // Adding another should evict first
138 cache.Set("b", 2)
139 _, ok = cache.Get("a")
140 assert.False(t, ok)
141}
142
143func TestLRU_ConcurrentAccess(t *testing.T) {
144 cache := NewLRU[int, int](100)
145 var wg sync.WaitGroup
146
147 // Concurrent writes
148 for i := range 100 {
149 wg.Add(1)
150 go func(i int) {
151 defer wg.Done()
152 cache.Set(i, i*10)
153 }(i)
154 }
155 wg.Wait()
156
157 // Concurrent reads
158 for i := range 100 {
159 wg.Add(1)
160 go func(i int) {
161 defer wg.Done()
162 cache.Get(i)
163 }(i)
164 }
165 wg.Wait()
166
167 // Concurrent mixed operations
168 for i := range 50 {
169 wg.Add(3)
170 go func(i int) {
171 defer wg.Done()
172 cache.Set(i+100, i)
173 }(i)
174 go func(i int) {
175 defer wg.Done()
176 cache.Get(i)
177 }(i)
178 go func(i int) {
179 defer wg.Done()
180 cache.Remove(i + 50)
181 }(i)
182 }
183 wg.Wait()
184
185 // Should not panic and cache should be in valid state
186 require.LessOrEqual(t, cache.Len(), 100)
187}
188
189func TestLRU_PointerValues(t *testing.T) {
190 type Data struct {
191 Name string
192 Value float64
193 }
194
195 cache := NewLRU[string, *Data](2)
196
197 cache.Set("key1", &Data{Name: "test1", Value: 1.5})
198 cache.Set("key2", &Data{Name: "test2", Value: 2.5})
199
200 val, ok := cache.Get("key1")
201 require.True(t, ok)
202 assert.Equal(t, "test1", val.Name)
203 assert.InDelta(t, 1.5, val.Value, 0.001)
204
205 // Update pointer value
206 cache.Set("key1", &Data{Name: "updated", Value: 99.9})
207
208 val, ok = cache.Get("key1")
209 require.True(t, ok)
210 assert.Equal(t, "updated", val.Name)
211 assert.InDelta(t, 99.9, val.Value, 0.001)
212}