Skip to content

Commit

Permalink
add UniqueValues option to random.Slice to enable slice creation with…
Browse files Browse the repository at this point in the history
… unique values
  • Loading branch information
adamluzsi committed Jul 23, 2024
1 parent 5088462 commit 7c09026
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 15 deletions.
61 changes: 57 additions & 4 deletions random/Make.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,46 @@ func (r *Random) Make(T any) any {
return r.Factory.Make(r, T)
}

func Slice[T any](length int, mk func() T) []T {
func Slice[T any](length int, mk func() T, opts ...sliceOption) []T {
var c sliceConfig
c.use(opts)
var vs []T
for i := 0; i < length; i++ {
vs = append(vs, mk())
var v T
if c.Unique {
v = Unique(mk, vs...)
} else {
v = mk()
}
vs = append(vs, v)
}
return vs
}

func Map[K comparable, V any](length int, mk func() (K, V)) map[K]V {
func Map[K comparable, V any](length int, mk func() (K, V), opts ...mapOption) map[K]V {
var c mapConfig
c.use(opts)
var (
vs = make(map[K]V)
collisionRetries = 42
)
for i := 0; i < length; i++ {
k, v := mk()
var (
k K
v V
)
if c.Unique {
var vals []V
for _, val := range vs {
vals = append(vals, val)
}
Unique(func() V {
k, v = mk()
return v
}, vals...)
} else {
k, v = mk()
}
if _, ok := vs[k]; ok {
if 0 < collisionRetries {
collisionRetries--
Expand All @@ -36,3 +61,31 @@ func KV[K comparable, V any](mkK func() K, mkV func() V) func() (K, V) {
return mkK(), mkV()
}
}

type sliceConfig struct {
Unique bool
}

func (c *sliceConfig) use(opts []sliceOption) {
for _, opt := range opts {
opt.sliceOption(c)
}
}

type sliceOption interface {
sliceOption(*sliceConfig)
}

type mapConfig struct {
Unique bool
}

func (c *mapConfig) use(opts []mapOption) {
for _, opt := range opts {
opt.mapOption(c)
}
}

type mapOption interface {
mapOption(*mapConfig)
}
47 changes: 47 additions & 0 deletions random/Make_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,25 @@ func TestSlice_smoke(t *testing.T) {
})
}

func TestSlice_withUniqueValues(t *testing.T) {
t.Run("without flag", func(t *testing.T) {
assert.Eventually(t, 10*time.Second, func(t assert.It) {
got := random.Slice[string](100, func() string {
return rnd.StringNC(3, random.CharsetDigit())
})
assert.NotUnique(t, got)
})
})
t.Run("with flag", func(t *testing.T) {
rnd.Repeat(3, 7, func() {
got := random.Slice[string](100, func() string {
return rnd.StringNC(3, random.CharsetDigit())
}, random.UniqueValues)
assert.Unique(t, got)
})
})
}

func TestMap_smoke(t *testing.T) {
it := assert.MakeIt(t)
eventually := assert.MakeRetry(5 * time.Second)
Expand Down Expand Up @@ -348,6 +367,34 @@ func TestMap_whenNotEnoughUniqueKeyCanBeGenerated_thenItReturnsWithLess(t *testi
})
}

func TestMap_withUniqueValues(t *testing.T) {
values := func(m map[int]string) []string {
var vs []string
for _, v := range m {
vs = append(vs, v)
}
return vs
}
t.Run("without flag", func(t *testing.T) {
assert.Eventually(t, 10*time.Second, func(t assert.It) {
got := random.Map[int, string](100, func() (int, string) {
return rnd.Int(), rnd.StringNC(3, random.CharsetDigit())
})

assert.NotUnique(t, values(got))
})
})
t.Run("with flag", func(t *testing.T) {
rnd.Repeat(3, 7, func() {
got := random.Map[int, string](100, func() (int, string) {
return rnd.Int(), rnd.StringNC(3, random.CharsetDigit())
}, random.UniqueValues)

assert.Unique(t, values(got))
})
})
}

func TestRandom_Make_structWithAnyField(t *testing.T) {
type T struct {
V1 any
Expand Down
25 changes: 20 additions & 5 deletions random/Unique.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,9 @@ func Unique[T any](blk func() T, excludeList ...T) T {
ok bool = true
)
for _, excluded := range excludeList {
isEqual, err := reflects.DeepEqual(v, excluded)
if err != nil {
panic(err.Error())
}
if isEqual {
if eq(v, excluded) {
ok = false
break
}
}
if ok {
Expand All @@ -44,3 +41,21 @@ func Unique[T any](blk func() T, excludeList ...T) T {
}
panic("random.Unique failed to find a unique value")
}

func eq[T any](a, b T) bool {
isEqual, err := reflects.DeepEqual(a, b)
if err != nil {
panic(err.Error())
}
return isEqual
}

// UniqueValues is an option that used to express a desire for unique value generation with certain functions.
// For example if random.Slice receives the UniqueValues flag, then the created values will be guaranteed to be unique,
// unless it is not possible within a reasonable attempts using the provided value maker function.
const UniqueValues = flagUniqueValues(0)

type flagUniqueValues int

func (flagUniqueValues) sliceOption(c *sliceConfig) { c.Unique = true }
func (flagUniqueValues) mapOption(c *mapConfig) { c.Unique = true }
4 changes: 2 additions & 2 deletions random/Unique_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (
"go.llib.dev/testcase/sandbox"
)

var rnd = random.New(random.CryptoSeed{})

func ExampleUnique() {
// useful when you need random values which are not equal
rnd := random.New(random.CryptoSeed{})
v1 := rnd.Int()
v2 := random.Unique(rnd.Int, v1)
v3 := random.Unique(rnd.Int, v1, v2)
Expand All @@ -23,7 +24,6 @@ func ExampleUnique() {
}

func TestUnique(t *testing.T) {
rnd := random.New(random.CryptoSeed{})
t.Run("no exclude list given", func(t *testing.T) {
v := random.Unique(rnd.Int)
assert.NotEmpty(t, v)
Expand Down
20 changes: 16 additions & 4 deletions random/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,28 @@ func TestExampleRandomError(t *testing.T) {
})
}

func ExampleMakeSlice() {
func ExampleSlice() {
rnd := random.New(random.CryptoSeed{})
slice := random.Slice[int](3, rnd.Int)
pp.PP(slice) // []int slice with 3 values
}

pp.PP(random.Slice[int](3, rnd.Int)) // []int slice with 3 values
func ExampleSlice_withUniqueValues() {
rnd := random.New(random.CryptoSeed{})
slice := random.Slice[int](3, rnd.Int, random.UniqueValues)
pp.PP(slice) // []int slice with 3 values
}

func ExampleMakeMap() {
func ExampleMap() {
rnd := random.New(random.CryptoSeed{})
m := random.Map[string, int](3, random.KV(rnd.String, rnd.Int))
pp.PP(m) // map[string]int slice with 3 key-value pairs
}

pp.PP(random.Map[string, int](3, random.KV(rnd.String, rnd.Int))) // map[string]int slice with 3 key-value pairs
func ExampleMap_withUniqueValues() {
rnd := random.New(random.CryptoSeed{})
m := random.Map[string, int](3, random.KV(rnd.String, rnd.Int), random.UniqueValues)
pp.PP(m) // map[string]int slice with 3 key-value pairs
}

func ExampleRandom_Repeat() {
Expand Down

0 comments on commit 7c09026

Please sign in to comment.