Switch to new external fuzz package

This commit is contained in:
Daniel Smith 2014-08-08 15:45:51 -07:00
parent a0e9cf575f
commit 079c9043bd
5 changed files with 33 additions and 555 deletions

View File

@ -25,54 +25,55 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/fsouza/go-dockerclient"
"github.com/google/gofuzz"
)
var fuzzIters = flag.Int("fuzz_iters", 3, "How many fuzzing iterations to do.")
var fuzzIters = flag.Int("fuzz_iters", 50, "How many fuzzing iterations to do.")
// apiObjectFuzzer can randomly populate api objects.
var apiObjectFuzzer = util.NewFuzzer(
func(j *JSONBase) {
var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
func(j *JSONBase, c fuzz.Continue) {
// We have to customize the randomization of JSONBases because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
j.ID = util.RandString()
j.ID = c.RandString()
// TODO: Fix JSON/YAML packages and/or write custom encoding
// for uint64's. Somehow the LS *byte* of this is lost, but
// only when all 8 bytes are set.
j.ResourceVersion = util.RandUint64() >> 8
j.SelfLink = util.RandString()
j.CreationTimestamp = util.RandString()
j.ResourceVersion = c.RandUint64() >> 8
j.SelfLink = c.RandString()
j.CreationTimestamp = c.RandString()
},
func(intstr *util.IntOrString) {
func(intstr *util.IntOrString, c fuzz.Continue) {
// util.IntOrString will panic if its kind is set wrong.
if util.RandBool() {
if c.RandBool() {
intstr.Kind = util.IntstrInt
intstr.IntVal = int(util.RandUint64())
intstr.IntVal = int(c.RandUint64())
intstr.StrVal = ""
} else {
intstr.Kind = util.IntstrString
intstr.IntVal = 0
intstr.StrVal = util.RandString()
intstr.StrVal = c.RandString()
}
},
func(u64 *uint64) {
func(u64 *uint64, c fuzz.Continue) {
// TODO: uint64's are NOT handled right.
*u64 = util.RandUint64() >> 8
*u64 = c.RandUint64() >> 8
},
func(pb map[docker.Port][]docker.PortBinding) {
func(pb map[docker.Port][]docker.PortBinding, c fuzz.Continue) {
// This is necessary because keys with nil values get omitted.
// TODO: Is this a bug?
pb[docker.Port(util.RandString())] = []docker.PortBinding{
{util.RandString(), util.RandString()},
{util.RandString(), util.RandString()},
pb[docker.Port(c.RandString())] = []docker.PortBinding{
{c.RandString(), c.RandString()},
{c.RandString(), c.RandString()},
}
},
func(pm map[string]docker.PortMapping) {
func(pm map[string]docker.PortMapping, c fuzz.Continue) {
// This is necessary because keys with nil values get omitted.
// TODO: Is this a bug?
pm[util.RandString()] = docker.PortMapping{
util.RandString(): util.RandString(),
pm[c.RandString()] = docker.PortMapping{
c.RandString(): c.RandString(),
}
},
)

View File

@ -21,7 +21,7 @@ import (
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/google/gofuzz"
)
func TestConverter_CallsRegisteredFunctions(t *testing.T) {
@ -95,7 +95,7 @@ func TestConverter_fuzz(t *testing.T) {
{newAnonType(), &TestType1{}, newAnonType()},
}
f := util.NewFuzzer()
f := fuzz.New().NilChance(.5).NumElements(0, 100)
c := NewConverter()
for i, item := range table {
@ -189,7 +189,7 @@ func TestConverter_flags(t *testing.T) {
shouldSucceed: true,
},
}
f := util.NewFuzzer()
f := fuzz.New().NilChance(.5).NumElements(0, 100)
c := NewConverter()
for i, item := range table {

View File

@ -24,9 +24,10 @@ import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/google/gofuzz"
)
var fuzzIters = flag.Int("fuzz_iters", 10, "How many fuzzing iterations to do.")
var fuzzIters = flag.Int("fuzz_iters", 50, "How many fuzzing iterations to do.")
// Test a weird version/kind embedding format.
type MyWeirdCustomEmbeddedVersionKindField struct {
@ -98,25 +99,25 @@ type ExternalInternalSame struct {
}
// TestObjectFuzzer can randomly populate all the above objects.
var TestObjectFuzzer = util.NewFuzzer(
func(j *MyWeirdCustomEmbeddedVersionKindField) {
var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs(
func(j *MyWeirdCustomEmbeddedVersionKindField, c fuzz.Continue) {
// We have to customize the randomization of MyWeirdCustomEmbeddedVersionKindFields because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.ObjectKind = ""
j.ID = util.RandString()
j.ID = c.RandString()
},
func(u *uint64) {
func(u *uint64, c fuzz.Continue) {
// TODO: Fix JSON/YAML packages and/or write custom encoding
// for uint64's. Somehow the LS *byte* of this is lost, but
// only when all 8 bytes are set.
*u = util.RandUint64() >> 8
*u = c.RandUint64() >> 8
},
func(u *uint) {
func(u *uint, c fuzz.Continue) {
// TODO: Fix JSON/YAML packages and/or write custom encoding
// for uint64's. Somehow the LS *byte* of this is lost, but
// only when all 8 bytes are set.
*u = uint(util.RandUint64() >> 8)
*u = uint(c.RandUint64() >> 8)
},
)

View File

@ -1,270 +0,0 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"math/rand"
"reflect"
)
// Fuzzer knows how to fill any object with random fields.
type Fuzzer struct {
customFuzz map[reflect.Type]func(reflect.Value)
}
// NewFuzzer returns a new Fuzzer with the given custom fuzzing functions.
//
// 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
// to be effective, the variable type should be either a pointer or a map.
//
// These functions are called sensibly, e.g., if you wanted custom string
// fuzzing, the function `func(s *string)` would get called and passed the
// 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 {
f := &Fuzzer{
map[reflect.Type]func(reflect.Value){},
}
for i := range fuzzFuncs {
v := reflect.ValueOf(fuzzFuncs[i])
if v.Kind() != reflect.Func {
panic("Need only funcs!")
}
t := v.Type()
if t.NumIn() != 1 || t.NumOut() != 0 {
panic("Need 1 in and 0 out params!")
}
argT := t.In(0)
switch argT.Kind() {
case reflect.Ptr, reflect.Map:
default:
panic("fuzzFunc must take pointer or map type")
}
f.customFuzz[argT] = func(toFuzz reflect.Value) {
if toFuzz.Type().AssignableTo(argT) {
v.Call([]reflect.Value{toFuzz})
} else if toFuzz.Type().ConvertibleTo(argT) {
v.Call([]reflect.Value{toFuzz.Convert(argT)})
} else {
panic(fmt.Errorf("%#v neither ConvertibleTo nor AssignableTo %v",
toFuzz.Interface(),
argT))
}
}
}
return f
}
// Fuzz recursively fills all of obj's fields with something random.
// Not safe for cyclic or tree-like structs!
// obj must be a pointer. Only exported (public) fields can be set (thanks, golang :/ )
// Intended for tests, so will panic on bad input or unimplemented fields.
func (f *Fuzzer) Fuzz(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
f.doFuzz(v)
}
func (f *Fuzzer) doFuzz(v reflect.Value) {
if !v.CanSet() {
return
}
// Check for both pointer and non-pointer custom functions.
if v.CanAddr() && f.tryCustom(v.Addr()) {
return
}
if f.tryCustom(v) {
return
}
if fn, ok := fillFuncMap[v.Kind()]; ok {
fn(v)
return
}
switch v.Kind() {
case reflect.Map:
if rand.Intn(5) > 0 {
v.Set(reflect.MakeMap(v.Type()))
n := 1 + rand.Intn(2)
for i := 0; i < n; i++ {
key := reflect.New(v.Type().Key()).Elem()
f.doFuzz(key)
val := reflect.New(v.Type().Elem()).Elem()
f.doFuzz(val)
v.SetMapIndex(key, val)
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Ptr:
if rand.Intn(5) > 0 {
v.Set(reflect.New(v.Type().Elem()))
f.doFuzz(v.Elem())
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Slice:
if rand.Intn(5) > 0 {
n := 1 + rand.Intn(2)
v.Set(reflect.MakeSlice(v.Type(), n, n))
for i := 0; i < n; i++ {
f.doFuzz(v.Index(i))
}
return
}
v.Set(reflect.Zero(v.Type()))
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
f.doFuzz(v.Field(i))
}
case reflect.Array:
fallthrough
case reflect.Chan:
fallthrough
case reflect.Func:
fallthrough
case reflect.Interface:
fallthrough
default:
panic(fmt.Sprintf("Can't handle %#v", v.Interface()))
}
}
// tryCustom searches for custom handlers, and returns true iff it finds a match
// and successfully randomizes v.
func (f Fuzzer) tryCustom(v reflect.Value) bool {
doCustom, ok := f.customFuzz[v.Type()]
if !ok {
return false
}
switch v.Kind() {
case reflect.Ptr:
if v.IsNil() {
if !v.CanSet() {
return false
}
v.Set(reflect.New(v.Type().Elem()))
}
case reflect.Map:
if v.IsNil() {
if !v.CanSet() {
return false
}
v.Set(reflect.MakeMap(v.Type()))
}
default:
return false
}
doCustom(v)
return true
}
func fuzzInt(v reflect.Value) {
v.SetInt(int64(RandUint64()))
}
func fuzzUint(v reflect.Value) {
v.SetUint(RandUint64())
}
var fillFuncMap = map[reflect.Kind]func(reflect.Value){
reflect.Bool: func(v reflect.Value) {
v.SetBool(RandBool())
},
reflect.Int: fuzzInt,
reflect.Int8: fuzzInt,
reflect.Int16: fuzzInt,
reflect.Int32: fuzzInt,
reflect.Int64: fuzzInt,
reflect.Uint: fuzzUint,
reflect.Uint8: fuzzUint,
reflect.Uint16: fuzzUint,
reflect.Uint32: fuzzUint,
reflect.Uint64: fuzzUint,
reflect.Uintptr: fuzzUint,
reflect.Float32: func(v reflect.Value) {
v.SetFloat(float64(rand.Float32()))
},
reflect.Float64: func(v reflect.Value) {
v.SetFloat(rand.Float64())
},
reflect.Complex64: func(v reflect.Value) {
panic("unimplemented")
},
reflect.Complex128: func(v reflect.Value) {
panic("unimplemented")
},
reflect.String: func(v reflect.Value) {
v.SetString(RandString())
},
reflect.UnsafePointer: func(v reflect.Value) {
panic("unimplemented")
},
}
// RandBool returns true or false randomly.
func RandBool() bool {
if rand.Int()&1 == 1 {
return true
}
return false
}
type charRange struct {
first, last rune
}
// choose returns a random unicode character from the given range.
func (r *charRange) choose() rune {
count := int64(r.last - r.first)
return r.first + rune(rand.Int63n(count))
}
var unicodeRanges = []charRange{
{' ', '~'}, // ASCII characters
{'\u00a0', '\u02af'}, // Multi-byte encoded characters
{'\u4e00', '\u9fff'}, // Common CJK (even longer encodings)
}
// RandString makes a random string up to 20 characters long. The returned string
// may include a variety of (valid) UTF-8 encodings. For testing.
func RandString() string {
n := rand.Intn(20)
runes := make([]rune, n)
for i := range runes {
runes[i] = unicodeRanges[rand.Intn(len(unicodeRanges))].choose()
}
return string(runes)
}
// RandUint64 makes random 64 bit numbers.
// Weirdly, rand doesn't have a function that gives you 64 random bits.
func RandUint64() uint64 {
return uint64(rand.Uint32())<<32 | uint64(rand.Uint32())
}

View File

@ -1,254 +0,0 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"reflect"
"testing"
)
func TestFuzz_basic(t *testing.T) {
obj := &struct {
I int
I8 int8
I16 int16
I32 int32
I64 int64
U uint
U8 uint8
U16 uint16
U32 uint32
U64 uint64
Uptr uintptr
S string
B bool
}{}
failed := map[string]int{}
for i := 0; i < 10; i++ {
NewFuzzer().Fuzz(obj)
if n, v := "i", obj.I; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "i8", obj.I8; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "i16", obj.I16; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "i32", obj.I32; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "i64", obj.I64; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "u", obj.U; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "u8", obj.U8; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "u16", obj.U16; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "u32", obj.U32; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "u64", obj.U64; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "uptr", obj.Uptr; v == 0 {
failed[n] = failed[n] + 1
}
if n, v := "s", obj.S; v == "" {
failed[n] = failed[n] + 1
}
if n, v := "b", obj.B; v == false {
failed[n] = failed[n] + 1
}
}
checkFailed(t, failed)
}
func checkFailed(t *testing.T, failed map[string]int) {
for k, v := range failed {
if v > 8 {
t.Errorf("%v seems to not be getting set, was zero value %v times", k, v)
}
}
}
func TestFuzz_structptr(t *testing.T) {
obj := &struct {
A *struct {
S string
}
}{}
failed := map[string]int{}
for i := 0; i < 10; i++ {
NewFuzzer().Fuzz(obj)
if n, v := "a", obj.A; v == nil {
failed[n] = failed[n] + 1
}
if n, v := "as", obj.A; v == nil || v.S == "" {
failed[n] = failed[n] + 1
}
}
checkFailed(t, failed)
}
// tryFuzz tries fuzzing up to 20 times. Fail if check() never passes, report the highest
// stage it ever got to.
func tryFuzz(t *testing.T, f *Fuzzer, obj interface{}, check func() (stage int, passed bool)) {
maxStage := 0
for i := 0; i < 20; i++ {
f.Fuzz(obj)
stage, passed := check()
if stage > maxStage {
maxStage = stage
}
if passed {
return
}
}
t.Errorf("Only ever got to stage %v", maxStage)
}
func TestFuzz_structmap(t *testing.T) {
obj := &struct {
A map[struct {
S string
}]struct {
S2 string
}
B map[string]string
}{}
tryFuzz(t, NewFuzzer(), obj, func() (int, bool) {
if obj.A == nil {
return 1, false
}
if len(obj.A) == 0 {
return 2, false
}
for k, v := range obj.A {
if k.S == "" {
return 3, false
}
if v.S2 == "" {
return 4, false
}
}
if obj.B == nil {
return 5, false
}
if len(obj.B) == 0 {
return 6, false
}
for k, v := range obj.B {
if k == "" {
return 7, false
}
if v == "" {
return 8, false
}
}
return 9, true
})
}
func TestFuzz_structslice(t *testing.T) {
obj := &struct {
A []struct {
S string
}
B []string
}{}
tryFuzz(t, NewFuzzer(), obj, func() (int, bool) {
if obj.A == nil {
return 1, false
}
if len(obj.A) == 0 {
return 2, false
}
for _, v := range obj.A {
if v.S == "" {
return 3, false
}
}
if obj.B == nil {
return 4, false
}
if len(obj.B) == 0 {
return 5, false
}
for _, v := range obj.B {
if v == "" {
return 6, false
}
}
return 7, true
})
}
func TestFuzz_custom(t *testing.T) {
obj := &struct {
A string
B *string
C map[string]string
D *map[string]string
}{}
testPhrase := "gotcalled"
testMap := map[string]string{"C": "D"}
f := NewFuzzer(
func(s *string) {
*s = testPhrase
},
func(m map[string]string) {
m["C"] = "D"
},
)
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
})
}