mirror of
https://github.com/charmbracelet/crush.git
synced 2025-08-02 05:20:46 +03:00
297 lines
5.7 KiB
Go
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())
|
|
})
|
|
}
|