Merge pull request #4985 from thockin/gofuzz

Update gofuzz dep
This commit is contained in:
Daniel Smith 2015-03-06 10:19:01 -08:00
commit b0f49449e1
8 changed files with 253 additions and 30 deletions

2
Godeps/Godeps.json generated
View File

@ -161,7 +161,7 @@
}, },
{ {
"ImportPath": "github.com/google/gofuzz", "ImportPath": "github.com/google/gofuzz",
"Rev": "aef70dacbc78771e35beb261bb3a72986adf7906" "Rev": "bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5"
}, },
{ {
"ImportPath": "github.com/imdario/mergo", "ImportPath": "github.com/imdario/mergo",

View File

@ -1,12 +1,13 @@
language: go language: go
go: go:
- 1.4
- 1.3 - 1.3
- 1.2 - 1.2
- tip - tip
install: install:
- go get code.google.com/p/go.tools/cmd/cover - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
script: script:
- go test -cover - go test -cover

View File

@ -28,17 +28,22 @@ type fuzzFuncMap map[reflect.Type]reflect.Value
// Fuzzer knows how to fill any object with random fields. // Fuzzer knows how to fill any object with random fields.
type Fuzzer struct { type Fuzzer struct {
fuzzFuncs fuzzFuncMap fuzzFuncs fuzzFuncMap
r *rand.Rand defaultFuzzFuncs fuzzFuncMap
nilChance float64 r *rand.Rand
minElements int nilChance float64
maxElements int minElements int
maxElements int
} }
// New returns a new Fuzzer. Customize your Fuzzer further by calling Funcs, // New returns a new Fuzzer. Customize your Fuzzer further by calling Funcs,
// RandSource, NilChance, or NumElements in any order. // RandSource, NilChance, or NumElements in any order.
func New() *Fuzzer { func New() *Fuzzer {
f := &Fuzzer{ f := &Fuzzer{
defaultFuzzFuncs: fuzzFuncMap{
reflect.TypeOf(&time.Time{}): reflect.ValueOf(fuzzTime),
},
fuzzFuncs: fuzzFuncMap{}, fuzzFuncs: fuzzFuncMap{},
r: rand.New(rand.NewSource(time.Now().UnixNano())), r: rand.New(rand.NewSource(time.Now().UnixNano())),
nilChance: .2, nilChance: .2,
@ -131,8 +136,16 @@ func (f *Fuzzer) genShouldFill() bool {
return f.r.Float64() > f.nilChance return f.r.Float64() > f.nilChance
} }
// Fuzz recursively fills all of obj's fields with something random. // Fuzz recursively fills all of obj's fields with something random. First
// this tries to find a custom fuzz function (see Funcs). If there is no
// custom function this tests whether the object implements fuzz.Interface and,
// if so, calls Fuzz on it to fuzz itself. If that fails, this will see if
// there is a default fuzz function provided by this package. If all of that
// fails, this will generate random values for all primitive fields and then
// recurse for all non-primitives.
//
// Not safe for cyclic or tree-like structs! // Not safe for cyclic or tree-like structs!
//
// obj must be a pointer. Only exported (public) fields can be set (thanks, golang :/ ) // 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. // Intended for tests, so will panic on bad input or unimplemented fields.
func (f *Fuzzer) Fuzz(obj interface{}) { func (f *Fuzzer) Fuzz(obj interface{}) {
@ -141,20 +154,45 @@ func (f *Fuzzer) Fuzz(obj interface{}) {
panic("needed ptr!") panic("needed ptr!")
} }
v = v.Elem() v = v.Elem()
f.doFuzz(v) f.doFuzz(v, 0)
} }
func (f *Fuzzer) doFuzz(v reflect.Value) { // FuzzNoCustom is just like Fuzz, except that any custom fuzz function for
// obj's type will not be called and obj will not be tested for fuzz.Interface
// conformance. This applies only to obj and not other instances of obj's
// type.
// 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) FuzzNoCustom(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
f.doFuzz(v, flagNoCustomFuzz)
}
const (
// Do not try to find a custom fuzz function. Does not apply recursively.
flagNoCustomFuzz uint64 = 1 << iota
)
func (f *Fuzzer) doFuzz(v reflect.Value, flags uint64) {
if !v.CanSet() { if !v.CanSet() {
return return
} }
// Check for both pointer and non-pointer custom functions.
if v.CanAddr() && f.tryCustom(v.Addr()) { if flags&flagNoCustomFuzz == 0 {
return // Check for both pointer and non-pointer custom functions.
} if v.CanAddr() && f.tryCustom(v.Addr()) {
if f.tryCustom(v) { return
return }
if f.tryCustom(v) {
return
}
} }
if fn, ok := fillFuncMap[v.Kind()]; ok { if fn, ok := fillFuncMap[v.Kind()]; ok {
fn(v, f.r) fn(v, f.r)
return return
@ -166,9 +204,9 @@ func (f *Fuzzer) doFuzz(v reflect.Value) {
n := f.genElementCount() n := f.genElementCount()
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()
f.doFuzz(key) f.doFuzz(key, 0)
val := reflect.New(v.Type().Elem()).Elem() val := reflect.New(v.Type().Elem()).Elem()
f.doFuzz(val) f.doFuzz(val, 0)
v.SetMapIndex(key, val) v.SetMapIndex(key, val)
} }
return return
@ -177,7 +215,7 @@ func (f *Fuzzer) doFuzz(v reflect.Value) {
case reflect.Ptr: case reflect.Ptr:
if f.genShouldFill() { if f.genShouldFill() {
v.Set(reflect.New(v.Type().Elem())) v.Set(reflect.New(v.Type().Elem()))
f.doFuzz(v.Elem()) f.doFuzz(v.Elem(), 0)
return return
} }
v.Set(reflect.Zero(v.Type())) v.Set(reflect.Zero(v.Type()))
@ -186,14 +224,14 @@ func (f *Fuzzer) doFuzz(v reflect.Value) {
n := f.genElementCount() n := f.genElementCount()
v.Set(reflect.MakeSlice(v.Type(), n, n)) v.Set(reflect.MakeSlice(v.Type(), n, n))
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
f.doFuzz(v.Index(i)) f.doFuzz(v.Index(i), 0)
} }
return return
} }
v.Set(reflect.Zero(v.Type())) v.Set(reflect.Zero(v.Type()))
case reflect.Struct: case reflect.Struct:
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
f.doFuzz(v.Field(i)) f.doFuzz(v.Field(i), 0)
} }
case reflect.Array: case reflect.Array:
fallthrough fallthrough
@ -211,9 +249,22 @@ func (f *Fuzzer) doFuzz(v reflect.Value) {
// tryCustom searches for custom handlers, and returns true iff it finds a match // tryCustom searches for custom handlers, and returns true iff 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 {
// First: see if we have a fuzz function for it.
doCustom, ok := f.fuzzFuncs[v.Type()] doCustom, ok := f.fuzzFuncs[v.Type()]
if !ok { if !ok {
return false // Second: see if it can fuzz itself.
if v.CanInterface() {
intf := v.Interface()
if fuzzable, ok := intf.(Interface); ok {
fuzzable.Fuzz(Continue{f: f, Rand: f.r})
return true
}
}
// Finally: see if there is a default fuzz function.
doCustom, ok = f.defaultFuzzFuncs[v.Type()]
if !ok {
return false
}
} }
switch v.Kind() { switch v.Kind() {
@ -242,6 +293,13 @@ func (f *Fuzzer) tryCustom(v reflect.Value) bool {
return true return true
} }
// Interface represents an object that knows how to fuzz itself. Any time we
// find a type that implements this interface we will delegate the act of
// fuzzing itself.
type Interface interface {
Fuzz(c Continue)
}
// Continue can be passed to custom fuzzing functions to allow them to use // Continue can be passed to custom fuzzing functions to allow them to use
// the correct source of randomness and to continue fuzzing their members. // the correct source of randomness and to continue fuzzing their members.
type Continue struct { type Continue struct {
@ -260,7 +318,20 @@ func (c Continue) Fuzz(obj interface{}) {
panic("needed ptr!") panic("needed ptr!")
} }
v = v.Elem() v = v.Elem()
c.f.doFuzz(v) c.f.doFuzz(v, 0)
}
// FuzzNoCustom continues fuzzing obj, except that any custom fuzz function for
// obj's type will not be called and obj will not be tested for fuzz.Interface
// conformance. This applies only to obj and not other instances of obj's
// type.
func (c Continue) FuzzNoCustom(obj interface{}) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
panic("needed ptr!")
}
v = v.Elem()
c.f.doFuzz(v, flagNoCustomFuzz)
} }
// RandString makes a random string up to 20 characters long. The returned string // RandString makes a random string up to 20 characters long. The returned string
@ -288,6 +359,15 @@ func fuzzUint(v reflect.Value, r *rand.Rand) {
v.SetUint(randUint64(r)) v.SetUint(randUint64(r))
} }
func fuzzTime(t *time.Time, c Continue) {
var sec, nsec int64
// Allow for about 1000 years of random time values, which keeps things
// like JSON parsing reasonably happy.
sec = c.Rand.Int63n(1000 * 365 * 24 * 60 * 60)
c.Fuzz(&nsec)
*t = time.Unix(sec, nsec)
}
var fillFuncMap = map[reflect.Kind]func(reflect.Value, *rand.Rand){ var fillFuncMap = map[reflect.Kind]func(reflect.Value, *rand.Rand){
reflect.Bool: func(v reflect.Value, r *rand.Rand) { reflect.Bool: func(v reflect.Value, r *rand.Rand) {
v.SetBool(randBool(r)) v.SetBool(randBool(r))

View File

@ -19,6 +19,7 @@ package fuzz
import ( import (
"reflect" "reflect"
"testing" "testing"
"time"
) )
func TestFuzz_basic(t *testing.T) { func TestFuzz_basic(t *testing.T) {
@ -36,6 +37,7 @@ func TestFuzz_basic(t *testing.T) {
Uptr uintptr Uptr uintptr
S string S string
B bool B bool
T time.Time
}{} }{}
failed := map[string]int{} failed := map[string]int{}
@ -81,6 +83,9 @@ func TestFuzz_basic(t *testing.T) {
if n, v := "b", obj.B; v == false { if n, v := "b", obj.B; v == false {
failed[n] = failed[n] + 1 failed[n] = failed[n] + 1
} }
if n, v := "t", obj.T; v.IsZero() {
failed[n] = failed[n] + 1
}
} }
checkFailed(t, failed) checkFailed(t, failed)
} }
@ -256,3 +261,124 @@ func TestFuzz_custom(t *testing.T) {
return 7, true return 7, true
}) })
} }
type SelfFuzzer string
// Implement fuzz.Interface.
func (sf *SelfFuzzer) Fuzz(c Continue) {
*sf = selfFuzzerTestPhrase
}
const selfFuzzerTestPhrase = "was fuzzed"
func TestFuzz_interface(t *testing.T) {
f := New()
var obj1 SelfFuzzer
tryFuzz(t, f, &obj1, func() (int, bool) {
if obj1 != selfFuzzerTestPhrase {
return 1, false
}
return 1, true
})
var obj2 map[int]SelfFuzzer
tryFuzz(t, f, &obj2, func() (int, bool) {
for _, v := range obj2 {
if v != selfFuzzerTestPhrase {
return 1, false
}
}
return 1, true
})
}
func TestFuzz_interfaceAndFunc(t *testing.T) {
const privateTestPhrase = "private phrase"
f := New().Funcs(
// This should take precedence over SelfFuzzer.Fuzz().
func(s *SelfFuzzer, c Continue) {
*s = privateTestPhrase
},
)
var obj1 SelfFuzzer
tryFuzz(t, f, &obj1, func() (int, bool) {
if obj1 != privateTestPhrase {
return 1, false
}
return 1, true
})
var obj2 map[int]SelfFuzzer
tryFuzz(t, f, &obj2, func() (int, bool) {
for _, v := range obj2 {
if v != privateTestPhrase {
return 1, false
}
}
return 1, true
})
}
func TestFuzz_noCustom(t *testing.T) {
type Inner struct {
Str string
}
type Outer struct {
Str string
In Inner
}
testPhrase := "gotcalled"
f := New().Funcs(
func(outer *Outer, c Continue) {
outer.Str = testPhrase
c.Fuzz(&outer.In)
},
func(inner *Inner, c Continue) {
inner.Str = testPhrase
},
)
c := Continue{f: f, Rand: f.r}
// Fuzzer.Fuzz()
obj1 := Outer{}
f.Fuzz(&obj1)
if obj1.Str != testPhrase {
t.Errorf("expected Outer custom function to have been called")
}
if obj1.In.Str != testPhrase {
t.Errorf("expected Inner custom function to have been called")
}
// Continue.Fuzz()
obj2 := Outer{}
c.Fuzz(&obj2)
if obj2.Str != testPhrase {
t.Errorf("expected Outer custom function to have been called")
}
if obj2.In.Str != testPhrase {
t.Errorf("expected Inner custom function to have been called")
}
// Fuzzer.FuzzNoCustom()
obj3 := Outer{}
f.FuzzNoCustom(&obj3)
if obj3.Str == testPhrase {
t.Errorf("expected Outer custom function to not have been called")
}
if obj3.In.Str != testPhrase {
t.Errorf("expected Inner custom function to have been called")
}
// Continue.FuzzNoCustom()
obj4 := Outer{}
c.FuzzNoCustom(&obj4)
if obj4.Str == testPhrase {
t.Errorf("expected Outer custom function to not have been called")
}
if obj4.In.Str != testPhrase {
t.Errorf("expected Inner custom function to have been called")
}
}

