test: test csync.Map

Carlos Alexandro Becker created

Change summary

internal/csync/maps_test.go | 450 +++++++++++++++++++++++++++++++++++++++
1 file changed, 450 insertions(+)

Detailed changes

internal/csync/maps_test.go 🔗

@@ -0,0 +1,450 @@
+package csync
+
+import (
+	"encoding/json"
+	"maps"
+	"sync"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewMap(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+	assert.NotNil(t, m)
+	assert.NotNil(t, m.inner)
+	assert.Equal(t, 0, m.Len())
+}
+
+func TestNewMapFrom(t *testing.T) {
+	t.Parallel()
+
+	original := map[string]int{
+		"key1": 1,
+		"key2": 2,
+	}
+
+	m := NewMapFrom(original)
+	assert.NotNil(t, m)
+	assert.Equal(t, original, m.inner)
+	assert.Equal(t, 2, m.Len())
+
+	value, ok := m.Get("key1")
+	assert.True(t, ok)
+	assert.Equal(t, 1, value)
+}
+
+func TestMap_Set(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+
+	m.Set("key1", 42)
+	value, ok := m.Get("key1")
+	assert.True(t, ok)
+	assert.Equal(t, 42, value)
+	assert.Equal(t, 1, m.Len())
+
+	m.Set("key1", 100)
+	value, ok = m.Get("key1")
+	assert.True(t, ok)
+	assert.Equal(t, 100, value)
+	assert.Equal(t, 1, m.Len())
+}
+
+func TestMap_Get(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+
+	value, ok := m.Get("nonexistent")
+	assert.False(t, ok)
+	assert.Equal(t, 0, value)
+
+	m.Set("key1", 42)
+	value, ok = m.Get("key1")
+	assert.True(t, ok)
+	assert.Equal(t, 42, value)
+}
+
+func TestMap_Del(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+	m.Set("key1", 42)
+	m.Set("key2", 100)
+
+	assert.Equal(t, 2, m.Len())
+
+	m.Del("key1")
+	_, ok := m.Get("key1")
+	assert.False(t, ok)
+	assert.Equal(t, 1, m.Len())
+
+	value, ok := m.Get("key2")
+	assert.True(t, ok)
+	assert.Equal(t, 100, value)
+
+	m.Del("nonexistent")
+	assert.Equal(t, 1, m.Len())
+}
+
+func TestMap_Len(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+	assert.Equal(t, 0, m.Len())
+
+	m.Set("key1", 1)
+	assert.Equal(t, 1, m.Len())
+
+	m.Set("key2", 2)
+	assert.Equal(t, 2, m.Len())
+
+	m.Del("key1")
+	assert.Equal(t, 1, m.Len())
+
+	m.Del("key2")
+	assert.Equal(t, 0, m.Len())
+}
+
+func TestMap_Seq2(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+	m.Set("key1", 1)
+	m.Set("key2", 2)
+	m.Set("key3", 3)
+
+	collected := maps.Collect(m.Seq2())
+
+	assert.Equal(t, 3, len(collected))
+	assert.Equal(t, 1, collected["key1"])
+	assert.Equal(t, 2, collected["key2"])
+	assert.Equal(t, 3, collected["key3"])
+}
+
+func TestMap_Seq2_EarlyReturn(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+	m.Set("key1", 1)
+	m.Set("key2", 2)
+	m.Set("key3", 3)
+
+	count := 0
+	for range m.Seq2() {
+		count++
+		if count == 2 {
+			break
+		}
+	}
+
+	assert.Equal(t, 2, count)
+}
+
+func TestMap_Seq2_EmptyMap(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+
+	count := 0
+	for range m.Seq2() {
+		count++
+	}
+
+	assert.Equal(t, 0, count)
+}
+
+func TestMap_MarshalJSON(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+	m.Set("key1", 1)
+	m.Set("key2", 2)
+
+	data, err := json.Marshal(m)
+	assert.NoError(t, err)
+
+	var result map[string]int
+	err = json.Unmarshal(data, &result)
+	assert.NoError(t, err)
+	assert.Equal(t, 2, len(result))
+	assert.Equal(t, 1, result["key1"])
+	assert.Equal(t, 2, result["key2"])
+}
+
+func TestMap_MarshalJSON_EmptyMap(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+
+	data, err := json.Marshal(m)
+	assert.NoError(t, err)
+	assert.Equal(t, "{}", string(data))
+}
+
+func TestMap_UnmarshalJSON(t *testing.T) {
+	t.Parallel()
+
+	jsonData := `{"key1": 1, "key2": 2}`
+
+	m := NewMap[string, int]()
+	err := json.Unmarshal([]byte(jsonData), m)
+	assert.NoError(t, err)
+
+	assert.Equal(t, 2, m.Len())
+	value, ok := m.Get("key1")
+	assert.True(t, ok)
+	assert.Equal(t, 1, value)
+
+	value, ok = m.Get("key2")
+	assert.True(t, ok)
+	assert.Equal(t, 2, value)
+}
+
+func TestMap_UnmarshalJSON_EmptyJSON(t *testing.T) {
+	t.Parallel()
+
+	jsonData := `{}`
+
+	m := NewMap[string, int]()
+	err := json.Unmarshal([]byte(jsonData), m)
+	assert.NoError(t, err)
+	assert.Equal(t, 0, m.Len())
+}
+
+func TestMap_UnmarshalJSON_InvalidJSON(t *testing.T) {
+	t.Parallel()
+
+	jsonData := `{"key1": 1, "key2":}`
+
+	m := NewMap[string, int]()
+	err := json.Unmarshal([]byte(jsonData), m)
+	assert.Error(t, err)
+}
+
+func TestMap_UnmarshalJSON_OverwritesExistingData(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[string, int]()
+	m.Set("existing", 999)
+
+	jsonData := `{"key1": 1, "key2": 2}`
+	err := json.Unmarshal([]byte(jsonData), m)
+	assert.NoError(t, err)
+
+	assert.Equal(t, 2, m.Len())
+	_, ok := m.Get("existing")
+	assert.False(t, ok)
+
+	value, ok := m.Get("key1")
+	assert.True(t, ok)
+	assert.Equal(t, 1, value)
+}
+
+func TestMap_JSONRoundTrip(t *testing.T) {
+	t.Parallel()
+
+	original := NewMap[string, int]()
+	original.Set("key1", 1)
+	original.Set("key2", 2)
+	original.Set("key3", 3)
+
+	data, err := json.Marshal(original)
+	assert.NoError(t, err)
+
+	restored := NewMap[string, int]()
+	err = json.Unmarshal(data, restored)
+	assert.NoError(t, err)
+
+	assert.Equal(t, original.Len(), restored.Len())
+
+	for k, v := range original.Seq2() {
+		restoredValue, ok := restored.Get(k)
+		assert.True(t, ok)
+		assert.Equal(t, v, restoredValue)
+	}
+}
+
+func TestMap_ConcurrentAccess(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[int, int]()
+	const numGoroutines = 100
+	const numOperations = 100
+
+	var wg sync.WaitGroup
+	wg.Add(numGoroutines)
+
+	for i := range numGoroutines {
+		go func(id int) {
+			defer wg.Done()
+			for j := range numOperations {
+				key := id*numOperations + j
+				m.Set(key, key*2)
+				value, ok := m.Get(key)
+				assert.True(t, ok)
+				assert.Equal(t, key*2, value)
+			}
+		}(i)
+	}
+
+	wg.Wait()
+
+	assert.Equal(t, numGoroutines*numOperations, m.Len())
+}
+
+func TestMap_ConcurrentReadWrite(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[int, int]()
+	const numReaders = 50
+	const numWriters = 50
+	const numOperations = 100
+
+	for i := range 1000 {
+		m.Set(i, i)
+	}
+
+	var wg sync.WaitGroup
+	wg.Add(numReaders + numWriters)
+
+	for range numReaders {
+		go func() {
+			defer wg.Done()
+			for j := range numOperations {
+				key := j % 1000
+				value, ok := m.Get(key)
+				if ok {
+					assert.Equal(t, key, value)
+				}
+				_ = m.Len()
+			}
+		}()
+	}
+
+	for i := range numWriters {
+		go func(id int) {
+			defer wg.Done()
+			for j := range numOperations {
+				key := 1000 + id*numOperations + j
+				m.Set(key, key)
+				if j%10 == 0 {
+					m.Del(key)
+				}
+			}
+		}(i)
+	}
+
+	wg.Wait()
+}
+
+func TestMap_ConcurrentSeq2(t *testing.T) {
+	t.Parallel()
+
+	m := NewMap[int, int]()
+	for i := range 100 {
+		m.Set(i, i*2)
+	}
+
+	var wg sync.WaitGroup
+	const numIterators = 10
+
+	wg.Add(numIterators)
+	for range numIterators {
+		go func() {
+			defer wg.Done()
+			count := 0
+			for k, v := range m.Seq2() {
+				assert.Equal(t, k*2, v)
+				count++
+			}
+			assert.Equal(t, 100, count)
+		}()
+	}
+
+	wg.Wait()
+}
+
+func TestMap_TypeSafety(t *testing.T) {
+	t.Parallel()
+
+	stringIntMap := NewMap[string, int]()
+	stringIntMap.Set("key", 42)
+	value, ok := stringIntMap.Get("key")
+	assert.True(t, ok)
+	assert.Equal(t, 42, value)
+
+	intStringMap := NewMap[int, string]()
+	intStringMap.Set(42, "value")
+	strValue, ok := intStringMap.Get(42)
+	assert.True(t, ok)
+	assert.Equal(t, "value", strValue)
+
+	structMap := NewMap[string, struct{ Name string }]()
+	structMap.Set("key", struct{ Name string }{Name: "test"})
+	structValue, ok := structMap.Get("key")
+	assert.True(t, ok)
+	assert.Equal(t, "test", structValue.Name)
+}
+
+func TestMap_InterfaceCompliance(t *testing.T) {
+	t.Parallel()
+
+	var _ json.Marshaler = &Map[string, any]{}
+	var _ json.Unmarshaler = &Map[string, any]{}
+}
+
+func BenchmarkMap_Set(b *testing.B) {
+	m := NewMap[int, int]()
+
+	for i := 0; b.Loop(); i++ {
+		m.Set(i, i*2)
+	}
+}
+
+func BenchmarkMap_Get(b *testing.B) {
+	m := NewMap[int, int]()
+	for i := range 1000 {
+		m.Set(i, i*2)
+	}
+
+	for i := 0; b.Loop(); i++ {
+		m.Get(i % 1000)
+	}
+}
+
+func BenchmarkMap_Seq2(b *testing.B) {
+	m := NewMap[int, int]()
+	for i := range 1000 {
+		m.Set(i, i*2)
+	}
+
+	for b.Loop() {
+		for range m.Seq2() {
+		}
+	}
+}
+
+func BenchmarkMap_ConcurrentReadWrite(b *testing.B) {
+	m := NewMap[int, int]()
+	for i := range 1000 {
+		m.Set(i, i*2)
+	}
+
+	b.ResetTimer()
+	b.RunParallel(func(pb *testing.PB) {
+		i := 0
+		for pb.Next() {
+			if i%2 == 0 {
+				m.Get(i % 1000)
+			} else {
+				m.Set(i+1000, i*2)
+			}
+			i++
+		}
+	})
+}