Add TODOs, clean up, clarify comments, and add one more test.

This commit is contained in:
Daniel Smith 2014-07-27 19:32:37 -07:00
parent aa92dd7fb2
commit d09b164e67
2 changed files with 69 additions and 37 deletions

View File

@ -28,12 +28,21 @@ type Fuzzer struct {
} }
// NewFuzzer returns a new Fuzzer with the given custom fuzzing functions. // NewFuzzer returns a new Fuzzer with the given custom fuzzing functions.
//
// Each entry in fuzzFuncs must be a function with one parameter, which will // Each entry in fuzzFuncs must be a function with one parameter, which will
// be the variable we wish that function to fill with random data. For this // be the variable we wish that function to fill with random data. For this
// to be effective, the variable type should be either a pointer, map, slice, // to be effective, the variable type should be either a pointer or a map.
// etc. These functions are called sensibly, e.g., if you wanted custom string //
// These functions are called sensibly, e.g., if you wanted custom string
// fuzzing, the function `func(s *string)` would get called and passed the // fuzzing, the function `func(s *string)` would get called and passed the
// address of strings. // address of strings. Maps and pointers will be made/new'd for you. For
// slices, it doesn't make much sense to pre-create them--Fuzzer doesn't
// know how long you want your slice--so take a pointer to a slice, and
// make it yourself. (If you don't want your map/pointer type pre-made,
// take a pointer to it, and make it yourself.)
//
// TODO: Take a source of randomness for deterministic, repeatable fuzzing.
// TODO: Make probability of getting a nil customizable.
func NewFuzzer(fuzzFuncs ...interface{}) *Fuzzer { func NewFuzzer(fuzzFuncs ...interface{}) *Fuzzer {
f := &Fuzzer{ f := &Fuzzer{
map[reflect.Type]func(reflect.Value){}, map[reflect.Type]func(reflect.Value){},
@ -48,7 +57,11 @@ func NewFuzzer(fuzzFuncs ...interface{}) *Fuzzer {
panic("Need 1 in and 0 out params!") panic("Need 1 in and 0 out params!")
} }
argT := t.In(0) argT := t.In(0)
// fmt.Printf("Making entry for thing with '%v' type\n", argT) switch argT.Kind() {
case reflect.Ptr, reflect.Map:
default:
panic("fuzzFunc must take pointer or map type")
}
f.customFuzz[argT] = func(toFuzz reflect.Value) { f.customFuzz[argT] = func(toFuzz reflect.Value) {
if toFuzz.Type().AssignableTo(argT) { if toFuzz.Type().AssignableTo(argT) {
v.Call([]reflect.Value{toFuzz}) v.Call([]reflect.Value{toFuzz})
@ -96,9 +109,6 @@ func (f *Fuzzer) doFuzz(v reflect.Value) {
case reflect.Map: case reflect.Map:
if rand.Intn(5) > 0 { if rand.Intn(5) > 0 {
v.Set(reflect.MakeMap(v.Type())) v.Set(reflect.MakeMap(v.Type()))
if f.tryCustom(v) {
return
}
n := 1 + rand.Intn(10) n := 1 + rand.Intn(10)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
key := reflect.New(v.Type().Key()).Elem() key := reflect.New(v.Type().Key()).Elem()
@ -121,9 +131,6 @@ func (f *Fuzzer) doFuzz(v reflect.Value) {
if rand.Intn(5) > 0 { if rand.Intn(5) > 0 {
n := 1 + rand.Intn(10) n := 1 + rand.Intn(10)
v.Set(reflect.MakeSlice(v.Type(), n, n)) v.Set(reflect.MakeSlice(v.Type(), n, n))
if f.tryCustom(v) {
return
}
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
f.doFuzz(v.Index(i)) f.doFuzz(v.Index(i))
} }
@ -147,15 +154,15 @@ func (f *Fuzzer) doFuzz(v reflect.Value) {
} }
} }
// tryCustom searches for custom handlers and Randomizer implementations, and // tryCustom searches for custom handlers, and returns true iff it finds a match
// returns true if it finds a match and successfully randomizes v. // and successfully randomizes v.
func (f Fuzzer) tryCustom(v reflect.Value) bool { func (f Fuzzer) tryCustom(v reflect.Value) bool {
// fmt.Printf("Trying thing with '%v' type\n", v.Type()) doCustom, ok := f.customFuzz[v.Type()]
if !ok {
return false
}
switch v.Kind() { switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Slice:
if v.IsNil() {
return false
}
case reflect.Ptr: case reflect.Ptr:
if v.IsNil() { if v.IsNil() {
if !v.CanSet() { if !v.CanSet() {
@ -170,12 +177,12 @@ func (f Fuzzer) tryCustom(v reflect.Value) bool {
} }
v.Set(reflect.MakeMap(v.Type())) v.Set(reflect.MakeMap(v.Type()))
} }
default:
return false
} }
if f, ok := f.customFuzz[v.Type()]; ok {
f(v) doCustom(v)
return true return true
}
return false
} }
func fuzzInt(v reflect.Value) { func fuzzInt(v reflect.Value) {

View File

@ -17,6 +17,7 @@ limitations under the License.
package util package util
import ( import (
"reflect"
"testing" "testing"
) )
@ -113,11 +114,12 @@ func TestFuzz_structptr(t *testing.T) {
checkFailed(t, failed) checkFailed(t, failed)
} }
// Try fuzzing up to 20 times. Fail if check() never passes. // tryFuzz tries fuzzing up to 20 times. Fail if check() never passes, report the highest
func tryFuzz(t *testing.T, obj interface{}, check func() (stage int, passed bool)) { // stage it ever got to.
func tryFuzz(t *testing.T, f *Fuzzer, obj interface{}, check func() (stage int, passed bool)) {
maxStage := 0 maxStage := 0
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
NewFuzzer().Fuzz(obj) f.Fuzz(obj)
stage, passed := check() stage, passed := check()
if stage > maxStage { if stage > maxStage {
maxStage = stage maxStage = stage
@ -139,7 +141,7 @@ func TestFuzz_structmap(t *testing.T) {
B map[string]string B map[string]string
}{} }{}
tryFuzz(t, obj, func() (int, bool) { tryFuzz(t, NewFuzzer(), obj, func() (int, bool) {
if obj.A == nil { if obj.A == nil {
return 1, false return 1, false
} }
@ -181,7 +183,7 @@ func TestFuzz_structslice(t *testing.T) {
B []string B []string
}{} }{}
tryFuzz(t, obj, func() (int, bool) { tryFuzz(t, NewFuzzer(), obj, func() (int, bool) {
if obj.A == nil { if obj.A == nil {
return 1, false return 1, false
} }
@ -213,17 +215,40 @@ func TestFuzz_custom(t *testing.T) {
obj := &struct { obj := &struct {
A string A string
B *string B *string
C map[string]string
D *map[string]string
}{} }{}
testPhrase := "gotcalled" testPhrase := "gotcalled"
NewFuzzer(func(s *string) { *s = testPhrase }).Fuzz(obj) testMap := map[string]string{"C": "D"}
if obj.A != testPhrase { f := NewFuzzer(
t.Errorf("A not set") func(s *string) {
} *s = testPhrase
if obj.B == nil { },
t.Fatalf("B is nil") func(m map[string]string) {
} m["C"] = "D"
if *obj.B != testPhrase { },
t.Errorf("B not set") )
}
tryFuzz(t, f, obj, func() (int, bool) {
if obj.A != testPhrase {
return 1, false
}
if obj.B == nil {
return 2, false
}
if *obj.B != testPhrase {
return 3, false
}
if e, a := testMap, obj.C; !reflect.DeepEqual(e, a) {
return 4, false
}
if obj.D == nil {
return 5, false
}
if e, a := testMap, *obj.D; !reflect.DeepEqual(e, a) {
return 6, false
}
return 7, true
})
} }