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}