Skip to content

Commit

Permalink
refactor:tests: add overrides
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Pana <[email protected]>
  • Loading branch information
acpana committed Dec 11, 2024
1 parent dd2d99c commit 72649f1
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 70 deletions.
101 changes: 63 additions & 38 deletions pkg/test/fuzz/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ package fuzz

import (
"encoding/json"
"math/rand"
"reflect"
"strconv"
"testing"
)

Expand All @@ -38,14 +41,17 @@ type TestKRMType struct {
ComplexMapFieldPtr *map[string]NestedKRMType
NestedStruct NestedKRMType
NestedStructPtr *NestedKRMType
EnumField MyEnumType
EnumFieldPtr *MyEnumType
StringEnumField MyStringEnumType
StringEnumFieldPtr *MyStringEnumType
StructSlice []NestedKRMType
StructSlicePtr *[]NestedKRMType
IntSlice []int
IntSlicePtr *[]int
// EnumField MyEnumType
// EnumFieldPtr *MyEnumType
// StringEnumField MyStringEnumType
// StringEnumFieldPtr *MyStringEnumType
StructSlice []NestedKRMType
StructSlicePtr *[]NestedKRMType
IntSlice []int
IntSlicePtr *[]int

PtrStringFieldThatNeedsInt *string
StringFieldThatNeedsInt string
}

// Nested structs
Expand All @@ -66,35 +72,36 @@ type DeepNestedKRMType struct {
FieldCPtr *[]byte
}

// Integer enum type
type MyEnumType int
// // Integer enum type
// type MyEnumType int

const (
EnumValueA MyEnumType = iota
EnumValueB
EnumValueC
)
// const (
// EnumValueA MyEnumType = iota
// EnumValueB
// EnumValueC
// )

// String enum type
type MyStringEnumType string
// // String enum type
// type MyStringEnumType string

const (
StringEnumOptionA MyStringEnumType = "OptionA"
StringEnumOptionB MyStringEnumType = "OptionB"
StringEnumOptionC MyStringEnumType = "OptionC"
)
// const (
// StringEnumOptionA MyStringEnumType = "OptionA"
// StringEnumOptionB MyStringEnumType = "OptionB"
// StringEnumOptionC MyStringEnumType = "OptionC"
// )

