Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}