View File

@ -61,7 +61,7 @@ var Semantic = conversion.EqualitiesOrDie(
return a.Amount.Cmp(b.Amount) == 0 return a.Amount.Cmp(b.Amount) == 0
}, },
func(a, b util.Time) bool { func(a, b util.Time) bool {
return a.Unix() == b.Unix() return a.UTC() == b.UTC()
}, },
) )

View File

@ -32,6 +32,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/davecgh/go-spew/spew"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
) )
@ -52,20 +53,22 @@ func fuzzInternalObject(t *testing.T, forVersion string, item runtime.Object, se
} }
func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) {
printer := spew.ConfigState{DisableMethods: true}
name := reflect.TypeOf(item).Elem().Name() name := reflect.TypeOf(item).Elem().Name()
data, err := codec.Encode(item) data, err := codec.Encode(item)
if err != nil { if err != nil {
t.Errorf("%v: %v (%#v)", name, err, item) t.Errorf("%v: %v (%s)", name, err, printer.Sprintf("%#v", item))
return return
} }
obj2, err := codec.Decode(data) obj2, err := codec.Decode(data)
if err != nil { if err != nil {
t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), item) t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), printer.Sprintf("%#v", item))
return return
} }
if !api.Semantic.DeepEqual(item, obj2) { if !api.Semantic.DeepEqual(item, obj2) {
t.Errorf("1: %v: diff: %v\nCodec: %v\nData: %s\nSource: %#v\nFinal: %#v", name, util.ObjectGoPrintDiff(item, obj2), codec, string(data), item, obj2) t.Errorf("1: %v: diff: %v\nCodec: %v\nData: %s\nSource: %#v\nFinal: %#v", name, util.ObjectGoPrintDiff(item, obj2), codec, string(data), printer.Sprintf("%#v", item), printer.Sprintf("%#v", obj2))
return return
} }