func TestRandomFillerFields(t *testing.T) {
// Define allowable bounds for integer enums and specific values for non-integer enums
intEnumAllowableValues := map[string]int64{
"MyEnumType": 2, // Allows values in the range [0, 2]
}
stringEnumAllowableValues := map[string][]interface{}{
"MyStringEnumType": {"OptionA", "OptionB", "OptionC"},
stream := rand.New(rand.NewSource(int64(9201995))) // for determinism

funcF := func(t *testing.T, fieldName string, field reflect.Value) {
field.SetString(strconv.FormatInt(stream.Int63(), 10))
}

seed := int64(9201995) // for determinism
filler := NewRandomFiller(seed, intEnumAllowableValues, stringEnumAllowableValues)
overrides := map[string]OverrideFiller{
".PtrStringFieldThatNeedsInt": funcF,
".StringFieldThatNeedsInt": funcF,
}
filler := NewRandomFiller(&FillerConfig{Stream: stream, FieldOverrides: overrides})

tests := []struct {
name string
Expand All @@ -118,14 +125,14 @@ func TestRandomFillerFields(t *testing.T) {
{"ComplexMapFieldPtr", func(krmObj *TestKRMType) bool {
return krmObj.ComplexMapFieldPtr != nil && len(*krmObj.ComplexMapFieldPtr) > 0
}},
{"EnumField", func(krmObj *TestKRMType) bool { return krmObj.EnumField >= 0 && krmObj.EnumField <= 2 }},
{"EnumFieldPtr", func(krmObj *TestKRMType) bool {
return krmObj.EnumFieldPtr != nil && *krmObj.EnumFieldPtr >= 0 && *krmObj.EnumFieldPtr <= 2
}},
{"StringEnumField", func(krmObj *TestKRMType) bool { return krmObj.StringEnumField != "" }},
{"StringEnumFieldPtr", func(krmObj *TestKRMType) bool {
return krmObj.StringEnumFieldPtr != nil && *krmObj.StringEnumFieldPtr != ""
}},
// {"EnumField", func(krmObj *TestKRMType) bool { return krmObj.EnumField >= 0 && krmObj.EnumField <= 2 }},
// {"EnumFieldPtr", func(krmObj *TestKRMType) bool {
// return krmObj.EnumFieldPtr != nil && *krmObj.EnumFieldPtr >= 0 && *krmObj.EnumFieldPtr <= 2
// }},
// {"StringEnumField", func(krmObj *TestKRMType) bool { return krmObj.StringEnumField != "" }},
// {"StringEnumFieldPtr", func(krmObj *TestKRMType) bool {
// return krmObj.StringEnumFieldPtr != nil && *krmObj.StringEnumFieldPtr != ""
// }},
{"StructSlice", func(krmObj *TestKRMType) bool { return len(krmObj.StructSlice) > 0 }},
{"StructSlicePtr", func(krmObj *TestKRMType) bool { return krmObj.StructSlicePtr != nil && len(*krmObj.StructSlicePtr) > 0 }},
{"IntSlice", func(krmObj *TestKRMType) bool { return len(krmObj.IntSlice) > 0 }},
Expand Down Expand Up @@ -154,6 +161,24 @@ func TestRandomFillerFields(t *testing.T) {
{"NestedStruct.DeepNestedField.FieldCPtr", func(krmObj *TestKRMType) bool {
return krmObj.NestedStruct.DeepNestedField != nil && krmObj.NestedStruct.DeepNestedField.FieldCPtr != nil && len(*krmObj.NestedStruct.DeepNestedField.FieldCPtr) > 0
}},
{"PtrStringFieldThatNeedsInt", func(krmObj *TestKRMType) bool {
if krmObj.PtrStringFieldThatNeedsInt == nil {
return false
}
if _, err := strconv.ParseInt(*krmObj.PtrStringFieldThatNeedsInt, 10, 64); err != nil {
t.Error("converting string field to int")
}
return true
}},
{"StringFieldThatNeedsInt", func(krmObj *TestKRMType) bool {
if krmObj.PtrStringFieldThatNeedsInt == nil {
return false
}
if _, err := strconv.ParseInt(*krmObj.PtrStringFieldThatNeedsInt, 10, 64); err != nil {
t.Error("converting string field to int")
}
return true
}},
}

krmObj := &TestKRMType{}
Expand Down
62 changes: 30 additions & 32 deletions pkg/test/fuzz/krmgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,51 +20,54 @@ import (
"testing"
)

type OverrideFiller func(t *testing.T, fieldName string, field reflect.Value)
type RandomFiller struct {
randStream *rand.Rand

// for iota based enums, defines the upper bound for a named enum type
intEnumAllowableValues map[string]int64
// for non iota based enums, holds the set of allowable values for a named enum type
stringEnumAllowableValues map[string][]interface{}
fieldOverrides map[string]OverrideFiller
}

func NewRandomFiller(seed int64, enumBoundsMap map[string]int64, enumValuesMap map[string][]interface{}) *RandomFiller {
type FillerConfig struct {
Stream *rand.Rand
FieldOverrides map[string]OverrideFiller
}

func NewRandomFiller(fc *FillerConfig) *RandomFiller {
return &RandomFiller{
randStream: rand.New(rand.NewSource(seed)),
intEnumAllowableValues: enumBoundsMap,
stringEnumAllowableValues: enumValuesMap,
randStream: fc.Stream,
fieldOverrides: fc.FieldOverrides,
}
}

// Fill populates the fields of a struct with random values. Enums are handled separately in the
// two maps passed to the RandomFiller.
func (rf *RandomFiller) Fill(t *testing.T, obj interface{}) {
rf.fillWithRandom(t, reflect.ValueOf(obj).Elem())
rf.fillWithRandom(t, "", reflect.ValueOf(obj).Elem())
rf.fillWithRandom(t, "", reflect.ValueOf(obj).Elem())
}

func (rf *RandomFiller) fillWithRandom(t *testing.T, field reflect.Value) {
func (rf *RandomFiller) fillWithRandom(t *testing.T, fieldName string, field reflect.Value) {
if field.Kind() == reflect.Ptr {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
rf.fillWithRandom(t, field.Elem())
rf.fillWithRandom(t, fieldName, field.Elem())
return
}

if rf.fieldOverrides != nil {
if override, ok := rf.fieldOverrides[fieldName]; ok {
override(t, fieldName, field)
return
}
}

switch field.Kind() {
case reflect.Bool:
field.SetBool(rf.randStream.Intn(2) == 1)

case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
// if this field is an iota enum field with a set of allowable values
if upperBound, ok := rf.intEnumAllowableValues[field.Type().Name()]; ok {
// Select a random integer value within the range [0, upperBound] for integer enums
field.SetInt(rf.randStream.Int63n(upperBound + 1))
} else {
// Otherwise, fill with a generic random integer
field.SetInt(rf.randStream.Int63())
}
field.SetInt(rf.randStream.Int63())

case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
field.SetUint(rf.randStream.Uint64())
Expand All @@ -73,22 +76,14 @@ func (rf *RandomFiller) fillWithRandom(t *testing.T, field reflect.Value) {
field.SetFloat(rf.randStream.Float64())

case reflect.String:
// if this field is a enum field with a set of allowable values
if values, ok := rf.stringEnumAllowableValues[field.Type().Name()]; ok {
// Select a random string from predefined values in enumValuesMap
selectedValue := values[rf.randStream.Intn(len(values))]
field.SetString(selectedValue.(string))
} else {
// Otherwise, fill with a generic random string
field.SetString(randomString(rf.randStream))
}
field.SetString(randomString(rf.randStream))

case reflect.Slice:
count := rf.randStream.Intn(10) + 1
slice := reflect.MakeSlice(field.Type(), count, count)
for j := 0; j < count; j++ {
element := reflect.New(field.Type().Elem()).Elem()
rf.fillWithRandom(t, element)
rf.fillWithRandom(t, "", element) // don't need to pass in a field name for slice elements
slice.Index(j).Set(element)
}
field.Set(slice)
Expand All @@ -99,15 +94,18 @@ func (rf *RandomFiller) fillWithRandom(t *testing.T, field reflect.Value) {
for j := 0; j < count; j++ {
key := reflect.New(field.Type().Key()).Elem()
value := reflect.New(field.Type().Elem()).Elem()
rf.fillWithRandom(t, key)
rf.fillWithRandom(t, value)
rf.fillWithRandom(t, "", key) // no need to pass in a field name for keys
rf.fillWithRandom(t, "", value) // no need to pass in a field name for values
mapType.SetMapIndex(key, value)
}
field.Set(mapType)

case reflect.Struct:
for i := 0; i < field.NumField(); i++ {
rf.fillWithRandom(t, field.Field(i))
structFieldName := field.Type().Field(i).Name
nestedStructFieldname := fieldName + "." + structFieldName

rf.fillWithRandom(t, nestedStructFieldname, field.Field(i))
}

default:
Expand Down

0 comments on commit 72649f1

Please sign in to comment.