1package csync
2
3import (
4 "slices"
5 "sync"
6 "sync/atomic"
7 "testing"
8 "testing/synctest"
9 "time"
10
11 "github.com/stretchr/testify/require"
12)
13
14func TestLazySlice_Seq(t *testing.T) {
15 t.Parallel()
16
17 synctest.Test(t, func(t *testing.T) {
18 t.Helper()
19 data := []string{"a", "b", "c"}
20 s := NewLazySlice(func() []string {
21 time.Sleep(10 * time.Millisecond) // Small delay to ensure loading happens
22 return data
23 })
24 require.Equal(t, data, slices.Collect(s.Seq()))
25 })
26}
27
28func TestLazySlice_SeqWaitsForLoading(t *testing.T) {
29 t.Parallel()
30 synctest.Test(t, func(t *testing.T) {
31 t.Helper()
32
33 var loaded atomic.Bool
34 data := []string{"x", "y", "z"}
35
36 s := NewLazySlice(func() []string {
37 time.Sleep(100 * time.Millisecond)
38 loaded.Store(true)
39 return data
40 })
41
42 require.False(t, loaded.Load(), "should not be loaded immediately")
43 require.Equal(t, data, slices.Collect(s.Seq()))
44 require.True(t, loaded.Load(), "should be loaded after Seq")
45 })
46}
47
48func TestLazySlice_EmptySlice(t *testing.T) {
49 t.Parallel()
50 s := NewLazySlice(func() []string {
51 return []string{}
52 })
53 require.Empty(t, slices.Collect(s.Seq()))
54}
55
56func TestLazySlice_EarlyBreak(t *testing.T) {
57 t.Parallel()
58
59 synctest.Test(t, func(t *testing.T) {
60 t.Helper()
61 data := []string{"a", "b", "c", "d", "e"}
62 s := NewLazySlice(func() []string {
63 time.Sleep(10 * time.Millisecond) // Small delay to ensure loading happens
64 return data
65 })
66
67 var result []string
68 for v := range s.Seq() {
69 result = append(result, v)
70 if len(result) == 2 {
71 break
72 }
73 }
74
75 require.Equal(t, []string{"a", "b"}, result)
76 })
77}
78
79func TestSlice(t *testing.T) {
80 t.Run("NewSlice", func(t *testing.T) {
81 s := NewSlice[int]()
82 require.Equal(t, 0, s.Len())
83 })
84
85 t.Run("NewSliceFrom", func(t *testing.T) {
86 original := []int{1, 2, 3}
87 s := NewSliceFrom(original)
88 require.Equal(t, 3, s.Len())
89
90 // Verify it's a copy, not a reference
91 original[0] = 999
92 val, ok := s.Get(0)
93 require.True(t, ok)
94 require.Equal(t, 1, val)
95 })
96
97 t.Run("Append", func(t *testing.T) {
98 s := NewSlice[string]()
99 s.Append("hello")
100 s.Append("world")
101
102 require.Equal(t, 2, s.Len())
103 val, ok := s.Get(0)
104 require.True(t, ok)
105 require.Equal(t, "hello", val)
106
107 val, ok = s.Get(1)
108 require.True(t, ok)
109 require.Equal(t, "world", val)
110 })
111
112 t.Run("Prepend", func(t *testing.T) {
113 s := NewSlice[string]()
114 s.Append("world")
115 s.Prepend("hello")
116
117 require.Equal(t, 2, s.Len())
118 val, ok := s.Get(0)
119 require.True(t, ok)
120 require.Equal(t, "hello", val)
121
122 val, ok = s.Get(1)
123 require.True(t, ok)
124 require.Equal(t, "world", val)
125 })
126
127 t.Run("Delete", func(t *testing.T) {
128 s := NewSliceFrom([]int{1, 2, 3, 4, 5})
129
130 // Delete middle element
131 ok := s.Delete(2)
132 require.True(t, ok)
133 require.Equal(t, 4, s.Len())
134
135 expected := []int{1, 2, 4, 5}
136 actual := slices.Collect(s.Seq())
137 require.Equal(t, expected, actual)
138
139 // Delete out of bounds
140 ok = s.Delete(10)
141 require.False(t, ok)
142 require.Equal(t, 4, s.Len())
143
144 // Delete negative index
145 ok = s.Delete(-1)
146 require.False(t, ok)
147 require.Equal(t, 4, s.Len())
148 })
149
150 t.Run("Get", func(t *testing.T) {
151 s := NewSliceFrom([]string{"a", "b", "c"})
152
153 val, ok := s.Get(1)
154 require.True(t, ok)
155 require.Equal(t, "b", val)
156
157 // Out of bounds
158 _, ok = s.Get(10)
159 require.False(t, ok)
160
161 // Negative index
162 _, ok = s.Get(-1)
163 require.False(t, ok)
164 })
165
166 t.Run("Set", func(t *testing.T) {
167 s := NewSliceFrom([]string{"a", "b", "c"})
168
169 ok := s.Set(1, "modified")
170 require.True(t, ok)
171
172 val, ok := s.Get(1)
173 require.True(t, ok)
174 require.Equal(t, "modified", val)
175
176 // Out of bounds
177 ok = s.Set(10, "invalid")
178 require.False(t, ok)
179
180 // Negative index
181 ok = s.Set(-1, "invalid")
182 require.False(t, ok)
183 })
184
185 t.Run("SetSlice", func(t *testing.T) {
186 s := NewSlice[int]()
187 s.Append(1)
188 s.Append(2)
189
190 newItems := []int{10, 20, 30}
191 s.SetSlice(newItems)
192
193 require.Equal(t, 3, s.Len())
194 require.Equal(t, newItems, slices.Collect(s.Seq()))
195
196 // Verify it's a copy
197 newItems[0] = 999
198 val, ok := s.Get(0)
199 require.True(t, ok)
200 require.Equal(t, 10, val)
201 })
202
203 t.Run("Slice", func(t *testing.T) {
204 original := []int{1, 2, 3}
205 s := NewSliceFrom(original)
206
207 copied := slices.Collect(s.Seq())
208 require.Equal(t, original, copied)
209
210 // Verify it's a copy
211 copied[0] = 999
212 val, ok := s.Get(0)
213 require.True(t, ok)
214 require.Equal(t, 1, val)
215 })
216
217 t.Run("Seq", func(t *testing.T) {
218 s := NewSliceFrom([]int{1, 2, 3})
219
220 var result []int
221 for v := range s.Seq() {
222 result = append(result, v)
223 }
224
225 require.Equal(t, []int{1, 2, 3}, result)
226 })
227
228 t.Run("SeqWithIndex", func(t *testing.T) {
229 s := NewSliceFrom([]string{"a", "b", "c"})
230
231 var indices []int
232 var values []string
233 for i, v := range s.Seq2() {
234 indices = append(indices, i)
235 values = append(values, v)
236 }
237
238 require.Equal(t, []int{0, 1, 2}, indices)
239 require.Equal(t, []string{"a", "b", "c"}, values)
240 })
241
242 t.Run("ConcurrentAccess", func(t *testing.T) {
243 s := NewSlice[int]()
244 const numGoroutines = 100
245 const itemsPerGoroutine = 10
246
247 var wg sync.WaitGroup
248
249 // Concurrent appends
250 for i := range numGoroutines {
251 wg.Add(2)
252 go func(start int) {
253 defer wg.Done()
254 for j := range itemsPerGoroutine {
255 s.Append(start*itemsPerGoroutine + j)
256 }
257 }(i)
258 go func() {
259 defer wg.Done()
260 for range itemsPerGoroutine {
261 s.Len() // Just read the length
262 }
263 }()
264 }
265
266 wg.Wait()
267
268 // Should have all items
269 require.Equal(t, numGoroutines*itemsPerGoroutine, s.Len())
270 })
271}