Files
crush-code-agent-ide/internal/csync/slices_test.go
Carlos Alexandro Becker 61ea243e48 feat: slices as well
2025-07-25 12:07:58 -03:00

297 lines
5.7 KiB
Go

package csync
import (
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLazySlice_Seq(t *testing.T) {
t.Parallel()
data := []string{"a", "b", "c"}
s := NewLazySlice(func() []string {
// TODO: use synctest when new Go is out.
time.Sleep(10 * time.Millisecond) // Small delay to ensure loading happens
return data
})
var result []string
for v := range s.Seq() {
result = append(result, v)
}
assert.Equal(t, data, result)
}
func TestLazySlice_SeqWaitsForLoading(t *testing.T) {
t.Parallel()
var loaded atomic.Bool
data := []string{"x", "y", "z"}
s := NewLazySlice(func() []string {
// TODO: use synctest when new Go is out.
time.Sleep(100 * time.Millisecond)
loaded.Store(true)
return data
})
assert.False(t, loaded.Load(), "should not be loaded immediately")
var result []string
for v := range s.Seq() {
result = append(result, v)
}
assert.True(t, loaded.Load(), "should be loaded after Seq")
assert.Equal(t, data, result)
}
func TestLazySlice_EmptySlice(t *testing.T) {
t.Parallel()
s := NewLazySlice(func() []string {
return []string{}
})
var result []string
for v := range s.Seq() {
result = append(result, v)
}
assert.Empty(t, result)
}
func TestLazySlice_EarlyBreak(t *testing.T) {
t.Parallel()
data := []string{"a", "b", "c", "d", "e"}
s := NewLazySlice(func() []string {
// TODO: use synctest when new Go is out.
time.Sleep(10 * time.Millisecond) // Small delay to ensure loading happens
return data
})
var result []string
for v := range s.Seq() {
result = append(result, v)
if len(result) == 2 {
break
}
}
assert.Equal(t, []string{"a", "b"}, result)
}
func TestSlice(t *testing.T) {
t.Run("NewSlice", func(t *testing.T) {
s := NewSlice[int]()
assert.Equal(t, 0, s.Len())
})
t.Run("NewSliceFrom", func(t *testing.T) {
original := []int{1, 2, 3}
s := NewSliceFrom(original)
assert.Equal(t, 3, s.Len())
// Verify it's a copy, not a reference
original[0] = 999
val, ok := s.Get(0)
require.True(t, ok)
assert.Equal(t, 1, val)
})
t.Run("Append", func(t *testing.T) {
s := NewSlice[string]()
s.Append("hello")
s.Append("world")
assert.Equal(t, 2, s.Len())
val, ok := s.Get(0)
require.True(t, ok)
assert.Equal(t, "hello", val)
val, ok = s.Get(1)
require.True(t, ok)
assert.Equal(t, "world", val)
})
t.Run("Prepend", func(t *testing.T) {
s := NewSlice[string]()
s.Append("world")
s.Prepend("hello")
assert.Equal(t, 2, s.Len())
val, ok := s.Get(0)
require.True(t, ok)
assert.Equal(t, "hello", val)
val, ok = s.Get(1)
require.True(t, ok)
assert.Equal(t, "world", val)
})
t.Run("Delete", func(t *testing.T) {
s := NewSliceFrom([]int{1, 2, 3, 4, 5})
// Delete middle element
ok := s.Delete(2)
assert.True(t, ok)
assert.Equal(t, 4, s.Len())
expected := []int{1, 2, 4, 5}
actual := s.Slice()
assert.Equal(t, expected, actual)
// Delete out of bounds
ok = s.Delete(10)
assert.False(t, ok)
assert.Equal(t, 4, s.Len())
// Delete negative index
ok = s.Delete(-1)
assert.False(t, ok)
assert.Equal(t, 4, s.Len())
})
t.Run("Get", func(t *testing.T) {
s := NewSliceFrom([]string{"a", "b", "c"})
val, ok := s.Get(1)
require.True(t, ok)
assert.Equal(t, "b", val)
// Out of bounds
_, ok = s.Get(10)
assert.False(t, ok)
// Negative index
_, ok = s.Get(-1)
assert.False(t, ok)
})
t.Run("Set", func(t *testing.T) {
s := NewSliceFrom([]string{"a", "b", "c"})
ok := s.Set(1, "modified")
assert.True(t, ok)
val, ok := s.Get(1)
require.True(t, ok)
assert.Equal(t, "modified", val)
// Out of bounds
ok = s.Set(10, "invalid")
assert.False(t, ok)
// Negative index
ok = s.Set(-1, "invalid")
assert.False(t, ok)
})
t.Run("SetSlice", func(t *testing.T) {
s := NewSlice[int]()
s.Append(1)
s.Append(2)
newItems := []int{10, 20, 30}
s.SetSlice(newItems)
assert.Equal(t, 3, s.Len())
assert.Equal(t, newItems, s.Slice())
// Verify it's a copy
newItems[0] = 999
val, ok := s.Get(0)
require.True(t, ok)
assert.Equal(t, 10, val)
})
t.Run("Clear", func(t *testing.T) {
s := NewSliceFrom([]int{1, 2, 3})
assert.Equal(t, 3, s.Len())
s.Clear()
assert.Equal(t, 0, s.Len())
})
t.Run("Slice", func(t *testing.T) {
original := []int{1, 2, 3}
s := NewSliceFrom(original)
copy := s.Slice()
assert.Equal(t, original, copy)
// Verify it's a copy
copy[0] = 999
val, ok := s.Get(0)
require.True(t, ok)
assert.Equal(t, 1, val)
})
t.Run("Seq", func(t *testing.T) {
s := NewSliceFrom([]int{1, 2, 3})
var result []int
for v := range s.Seq() {
result = append(result, v)
}
assert.Equal(t, []int{1, 2, 3}, result)
})
t.Run("SeqWithIndex", func(t *testing.T) {
s := NewSliceFrom([]string{"a", "b", "c"})
var indices []int
var values []string
for i, v := range s.Seq2() {
indices = append(indices, i)
values = append(values, v)
}
assert.Equal(t, []int{0, 1, 2}, indices)
assert.Equal(t, []string{"a", "b", "c"}, values)
})
t.Run("ConcurrentAccess", func(t *testing.T) {
s := NewSlice[int]()
const numGoroutines = 100
const itemsPerGoroutine = 10
var wg sync.WaitGroup
// Concurrent appends
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(start int) {
defer wg.Done()
for j := 0; j < itemsPerGoroutine; j++ {
s.Append(start*itemsPerGoroutine + j)
}
}(i)
}
// Concurrent reads
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < itemsPerGoroutine; j++ {
s.Len() // Just read the length
}
}()
}
wg.Wait()
// Should have all items
assert.Equal(t, numGoroutines*itemsPerGoroutine, s.Len())
})
}