Skip to content

Commit

Permalink
Rework structure, add configuration options
Browse files Browse the repository at this point in the history
  • Loading branch information
lesiw committed Jan 2, 2025
1 parent 3786ab6 commit 015906e
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 61 deletions.
19 changes: 8 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,16 @@ import (
"lesiw.io/fill"
)

func FuzzTlsConfigClone(f *testing.F) {
func TestTlsConfigClone(f *testing.F) {
opts := cmp.Options{cmpopts.IgnoreUnexported(tls.Config{})}
f.Fuzz(func(t *testing.T, seed1, seed2 uint64) {
cfg1 := &tls.Config{}
fill.Rand(&cfg1, rand.New(rand.NewPCG(seed1, seed2)))
cfg2 := cfg1.Clone()
if !cmp.Equal(cfg1, cfg2, opts) {
t.Errorf("-original +cloned\n%s", cmp.Diff(cfg1, cfg2, opts))
for range 100 {
var cfg tls.Config
fill.Rand(&cfg)
if want, got := cfg, cfg.Clone(); !cmp.Equal(want, got, opts) {
t.Errorf("-original +cloned\n%s", cmp.Diff(want, got, opts))
}
})
}
}
```

[▶️ Run this example on the Go Playground](https://go.dev/play/p/TTx2CT85Ro3)

To run locally, `go test -fuzz=Fuzz -fuzztime=10s`.
[▶️ Run this example on the Go Playground](https://go.dev/play/p/PJCBSOH2VaO)
54 changes: 54 additions & 0 deletions filler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package fill

import "math/rand/v2"

// A Filler fills in random values.
type Filler struct {
MinSize int // Arbitrary lower bound for value sizes.
MaxSize int // Arbitrary upper bound for value sizes.
Runes []rune // Runes to select from when filling strings.
NeverNil bool // Never select nil for fillable values.
RandSource *rand.Rand // Custom rand source.
}

var Base64 = []rune{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', '+', '/',
}

func (f *Filler) intN(n int) int {
if n == 0 {
return 0
} else if f.RandSource == nil {
return rand.IntN(n)
} else {
return f.RandSource.IntN(n)
}
}

func (f *Filler) int64() int64 {
if f.RandSource == nil {
return rand.Int64()
} else {
return f.RandSource.Int64()
}
}

func (f *Filler) uint64() uint64 {
if f.RandSource == nil {
return rand.Uint64()
} else {
return f.RandSource.Uint64()
}
}

func (f *Filler) float64() float64 {
if f.RandSource == nil {
return rand.Float64()
} else {
return f.RandSource.Float64()
}
}
106 changes: 56 additions & 50 deletions rand.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,134 +2,140 @@ package fill

import (
"fmt"
"math/rand/v2"
"reflect"
"strings"
// math/rand/v2 should not be imported in this file.
)

// maxSize is an arbitrary upper bound on value length.
const maxSize = 16
var randFiller = Filler{MaxSize: 8, Runes: Base64}

// Rand fills a value with random data.
func Rand(a any, rng *rand.Rand) {
if rng == nil {
panic("bad parameter: nil rand.Rand")
}
func Rand(a any) { randFiller.Fill(a) }

func (f *Filler) Fill(a any) {
val := reflect.ValueOf(a)
if val.Kind() != reflect.Pointer {
panic("bad parameter: value to fill must be pointer")
}
randValue(val.Elem(), rng)
f.fillValue(val)
}

func randValue(val reflect.Value, rng *rand.Rand) {
func (f *Filler) fillValue(val reflect.Value) {
k := val.Kind()
switch {
case k == reflect.Bool:
val.SetBool(rng.IntN(2) == 0)
val.SetBool(f.intN(2) == 0)
case intKind(k):
val.SetInt(rng.Int64())
val.SetInt(f.int64())
case uintKind(k):
val.SetUint(rng.Uint64())
val.SetUint(f.uint64())
case floatKind(k):
val.SetFloat(rng.Float64())
val.SetFloat(f.float64())
case complexKind(k):
val.SetComplex(complex(rng.Float64(), rng.Float64()))
val.SetComplex(complex(f.float64(), f.float64()))
case k == reflect.Array:
randArray(val, rng)
f.fillArray(val)
case k == reflect.Chan:
randChan(val, rng)
f.fillChan(val)
case k == reflect.Func || k == reflect.Interface:
// Can't fill.
case k == reflect.Map:
randMap(val, rng)
f.fillMap(val)
case k == reflect.Pointer:
randPointer(val, rng)
f.fillPointer(val)
case k == reflect.Slice:
randSlice(val, rng)
f.fillSlice(val)
case k == reflect.String:
randString(val, rng)
f.fillString(val)
case k == reflect.Struct:
randStruct(val, rng)
f.fillStruct(val)
default:
panic(fmt.Sprintf("unhandled type: %s", val.Kind()))
}
}

func randArray(val reflect.Value, rng *rand.Rand) {
func (f *Filler) fillArray(val reflect.Value) {
for i := range val.Len() {
randValue(val.Index(i), rng)
f.fillValue(val.Index(i))
}
}

func randChan(val reflect.Value, rng *rand.Rand) {
if sz := rng.IntN(maxSize); sz == 0 {
func (f *Filler) fillChan(val reflect.Value) {
if sz := f.intN(f.MaxSize - f.MinSize); sz == 0 && !f.NeverNil {
val.SetZero() // nil
} else {
val.Set(reflect.MakeChan(val.Type(), sz-1))
val.Set(reflect.MakeChan(val.Type(),
f.MinSize+f.intN(f.MaxSize-f.MinSize)))
}
}

func randMap(val reflect.Value, rng *rand.Rand) {
sz := rng.IntN(maxSize)
if sz == 0 {
func (f *Filler) fillMap(val reflect.Value) {
sz := f.intN(f.MaxSize - f.MinSize)
if !f.NeverNil && sz == 0 {
val.SetZero() // nil
return
}
sz-- // Allow zero size.
sz = f.MinSize + f.intN(f.MaxSize-f.MinSize)
if val.IsZero() {
val.Set(reflect.MakeMapWithSize(val.Type(), sz))
} else {
val.Clear()
}
for range sz {
k := reflect.New(val.Type().Key()).Elem()
randValue(k, rng)
f.fillValue(k)
v := reflect.New(val.Type().Elem()).Elem()
randValue(v, rng)
f.fillValue(v)
val.SetMapIndex(k, v)
}
}

func randPointer(val reflect.Value, rng *rand.Rand) {
if rng.IntN(maxSize) == 0 {
func (f *Filler) fillPointer(val reflect.Value) {
if !f.NeverNil && f.intN(f.MaxSize-f.MinSize) == 0 {
val.SetZero() // nil
} else {
if val.IsZero() {
val.Set(reflect.New(val.Type().Elem()))
}
randValue(val.Elem(), rng)
f.fillValue(val.Elem())
}
}

func randSlice(val reflect.Value, rng *rand.Rand) {
sz := rng.IntN(maxSize)
if sz == 0 {
func (f *Filler) fillSlice(val reflect.Value) {
sz := f.intN(f.MaxSize - f.MinSize)
if !f.NeverNil && sz == 0 {
val.SetZero() // nil
return
}
sz-- // Allow zero size.
sz = f.MinSize + f.intN(f.MaxSize-f.MinSize)
for range sz {
e := reflect.New(val.Type().Elem()).Elem()
randValue(e, rng)
f.fillValue(e)
val.Set(reflect.Append(val, e))
}
}

func randString(val reflect.Value, rng *rand.Rand) {
sz := rng.IntN(maxSize)
b := make([]byte, sz)
for i := range sz {
b[i] = byte(rng.IntN(256))
func (f *Filler) fillString(val reflect.Value) {
sz := f.MinSize + f.intN(f.MaxSize-f.MinSize)
var b strings.Builder
if f.Runes == nil {
for range sz {
b.WriteByte(byte(f.intN(256)))
}
} else {
for range sz {
b.WriteRune(f.Runes[f.intN(len(f.Runes))])
}
}
val.SetString(string(b))
val.SetString(b.String())
}

func randStruct(val reflect.Value, rng *rand.Rand) {
func (f *Filler) fillStruct(val reflect.Value) {
for i := range val.NumField() {
f := val.Field(i)
if !f.CanSet() {
field := val.Field(i)
if !field.CanSet() {
continue
}
randValue(f, rng)
f.fillValue(field)
}
}
6 changes: 6 additions & 0 deletions zero.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package fill

var zeroFiller = Filler{NeverNil: true}

// Zero fills a value with zero-valued data.
func Zero(a any) { zeroFiller.Fill(a) }

0 comments on commit 015906e

Please sign in to comment.