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}