From cee14ab51bc1ec14db2d90ab873c2207c41aed75 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Tue, 3 Mar 2015 09:01:52 -0800 Subject: [PATCH 1/4] Update gofuzz dep --- Godeps/Godeps.json | 2 +- .../src/github.com/google/gofuzz/.travis.yml | 5 +- .../src/github.com/google/gofuzz/fuzz.go | 122 ++++++++++++++--- .../src/github.com/google/gofuzz/fuzz_test.go | 126 ++++++++++++++++++ 4 files changed, 231 insertions(+), 24 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index efbb18030d5..1012061ce4d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -161,7 +161,7 @@ }, { "ImportPath": "github.com/google/gofuzz", - "Rev": "aef70dacbc78771e35beb261bb3a72986adf7906" + "Rev": "bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5" }, { "ImportPath": "github.com/imdario/mergo", diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/.travis.yml b/Godeps/_workspace/src/github.com/google/gofuzz/.travis.yml index 9384a54fd77..f8684d99fc4 100644 --- a/Godeps/_workspace/src/github.com/google/gofuzz/.travis.yml +++ b/Godeps/_workspace/src/github.com/google/gofuzz/.travis.yml @@ -1,12 +1,13 @@ language: go go: + - 1.4 - 1.3 - 1.2 - tip -install: - - go get code.google.com/p/go.tools/cmd/cover +install: + - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi script: - go test -cover diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/fuzz.go b/Godeps/_workspace/src/github.com/google/gofuzz/fuzz.go index 31c2838940e..42d9a48b3e2 100644 --- a/Godeps/_workspace/src/github.com/google/gofuzz/fuzz.go +++ b/Godeps/_workspace/src/github.com/google/gofuzz/fuzz.go @@ -28,17 +28,22 @@ type fuzzFuncMap map[reflect.Type]reflect.Value // Fuzzer knows how to fill any object with random fields. type Fuzzer struct { - fuzzFuncs fuzzFuncMap - r *rand.Rand - nilChance float64 - minElements int - maxElements int + fuzzFuncs fuzzFuncMap + defaultFuzzFuncs fuzzFuncMap + r *rand.Rand + nilChance float64 + minElements int + maxElements int } // New returns a new Fuzzer. Customize your Fuzzer further by calling Funcs, // RandSource, NilChance, or NumElements in any order. func New() *Fuzzer { f := &Fuzzer{ + defaultFuzzFuncs: fuzzFuncMap{ + reflect.TypeOf(&time.Time{}): reflect.ValueOf(fuzzTime), + }, + fuzzFuncs: fuzzFuncMap{}, r: rand.New(rand.NewSource(time.Now().UnixNano())), nilChance: .2, @@ -131,8 +136,16 @@ func (f *Fuzzer) genShouldFill() bool { 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! +// // 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{}) { @@ -141,20 +154,45 @@ func (f *Fuzzer) Fuzz(obj interface{}) { panic("needed ptr!") } 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() { return } - // Check for both pointer and non-pointer custom functions. - if v.CanAddr() && f.tryCustom(v.Addr()) { - return - } - if f.tryCustom(v) { - return + + if flags&flagNoCustomFuzz == 0 { + // 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, f.r) return @@ -166,9 +204,9 @@ func (f *Fuzzer) doFuzz(v reflect.Value) { n := f.genElementCount() for i := 0; i < n; i++ { key := reflect.New(v.Type().Key()).Elem() - f.doFuzz(key) + f.doFuzz(key, 0) val := reflect.New(v.Type().Elem()).Elem() - f.doFuzz(val) + f.doFuzz(val, 0) v.SetMapIndex(key, val) } return @@ -177,7 +215,7 @@ func (f *Fuzzer) doFuzz(v reflect.Value) { case reflect.Ptr: if f.genShouldFill() { v.Set(reflect.New(v.Type().Elem())) - f.doFuzz(v.Elem()) + f.doFuzz(v.Elem(), 0) return } v.Set(reflect.Zero(v.Type())) @@ -186,14 +224,14 @@ func (f *Fuzzer) doFuzz(v reflect.Value) { n := f.genElementCount() v.Set(reflect.MakeSlice(v.Type(), n, n)) for i := 0; i < n; i++ { - f.doFuzz(v.Index(i)) + f.doFuzz(v.Index(i), 0) } return } v.Set(reflect.Zero(v.Type())) case reflect.Struct: for i := 0; i < v.NumField(); i++ { - f.doFuzz(v.Field(i)) + f.doFuzz(v.Field(i), 0) } case reflect.Array: 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 // and successfully randomizes v. func (f *Fuzzer) tryCustom(v reflect.Value) bool { + // First: see if we have a fuzz function for it. doCustom, ok := f.fuzzFuncs[v.Type()] 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() { @@ -242,6 +293,13 @@ func (f *Fuzzer) tryCustom(v reflect.Value) bool { 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 // the correct source of randomness and to continue fuzzing their members. type Continue struct { @@ -260,7 +318,20 @@ func (c Continue) Fuzz(obj interface{}) { panic("needed ptr!") } 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 @@ -288,6 +359,15 @@ func fuzzUint(v reflect.Value, r *rand.Rand) { 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){ reflect.Bool: func(v reflect.Value, r *rand.Rand) { v.SetBool(randBool(r)) diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/fuzz_test.go b/Godeps/_workspace/src/github.com/google/gofuzz/fuzz_test.go index 4f0d4dbad7d..12abc8f6598 100644 --- a/Godeps/_workspace/src/github.com/google/gofuzz/fuzz_test.go +++ b/Godeps/_workspace/src/github.com/google/gofuzz/fuzz_test.go @@ -19,6 +19,7 @@ package fuzz import ( "reflect" "testing" + "time" ) func TestFuzz_basic(t *testing.T) { @@ -36,6 +37,7 @@ func TestFuzz_basic(t *testing.T) { Uptr uintptr S string B bool + T time.Time }{} failed := map[string]int{} @@ -81,6 +83,9 @@ func TestFuzz_basic(t *testing.T) { if n, v := "b", obj.B; v == false { failed[n] = failed[n] + 1 } + if n, v := "t", obj.T; v.IsZero() { + failed[n] = failed[n] + 1 + } } checkFailed(t, failed) } @@ -256,3 +261,124 @@ func TestFuzz_custom(t *testing.T) { 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") + } +} From b3304c49ad160fdd8f21eafe6f002ea3ffbd6b55 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Thu, 5 Mar 2015 20:55:32 -0800 Subject: [PATCH 2/4] Use deep spew in serialization test & go obj diff --- pkg/api/serialization_test.go | 9 ++++++--- pkg/util/diff.go | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index 25caa9a5928..f59f8fbb66e 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -32,6 +32,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/davecgh/go-spew/spew" 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) { + printer := spew.ConfigState{DisableMethods: true} + name := reflect.TypeOf(item).Elem().Name() data, err := codec.Encode(item) if err != nil { - t.Errorf("%v: %v (%#v)", name, err, item) + t.Errorf("%v: %v (%s)", name, err, printer.Sprintf("%#v", item)) return } obj2, err := codec.Decode(data) 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 } 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 } diff --git a/pkg/util/diff.go b/pkg/util/diff.go index a06aed9d23e..dd5057beec4 100644 --- a/pkg/util/diff.go +++ b/pkg/util/diff.go @@ -67,9 +67,10 @@ func ObjectDiff(a, b interface{}) string { // can't figure out why reflect.DeepEqual is returning false and nothing is // showing you differences. This will. func ObjectGoPrintDiff(a, b interface{}) string { + s := spew.ConfigState{DisableMethods: true} return StringDiff( - spew.Sprintf("%#v", a), - spew.Sprintf("%#v", b), + s.Sprintf("%#v", a), + s.Sprintf("%#v", b), ) } From 0b38282b9b16855df7719ece7ff56bb09cb6467d Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Thu, 5 Mar 2015 17:51:09 -0800 Subject: [PATCH 3/4] Fuzz util.Time with no nsec because JSON --- pkg/util/time.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/util/time.go b/pkg/util/time.go index 574de7cccc3..84d02ac8dc3 100644 --- a/pkg/util/time.go +++ b/pkg/util/time.go @@ -19,6 +19,8 @@ package util import ( "encoding/json" "time" + + "github.com/google/gofuzz" ) // 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)) } + +// 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{} From f9e2df3a1002791b0eb8745252dd0650e2ea457f Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Thu, 5 Mar 2015 21:03:21 -0800 Subject: [PATCH 4/4] Define semantic equal for time --- pkg/api/helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index 774111d4301..d846bbc0b73 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -61,7 +61,7 @@ var Semantic = conversion.EqualitiesOrDie( return a.Amount.Cmp(b.Amount) == 0 }, func(a, b util.Time) bool { - return a.Unix() == b.Unix() + return a.UTC() == b.UTC() }, )