maps_test.go

  1package csync
  2
  3import (
  4	"encoding/json"
  5	"maps"
  6	"sync"
  7	"testing"
  8
  9	"github.com/stretchr/testify/require"
 10)
 11
 12func TestNewMap(t *testing.T) {
 13	t.Parallel()
 14
 15	m := NewMap[string, int]()
 16	require.NotNil(t, m)
 17	require.NotNil(t, m.inner)
 18	require.Equal(t, 0, m.Len())
 19}
 20
 21func TestNewMapFrom(t *testing.T) {
 22	t.Parallel()
 23
 24	original := map[string]int{
 25		"key1": 1,
 26		"key2": 2,
 27	}
 28
 29	m := NewMapFrom(original)
 30	require.NotNil(t, m)
 31	require.Equal(t, original, m.inner)
 32	require.Equal(t, 2, m.Len())
 33
 34	value, ok := m.Get("key1")
 35	require.True(t, ok)
 36	require.Equal(t, 1, value)
 37}
 38
 39func TestMap_Set(t *testing.T) {
 40	t.Parallel()
 41
 42	m := NewMap[string, int]()
 43
 44	m.Set("key1", 42)
 45	value, ok := m.Get("key1")
 46	require.True(t, ok)
 47	require.Equal(t, 42, value)
 48	require.Equal(t, 1, m.Len())
 49
 50	m.Set("key1", 100)
 51	value, ok = m.Get("key1")
 52	require.True(t, ok)
 53	require.Equal(t, 100, value)
 54	require.Equal(t, 1, m.Len())
 55}
 56
 57func TestMap_Get(t *testing.T) {
 58	t.Parallel()
 59
 60	m := NewMap[string, int]()
 61
 62	value, ok := m.Get("nonexistent")
 63	require.False(t, ok)
 64	require.Equal(t, 0, value)
 65
 66	m.Set("key1", 42)
 67	value, ok = m.Get("key1")
 68	require.True(t, ok)
 69	require.Equal(t, 42, value)
 70}
 71
 72func TestMap_Del(t *testing.T) {
 73	t.Parallel()
 74
 75	m := NewMap[string, int]()
 76	m.Set("key1", 42)
 77	m.Set("key2", 100)
 78
 79	require.Equal(t, 2, m.Len())
 80
 81	m.Del("key1")
 82	_, ok := m.Get("key1")
 83	require.False(t, ok)
 84	require.Equal(t, 1, m.Len())
 85
 86	value, ok := m.Get("key2")
 87	require.True(t, ok)
 88	require.Equal(t, 100, value)
 89
 90	m.Del("nonexistent")
 91	require.Equal(t, 1, m.Len())
 92}
 93
 94func TestMap_Len(t *testing.T) {
 95	t.Parallel()
 96
 97	m := NewMap[string, int]()
 98	require.Equal(t, 0, m.Len())
 99
100	m.Set("key1", 1)
101	require.Equal(t, 1, m.Len())
102
103	m.Set("key2", 2)
104	require.Equal(t, 2, m.Len())
105
106	m.Del("key1")
107	require.Equal(t, 1, m.Len())
108
109	m.Del("key2")
110	require.Equal(t, 0, m.Len())
111}
112
113func TestMap_Take(t *testing.T) {
114	t.Parallel()
115
116	m := NewMap[string, int]()
117	m.Set("key1", 42)
118	m.Set("key2", 100)
119
120	require.Equal(t, 2, m.Len())
121
122	value, ok := m.Take("key1")
123	require.True(t, ok)
124	require.Equal(t, 42, value)
125	require.Equal(t, 1, m.Len())
126
127	_, exists := m.Get("key1")
128	require.False(t, exists)
129
130	value, ok = m.Get("key2")
131	require.True(t, ok)
132	require.Equal(t, 100, value)
133}
134
135func TestMap_Take_NonexistentKey(t *testing.T) {
136	t.Parallel()
137
138	m := NewMap[string, int]()
139	m.Set("key1", 42)
140
141	value, ok := m.Take("nonexistent")
142	require.False(t, ok)
143	require.Equal(t, 0, value)
144	require.Equal(t, 1, m.Len())
145
146	value, ok = m.Get("key1")
147	require.True(t, ok)
148	require.Equal(t, 42, value)
149}
150
151func TestMap_Take_EmptyMap(t *testing.T) {
152	t.Parallel()
153
154	m := NewMap[string, int]()
155
156	value, ok := m.Take("key1")
157	require.False(t, ok)
158	require.Equal(t, 0, value)
159	require.Equal(t, 0, m.Len())
160}
161
162func TestMap_Take_SameKeyTwice(t *testing.T) {
163	t.Parallel()
164
165	m := NewMap[string, int]()
166	m.Set("key1", 42)
167
168	value, ok := m.Take("key1")
169	require.True(t, ok)
170	require.Equal(t, 42, value)
171	require.Equal(t, 0, m.Len())
172
173	value, ok = m.Take("key1")
174	require.False(t, ok)
175	require.Equal(t, 0, value)
176	require.Equal(t, 0, m.Len())
177}
178
179func TestMap_Seq2(t *testing.T) {
180	t.Parallel()
181
182	m := NewMap[string, int]()
183	m.Set("key1", 1)
184	m.Set("key2", 2)
185	m.Set("key3", 3)
186
187	collected := maps.Collect(m.Seq2())
188
189	require.Equal(t, 3, len(collected))
190	require.Equal(t, 1, collected["key1"])
191	require.Equal(t, 2, collected["key2"])
192	require.Equal(t, 3, collected["key3"])
193}
194
195func TestMap_Seq2_EarlyReturn(t *testing.T) {
196	t.Parallel()
197
198	m := NewMap[string, int]()
199	m.Set("key1", 1)
200	m.Set("key2", 2)
201	m.Set("key3", 3)
202
203	count := 0
204	for range m.Seq2() {
205		count++
206		if count == 2 {
207			break
208		}
209	}
210
211	require.Equal(t, 2, count)
212}
213
214func TestMap_Seq2_EmptyMap(t *testing.T) {
215	t.Parallel()
216
217	m := NewMap[string, int]()
218
219	count := 0
220	for range m.Seq2() {
221		count++
222	}
223
224	require.Equal(t, 0, count)
225}
226
227func TestMap_Seq(t *testing.T) {
228	t.Parallel()
229
230	m := NewMap[string, int]()
231	m.Set("key1", 1)
232	m.Set("key2", 2)
233	m.Set("key3", 3)
234
235	collected := make([]int, 0)
236	for v := range m.Seq() {
237		collected = append(collected, v)
238	}
239
240	require.Equal(t, 3, len(collected))
241	require.Contains(t, collected, 1)
242	require.Contains(t, collected, 2)
243	require.Contains(t, collected, 3)
244}
245
246func TestMap_Seq_EarlyReturn(t *testing.T) {
247	t.Parallel()
248
249	m := NewMap[string, int]()
250	m.Set("key1", 1)
251	m.Set("key2", 2)
252	m.Set("key3", 3)
253
254	count := 0
255	for range m.Seq() {
256		count++
257		if count == 2 {
258			break
259		}
260	}
261
262	require.Equal(t, 2, count)
263}
264
265func TestMap_Seq_EmptyMap(t *testing.T) {
266	t.Parallel()
267
268	m := NewMap[string, int]()
269
270	count := 0
271	for range m.Seq() {
272		count++
273	}
274
275	require.Equal(t, 0, count)
276}
277
278func TestMap_MarshalJSON(t *testing.T) {
279	t.Parallel()
280
281	m := NewMap[string, int]()
282	m.Set("key1", 1)
283	m.Set("key2", 2)
284
285	data, err := json.Marshal(m)
286	require.NoError(t, err)
287
288	result := &Map[string, int]{}
289	err = json.Unmarshal(data, result)
290	require.NoError(t, err)
291	require.Equal(t, 2, result.Len())
292	v1, _ := result.Get("key1")
293	v2, _ := result.Get("key2")
294	require.Equal(t, 1, v1)
295	require.Equal(t, 2, v2)
296}
297
298func TestMap_MarshalJSON_EmptyMap(t *testing.T) {
299	t.Parallel()
300
301	m := NewMap[string, int]()
302
303	data, err := json.Marshal(m)
304	require.NoError(t, err)
305	require.Equal(t, "{}", string(data))
306}
307
308func TestMap_UnmarshalJSON(t *testing.T) {
309	t.Parallel()
310
311	jsonData := `{"key1": 1, "key2": 2}`
312
313	m := NewMap[string, int]()
314	err := json.Unmarshal([]byte(jsonData), m)
315	require.NoError(t, err)
316
317	require.Equal(t, 2, m.Len())
318	value, ok := m.Get("key1")
319	require.True(t, ok)
320	require.Equal(t, 1, value)
321
322	value, ok = m.Get("key2")
323	require.True(t, ok)
324	require.Equal(t, 2, value)
325}
326
327func TestMap_UnmarshalJSON_EmptyJSON(t *testing.T) {
328	t.Parallel()
329
330	jsonData := `{}`
331
332	m := NewMap[string, int]()
333	err := json.Unmarshal([]byte(jsonData), m)
334	require.NoError(t, err)
335	require.Equal(t, 0, m.Len())
336}
337
338func TestMap_UnmarshalJSON_InvalidJSON(t *testing.T) {
339	t.Parallel()
340
341	jsonData := `{"key1": 1, "key2":}`
342
343	m := NewMap[string, int]()
344	err := json.Unmarshal([]byte(jsonData), m)
345	require.Error(t, err)
346}
347
348func TestMap_UnmarshalJSON_OverwritesExistingData(t *testing.T) {
349	t.Parallel()
350
351	m := NewMap[string, int]()
352	m.Set("existing", 999)
353
354	jsonData := `{"key1": 1, "key2": 2}`
355	err := json.Unmarshal([]byte(jsonData), m)
356	require.NoError(t, err)
357
358	require.Equal(t, 2, m.Len())
359	_, ok := m.Get("existing")
360	require.False(t, ok)
361
362	value, ok := m.Get("key1")
363	require.True(t, ok)
364	require.Equal(t, 1, value)
365}
366
367func TestMap_JSONRoundTrip(t *testing.T) {
368	t.Parallel()
369
370	original := NewMap[string, int]()
371	original.Set("key1", 1)
372	original.Set("key2", 2)
373	original.Set("key3", 3)
374
375	data, err := json.Marshal(original)
376	require.NoError(t, err)
377
378	restored := NewMap[string, int]()
379	err = json.Unmarshal(data, restored)
380	require.NoError(t, err)
381
382	require.Equal(t, original.Len(), restored.Len())
383
384	for k, v := range original.Seq2() {
385		restoredValue, ok := restored.Get(k)
386		require.True(t, ok)
387		require.Equal(t, v, restoredValue)
388	}
389}
390
391func TestMap_ConcurrentAccess(t *testing.T) {
392	t.Parallel()
393
394	m := NewMap[int, int]()
395	const numGoroutines = 100
396	const numOperations = 100
397
398	var wg sync.WaitGroup
399	wg.Add(numGoroutines)
400
401	for i := range numGoroutines {
402		go func(id int) {
403			defer wg.Done()
404			for j := range numOperations {
405				key := id*numOperations + j
406				m.Set(key, key*2)
407				value, ok := m.Get(key)
408				require.True(t, ok)
409				require.Equal(t, key*2, value)
410			}
411		}(i)
412	}
413
414	wg.Wait()
415
416	require.Equal(t, numGoroutines*numOperations, m.Len())
417}
418
419func TestMap_ConcurrentReadWrite(t *testing.T) {
420	t.Parallel()
421
422	m := NewMap[int, int]()
423	const numReaders = 50
424	const numWriters = 50
425	const numOperations = 100
426
427	for i := range 1000 {
428		m.Set(i, i)
429	}
430
431	var wg sync.WaitGroup
432	wg.Add(numReaders + numWriters)
433
434	for range numReaders {
435		go func() {
436			defer wg.Done()
437			for j := range numOperations {
438				key := j % 1000
439				value, ok := m.Get(key)
440				if ok {
441					require.Equal(t, key, value)
442				}
443				_ = m.Len()
444			}
445		}()
446	}
447
448	for i := range numWriters {
449		go func(id int) {
450			defer wg.Done()
451			for j := range numOperations {
452				key := 1000 + id*numOperations + j
453				m.Set(key, key)
454				if j%10 == 0 {
455					m.Del(key)
456				}
457			}
458		}(i)
459	}
460
461	wg.Wait()
462}
463
464func TestMap_ConcurrentSeq2(t *testing.T) {
465	t.Parallel()
466
467	m := NewMap[int, int]()
468	for i := range 100 {
469		m.Set(i, i*2)
470	}
471
472	var wg sync.WaitGroup
473	const numIterators = 10
474
475	wg.Add(numIterators)
476	for range numIterators {
477		go func() {
478			defer wg.Done()
479			count := 0
480			for k, v := range m.Seq2() {
481				require.Equal(t, k*2, v)
482				count++
483			}
484			require.Equal(t, 100, count)
485		}()
486	}
487
488	wg.Wait()
489}
490
491func TestMap_ConcurrentSeq(t *testing.T) {
492	t.Parallel()
493
494	m := NewMap[int, int]()
495	for i := range 100 {
496		m.Set(i, i*2)
497	}
498
499	var wg sync.WaitGroup
500	const numIterators = 10
501
502	wg.Add(numIterators)
503	for range numIterators {
504		go func() {
505			defer wg.Done()
506			count := 0
507			values := make(map[int]bool)
508			for v := range m.Seq() {
509				values[v] = true
510				count++
511			}
512			require.Equal(t, 100, count)
513			for i := range 100 {
514				require.True(t, values[i*2])
515			}
516		}()
517	}
518
519	wg.Wait()
520}
521
522func TestMap_ConcurrentTake(t *testing.T) {
523	t.Parallel()
524
525	m := NewMap[int, int]()
526	const numItems = 1000
527
528	for i := range numItems {
529		m.Set(i, i*2)
530	}
531
532	var wg sync.WaitGroup
533	const numWorkers = 10
534	taken := make([][]int, numWorkers)
535
536	wg.Add(numWorkers)
537	for i := range numWorkers {
538		go func(workerID int) {
539			defer wg.Done()
540			taken[workerID] = make([]int, 0)
541			for j := workerID; j < numItems; j += numWorkers {
542				if value, ok := m.Take(j); ok {
543					taken[workerID] = append(taken[workerID], value)
544				}
545			}
546		}(i)
547	}
548
549	wg.Wait()
550
551	require.Equal(t, 0, m.Len())
552
553	allTaken := make(map[int]bool)
554	for _, workerTaken := range taken {
555		for _, value := range workerTaken {
556			require.False(t, allTaken[value], "Value %d was taken multiple times", value)
557			allTaken[value] = true
558		}
559	}
560
561	require.Equal(t, numItems, len(allTaken))
562	for i := range numItems {
563		require.True(t, allTaken[i*2], "Expected value %d to be taken", i*2)
564	}
565}
566
567func TestMap_TypeSafety(t *testing.T) {
568	t.Parallel()
569
570	stringIntMap := NewMap[string, int]()
571	stringIntMap.Set("key", 42)
572	value, ok := stringIntMap.Get("key")
573	require.True(t, ok)
574	require.Equal(t, 42, value)
575
576	intStringMap := NewMap[int, string]()
577	intStringMap.Set(42, "value")
578	strValue, ok := intStringMap.Get(42)
579	require.True(t, ok)
580	require.Equal(t, "value", strValue)
581
582	structMap := NewMap[string, struct{ Name string }]()
583	structMap.Set("key", struct{ Name string }{Name: "test"})
584	structValue, ok := structMap.Get("key")
585	require.True(t, ok)
586	require.Equal(t, "test", structValue.Name)
587}
588
589func TestMap_InterfaceCompliance(t *testing.T) {
590	t.Parallel()
591
592	var _ json.Marshaler = &Map[string, any]{}
593	var _ json.Unmarshaler = &Map[string, any]{}
594}
595
596func BenchmarkMap_Set(b *testing.B) {
597	m := NewMap[int, int]()
598
599	for i := 0; b.Loop(); i++ {
600		m.Set(i, i*2)
601	}
602}
603
604func BenchmarkMap_Get(b *testing.B) {
605	m := NewMap[int, int]()
606	for i := range 1000 {
607		m.Set(i, i*2)
608	}
609
610	for i := 0; b.Loop(); i++ {
611		m.Get(i % 1000)
612	}
613}
614
615func BenchmarkMap_Seq2(b *testing.B) {
616	m := NewMap[int, int]()
617	for i := range 1000 {
618		m.Set(i, i*2)
619	}
620
621	for b.Loop() {
622		for range m.Seq2() {
623		}
624	}
625}
626
627func BenchmarkMap_Seq(b *testing.B) {
628	m := NewMap[int, int]()
629	for i := range 1000 {
630		m.Set(i, i*2)
631	}
632
633	for b.Loop() {
634		for range m.Seq() {
635		}
636	}
637}
638
639func BenchmarkMap_Take(b *testing.B) {
640	m := NewMap[int, int]()
641	for i := range 1000 {
642		m.Set(i, i*2)
643	}
644
645	b.ResetTimer()
646	for i := 0; b.Loop(); i++ {
647		key := i % 1000
648		m.Take(key)
649		if i%1000 == 999 {
650			b.StopTimer()
651			for j := range 1000 {
652				m.Set(j, j*2)
653			}
654			b.StartTimer()
655		}
656	}
657}
658
659func BenchmarkMap_ConcurrentReadWrite(b *testing.B) {
660	m := NewMap[int, int]()
661	for i := range 1000 {
662		m.Set(i, i*2)
663	}
664
665	b.ResetTimer()
666	b.RunParallel(func(pb *testing.PB) {
667		i := 0
668		for pb.Next() {
669			if i%2 == 0 {
670				m.Get(i % 1000)
671			} else {
672				m.Set(i+1000, i*2)
673			}
674			i++
675		}
676	})
677}