memo_test.gno
9.46 Kb ยท 448 lines
1package memo
2
3import (
4 "testing"
5 "time"
6)
7
8type timestampedValue struct {
9 value any
10 timestamp time.Time
11}
12
13// complexKey is used to test struct keys
14type complexKey struct {
15 ID int
16 Name string
17}
18
19func TestMemoize(t *testing.T) {
20 tests := []struct {
21 name string
22 key any
23 value any
24 callCount *int
25 }{
26 {
27 name: "string key and value",
28 key: "test-key",
29 value: "test-value",
30 callCount: new(int),
31 },
32 {
33 name: "int key and value",
34 key: 42,
35 value: 123,
36 callCount: new(int),
37 },
38 {
39 name: "mixed types",
40 key: "number",
41 value: 42,
42 callCount: new(int),
43 },
44 }
45
46 for _, tt := range tests {
47 t.Run(tt.name, func(t *testing.T) {
48 m := New()
49 if m.Size() != 0 {
50 t.Errorf("Initial size = %d, want 0", m.Size())
51 }
52
53 fn := func() any {
54 *tt.callCount++
55 return tt.value
56 }
57
58 // First call should compute
59 result := m.Memoize(tt.key, fn)
60 if result != tt.value {
61 t.Errorf("Memoize() = %v, want %v", result, tt.value)
62 }
63 if *tt.callCount != 1 {
64 t.Errorf("Function called %d times, want 1", *tt.callCount)
65 }
66 if m.Size() != 1 {
67 t.Errorf("Size after first call = %d, want 1", m.Size())
68 }
69
70 // Second call should use cache
71 result = m.Memoize(tt.key, fn)
72 if result != tt.value {
73 t.Errorf("Memoize() second call = %v, want %v", result, tt.value)
74 }
75 if *tt.callCount != 1 {
76 t.Errorf("Function called %d times, want 1", *tt.callCount)
77 }
78 if m.Size() != 1 {
79 t.Errorf("Size after second call = %d, want 1", m.Size())
80 }
81 })
82 }
83}
84
85func TestMemoizeWithValidator(t *testing.T) {
86 tests := []struct {
87 name string
88 key any
89 value any
90 validDuration time.Duration
91 waitDuration time.Duration
92 expectedCalls int
93 shouldRecompute bool
94 }{
95 {
96 name: "valid cache",
97 key: "key1",
98 value: "value1",
99 validDuration: time.Hour,
100 waitDuration: time.Millisecond,
101 expectedCalls: 1,
102 shouldRecompute: false,
103 },
104 {
105 name: "expired cache",
106 key: "key2",
107 value: "value2",
108 validDuration: time.Millisecond,
109 waitDuration: time.Millisecond * 2,
110 expectedCalls: 2,
111 shouldRecompute: true,
112 },
113 }
114
115 for _, tt := range tests {
116 t.Run(tt.name, func(t *testing.T) {
117 m := New()
118 callCount := 0
119
120 fn := func() any {
121 callCount++
122 return timestampedValue{
123 value: tt.value,
124 timestamp: time.Now(),
125 }
126 }
127
128 isValid := func(cached any) bool {
129 if tv, ok := cached.(timestampedValue); ok {
130 return time.Since(tv.timestamp) < tt.validDuration
131 }
132 return false
133 }
134
135 // First call
136 result := m.MemoizeWithValidator(tt.key, fn, isValid)
137 if tv, ok := result.(timestampedValue); !ok || tv.value != tt.value {
138 t.Errorf("MemoizeWithValidator() = %v, want value %v", result, tt.value)
139 }
140
141 // Wait
142 testing.SkipHeights(10)
143
144 // Second call
145 result = m.MemoizeWithValidator(tt.key, fn, isValid)
146 if tv, ok := result.(timestampedValue); !ok || tv.value != tt.value {
147 t.Errorf("MemoizeWithValidator() second call = %v, want value %v", result, tt.value)
148 }
149
150 if callCount != tt.expectedCalls {
151 t.Errorf("Function called %d times, want %d", callCount, tt.expectedCalls)
152 }
153 })
154 }
155}
156
157func TestInvalidate(t *testing.T) {
158 tests := []struct {
159 name string
160 key any
161 value any
162 callCount *int
163 }{
164 {
165 name: "invalidate existing key",
166 key: "test-key",
167 value: "test-value",
168 callCount: new(int),
169 },
170 {
171 name: "invalidate non-existing key",
172 key: "missing-key",
173 value: "test-value",
174 callCount: new(int),
175 },
176 }
177
178 for _, tt := range tests {
179 t.Run(tt.name, func(t *testing.T) {
180 m := New()
181 fn := func() any {
182 *tt.callCount++
183 return tt.value
184 }
185
186 // First call
187 m.Memoize(tt.key, fn)
188 if m.Size() != 1 {
189 t.Errorf("Size after first call = %d, want 1", m.Size())
190 }
191
192 // Invalidate
193 m.Invalidate(tt.key)
194 if m.Size() != 0 {
195 t.Errorf("Size after invalidate = %d, want 0", m.Size())
196 }
197
198 // Call again should recompute
199 result := m.Memoize(tt.key, fn)
200 if result != tt.value {
201 t.Errorf("Memoize() after invalidate = %v, want %v", result, tt.value)
202 }
203 if *tt.callCount != 2 {
204 t.Errorf("Function called %d times, want 2", *tt.callCount)
205 }
206 if m.Size() != 1 {
207 t.Errorf("Size after recompute = %d, want 1", m.Size())
208 }
209 })
210 }
211}
212
213func TestClear(t *testing.T) {
214 m := New()
215 callCount := 0
216
217 fn := func() any {
218 callCount++
219 return "value"
220 }
221
222 // Cache some values
223 m.Memoize("key1", fn)
224 m.Memoize("key2", fn)
225
226 if callCount != 2 {
227 t.Errorf("Initial calls = %d, want 2", callCount)
228 }
229 if m.Size() != 2 {
230 t.Errorf("Size after initial calls = %d, want 2", m.Size())
231 }
232
233 // Clear cache
234 m.Clear()
235 if m.Size() != 0 {
236 t.Errorf("Size after clear = %d, want 0", m.Size())
237 }
238
239 // Recompute values
240 m.Memoize("key1", fn)
241 m.Memoize("key2", fn)
242
243 if callCount != 4 {
244 t.Errorf("Calls after clear = %d, want 4", callCount)
245 }
246 if m.Size() != 2 {
247 t.Errorf("Size after recompute = %d, want 2", m.Size())
248 }
249}
250
251func TestSize(t *testing.T) {
252 m := New()
253
254 if m.Size() != 0 {
255 t.Errorf("Initial size = %d, want 0", m.Size())
256 }
257
258 callCount := 0
259 fn := func() any {
260 callCount++
261 return "value"
262 }
263
264 // Add items
265 m.Memoize("key1", fn)
266 if m.Size() != 1 {
267 t.Errorf("Size after first insert = %d, want 1", m.Size())
268 }
269
270 m.Memoize("key2", fn)
271 if m.Size() != 2 {
272 t.Errorf("Size after second insert = %d, want 2", m.Size())
273 }
274
275 // Duplicate key should not increase size
276 m.Memoize("key1", fn)
277 if m.Size() != 2 {
278 t.Errorf("Size after duplicate insert = %d, want 2", m.Size())
279 }
280
281 // Remove item
282 m.Invalidate("key1")
283 if m.Size() != 1 {
284 t.Errorf("Size after invalidate = %d, want 1", m.Size())
285 }
286
287 // Clear all
288 m.Clear()
289 if m.Size() != 0 {
290 t.Errorf("Size after clear = %d, want 0", m.Size())
291 }
292}
293
294func TestMemoizeWithDifferentKeyTypes(t *testing.T) {
295 tests := []struct {
296 name string
297 keys []any // Now an array of keys
298 values []string // Corresponding values
299 callCount *int
300 }{
301 {
302 name: "integer keys",
303 keys: []any{42, 43},
304 values: []string{"value-for-42", "value-for-43"},
305 callCount: new(int),
306 },
307 {
308 name: "float keys",
309 keys: []any{3.14, 2.718},
310 values: []string{"value-for-pi", "value-for-e"},
311 callCount: new(int),
312 },
313 {
314 name: "bool keys",
315 keys: []any{true, false},
316 values: []string{"value-for-true", "value-for-false"},
317 callCount: new(int),
318 },
319 /*
320 {
321 name: "struct keys",
322 keys: []any{
323 complexKey{ID: 1, Name: "test1"},
324 complexKey{ID: 2, Name: "test2"},
325 },
326 values: []string{"value-for-struct1", "value-for-struct2"},
327 callCount: new(int),
328 },
329 {
330 name: "nil and empty interface keys",
331 keys: []any{nil, any(nil)},
332 values: []string{"value-for-nil", "value-for-empty-interface"},
333 callCount: new(int),
334 },
335 */
336 }
337
338 for _, tt := range tests {
339 t.Run(tt.name, func(t *testing.T) {
340 m := New()
341
342 // Test both keys
343 for i, key := range tt.keys {
344 value := tt.values[i]
345 fn := func() any {
346 *tt.callCount++
347 return value
348 }
349
350 // First call should compute
351 result := m.Memoize(key, fn)
352 if result != value {
353 t.Errorf("Memoize() for key %v = %v, want %v", key, result, value)
354 }
355 if *tt.callCount != i+1 {
356 t.Errorf("Function called %d times, want %d", *tt.callCount, i+1)
357 }
358 }
359
360 // Verify size includes both entries
361 if m.Size() != 2 {
362 t.Errorf("Size after both inserts = %d, want 2", m.Size())
363 }
364
365 // Second call for each key should use cache
366 for i, key := range tt.keys {
367 initialCount := *tt.callCount
368 result := m.Memoize(key, func() any {
369 *tt.callCount++
370 return "should-not-be-called"
371 })
372
373 if result != tt.values[i] {
374 t.Errorf("Memoize() second call for key %v = %v, want %v", key, result, tt.values[i])
375 }
376 if *tt.callCount != initialCount {
377 t.Errorf("Cache miss for key %v", key)
378 }
379 }
380
381 // Test invalidate for each key
382 for i, key := range tt.keys {
383 m.Invalidate(key)
384 if m.Size() != 1-i {
385 t.Errorf("Size after invalidate %d = %d, want %d", i+1, m.Size(), 1-i)
386 }
387 }
388 })
389 }
390}
391
392func TestMultipleKeyTypes(t *testing.T) {
393 m := New()
394 callCount := 0
395
396 // Insert different key types simultaneously (two of each type)
397 keys := []any{
398 42, 43, // ints
399 "string-key1", "string-key2", // strings
400 3.14, 2.718, // floats
401 true, false, // bools
402 }
403
404 for i, key := range keys {
405 value := i
406 m.Memoize(key, func() any {
407 callCount++
408 return value
409 })
410 }
411
412 // Verify size includes all entries
413 if m.Size() != len(keys) {
414 t.Errorf("Size = %d, want %d", m.Size(), len(keys))
415 }
416
417 // Verify all values are cached correctly
418 for i, key := range keys {
419 initialCount := callCount
420 result := m.Memoize(key, func() any {
421 callCount++
422 return -1 // Should never be returned if cache works
423 })
424
425 if result != i {
426 t.Errorf("Memoize(%v) = %v, want %v", key, result, i)
427 }
428 if callCount != initialCount {
429 t.Errorf("Cache miss for key %v", key)
430 }
431 }
432
433 // Test invalidation of pairs
434 for i := 0; i < len(keys); i += 2 {
435 m.Invalidate(keys[i])
436 m.Invalidate(keys[i+1])
437 expectedSize := len(keys) - (i + 2)
438 if m.Size() != expectedSize {
439 t.Errorf("Size after invalidating pair %d = %d, want %d", i/2, m.Size(), expectedSize)
440 }
441 }
442
443 // Clear and verify
444 m.Clear()
445 if m.Size() != 0 {
446 t.Errorf("Size after clear = %d, want 0", m.Size())
447 }
448}