Files
go-utils/slice/slice_test.go
2025-10-11 19:53:28 +04:00

325 lines
7.7 KiB
Go

package slice
import (
"math/rand/v2"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCycle(t *testing.T) {
t.Run("cycles through items", func(t *testing.T) {
next := Cycle(1, 2, 3)
assert.Equal(t, 1, next())
assert.Equal(t, 2, next())
assert.Equal(t, 3, next())
assert.Equal(t, 1, next()) // wraps back to first
assert.Equal(t, 2, next())
assert.Equal(t, 3, next())
assert.Equal(t, 1, next()) // wraps again
})
t.Run("cycles through single item", func(t *testing.T) {
next := Cycle(42)
assert.Equal(t, 42, next())
assert.Equal(t, 42, next())
assert.Equal(t, 42, next())
})
t.Run("returns zero value for empty slice", func(t *testing.T) {
next := Cycle[int]()
assert.Equal(t, 0, next())
assert.Equal(t, 0, next())
assert.Equal(t, 0, next())
})
t.Run("works with string type", func(t *testing.T) {
next := Cycle("a", "b", "c")
assert.Equal(t, "a", next())
assert.Equal(t, "b", next())
assert.Equal(t, "c", next())
assert.Equal(t, "a", next())
})
t.Run("works with struct type", func(t *testing.T) {
type Point struct {
X, Y int
}
next := Cycle(
Point{1, 2},
Point{3, 4},
)
assert.Equal(t, Point{1, 2}, next())
assert.Equal(t, Point{3, 4}, next())
assert.Equal(t, Point{1, 2}, next())
})
t.Run("works with pointer type", func(t *testing.T) {
val1, val2 := 10, 20
next := Cycle(&val1, &val2)
assert.Equal(t, &val1, next())
assert.Equal(t, &val2, next())
assert.Equal(t, &val1, next())
})
t.Run("empty string slice returns empty string", func(t *testing.T) {
next := Cycle[string]()
assert.Empty(t, next())
assert.Empty(t, next())
})
t.Run("multiple cycles work correctly", func(t *testing.T) {
next := Cycle("x", "y")
// First cycle
assert.Equal(t, "x", next())
assert.Equal(t, "y", next())
// Second cycle
assert.Equal(t, "x", next())
assert.Equal(t, "y", next())
// Third cycle
assert.Equal(t, "x", next())
assert.Equal(t, "y", next())
})
t.Run("each function instance maintains its own state", func(t *testing.T) {
next1 := Cycle(1, 2, 3)
next2 := Cycle(1, 2, 3)
assert.Equal(t, 1, next1())
assert.Equal(t, 1, next2())
assert.Equal(t, 2, next1())
assert.Equal(t, 2, next2())
assert.Equal(t, 3, next1())
assert.Equal(t, 1, next1())
assert.Equal(t, 3, next2())
})
t.Run("works with boolean type", func(t *testing.T) {
next := Cycle(true, false)
assert.True(t, next())
assert.False(t, next())
assert.True(t, next())
assert.False(t, next())
})
t.Run("works with float type", func(t *testing.T) {
next := Cycle(1.1, 2.2, 3.3)
assert.InDelta(t, 1.1, next(), 0.001)
assert.InDelta(t, 2.2, next(), 0.001)
assert.InDelta(t, 3.3, next(), 0.001)
assert.InDelta(t, 1.1, next(), 0.001)
})
}
func TestRandomCycle(t *testing.T) {
t.Run("returns zero value for empty slice", func(t *testing.T) {
next := RandomCycle[int](nil)
assert.Equal(t, 0, next())
assert.Equal(t, 0, next())
assert.Equal(t, 0, next())
})
t.Run("returns same item for single item slice", func(t *testing.T) {
next := RandomCycle(nil, 42)
assert.Equal(t, 42, next())
assert.Equal(t, 42, next())
assert.Equal(t, 42, next())
})
t.Run("cycles through all items with seeded random", func(t *testing.T) {
seed := rand.NewPCG(1, 2)
r := rand.New(seed)
next := RandomCycle(r, "a", "b", "c")
// Collect items to verify all are returned
seen := make(map[string]bool)
for range 100 {
item := next()
seen[item] = true
}
// All items should have been seen
assert.True(t, seen["a"], "should see 'a'")
assert.True(t, seen["b"], "should see 'b'")
assert.True(t, seen["c"], "should see 'c'")
})
t.Run("works with seeded random generator", func(t *testing.T) {
// Using same seed should produce same sequence
seed1 := rand.NewPCG(42, 42)
r1 := rand.New(seed1)
next1 := RandomCycle(r1, 1, 2, 3)
seed2 := rand.NewPCG(42, 42)
r2 := rand.New(seed2)
next2 := RandomCycle(r2, 1, 2, 3)
// First few calls should match
for range 10 {
assert.Equal(t, next1(), next2(), "calls with same seed should match")
}
})
t.Run("creates own random generator when nil provided", func(t *testing.T) {
next := RandomCycle[int](nil, 1, 2, 3)
// Should not panic and should return valid values
for range 10 {
val := next()
assert.Contains(t, []int{1, 2, 3}, val)
}
})
t.Run("eventually returns all items in cycle", func(t *testing.T) {
seed := rand.NewPCG(123, 456)
r := rand.New(seed)
next := RandomCycle(r, 1, 2, 3, 4, 5)
// Track items seen in current "window"
for range 5 {
seen := make(map[int]int)
// Collect enough items to ensure we see at least one full cycle
for range 10 {
item := next()
seen[item]++
assert.Contains(t, []int{1, 2, 3, 4, 5}, item)
}
// Should see multiple items
assert.GreaterOrEqual(t, len(seen), 3, "should see at least 3 different items")
}
})
t.Run("works with string type", func(t *testing.T) {
seed := rand.NewPCG(1, 2)
r := rand.New(seed)
next := RandomCycle(r, "x", "y", "z")
seen := make(map[string]bool)
for range 50 {
item := next()
seen[item] = true
assert.Contains(t, []string{"x", "y", "z"}, item)
}
assert.True(t, seen["x"])
assert.True(t, seen["y"])
assert.True(t, seen["z"])
})
t.Run("works with struct type", func(t *testing.T) {
type Item struct {
ID int
}
seed := rand.NewPCG(1, 2)
r := rand.New(seed)
next := RandomCycle(r, Item{1}, Item{2}, Item{3})
seen := make(map[int]bool)
for range 50 {
item := next()
seen[item.ID] = true
}
assert.True(t, seen[1])
assert.True(t, seen[2])
assert.True(t, seen[3])
})
t.Run("each function instance maintains its own state", func(t *testing.T) {
seed1 := rand.NewPCG(1, 2)
r1 := rand.New(seed1)
next1 := RandomCycle(r1, 1, 2, 3)
seed2 := rand.NewPCG(3, 5)
r2 := rand.New(seed2)
next2 := RandomCycle(r2, 1, 2, 3)
// Get a few values from each
vals1 := []int{next1(), next1(), next1()}
vals2 := []int{next2(), next2(), next2()}
// They should be different (very high probability with different seeds)
assert.NotEqual(t, vals1, vals2, "different seeds should produce different sequences")
})
t.Run("with two items", func(t *testing.T) {
seed := rand.NewPCG(99, 100)
r := rand.New(seed)
next := RandomCycle(r, "a", "b")
seen := make(map[string]bool)
for range 20 {
item := next()
seen[item] = true
assert.Contains(t, []string{"a", "b"}, item)
}
assert.True(t, seen["a"])
assert.True(t, seen["b"])
})
t.Run("deterministic with same seed across multiple cycles", func(t *testing.T) {
// First run
seed1 := rand.NewPCG(777, 888)
r1 := rand.New(seed1)
next1 := RandomCycle(r1, 10, 20, 30)
sequence1 := make([]int, 20)
for i := range 20 {
sequence1[i] = next1()
}
// Second run with same seed
seed2 := rand.NewPCG(777, 888)
r2 := rand.New(seed2)
next2 := RandomCycle(r2, 10, 20, 30)
sequence2 := make([]int, 20)
for i := range 20 {
sequence2[i] = next2()
}
assert.Equal(t, sequence1, sequence2, "same seed should produce same sequence")
})
t.Run("empty string slice returns empty string", func(t *testing.T) {
next := RandomCycle[string](nil)
assert.Empty(t, next())
assert.Empty(t, next())
})
t.Run("large number of items", func(t *testing.T) {
items := make([]int, 100)
for i := range 100 {
items[i] = i
}
seed := rand.NewPCG(42, 43)
r := rand.New(seed)
next := RandomCycle(r, items...)
seen := make(map[int]bool)
// Call enough times to likely see all items
for range 1000 {
item := next()
seen[item] = true
assert.GreaterOrEqual(t, item, 0)
assert.LessOrEqual(t, item, 99)
}
// Should see a good variety of items
assert.Greater(t, len(seen), 90, "should see most items with 1000 calls")
})
}