slices_test.go

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