View File

@ -67,9 +67,10 @@ func ObjectDiff(a, b interface{}) string {
// can't figure out why reflect.DeepEqual is returning false and nothing is // can't figure out why reflect.DeepEqual is returning false and nothing is
// showing you differences. This will. // showing you differences. This will.
func ObjectGoPrintDiff(a, b interface{}) string { func ObjectGoPrintDiff(a, b interface{}) string {
s := spew.ConfigState{DisableMethods: true}
return StringDiff( return StringDiff(
spew.Sprintf("%#v", a), s.Sprintf("%#v", a),
spew.Sprintf("%#v", b), s.Sprintf("%#v", b),
) )
} }

View File

@ -19,6 +19,8 @@ package util
import ( import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/google/gofuzz"
) )
// Time is a wrapper around time.Time which supports correct // Time is a wrapper around time.Time which supports correct
@ -89,3 +91,13 @@ func (t Time) MarshalJSON() ([]byte, error) {
return json.Marshal(t.Format(time.RFC3339)) return json.Marshal(t.Format(time.RFC3339))
} }
// Fuzz satisfies fuzz.Interface.
func (t *Time) Fuzz(c fuzz.Continue) {
// Allow for about 1000 years of randomness. Leave off nanoseconds
// because JSON doesn't represent them so they can't round-trip
// properly.
t.Time = time.Unix(c.Rand.Int63n(1000*365*24*60*60), 0)
}
var _ fuzz.Interface = &Time{}