Add go fuzzer in preparation for testing. Also gofmt a few files that needed it.

This commit is contained in:
Daniel Smith 2014-07-25 17:58:36 -07:00
parent 8a5cc87df8
commit aa92dd7fb2
6 changed files with 524 additions and 4 deletions

263
pkg/util/fuzz.go Normal file
View File

@ -0,0 +1,263 @@
/*
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, map, slice,
// etc. 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.
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)
// fmt.Printf("Making entry for thing with '%v' type\n", argT)
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()))
if f.tryCustom(v) {
return
}
n := 1 + rand.Intn(10)
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(10)
v.Set(reflect.MakeSlice(v.Type(), n, n))
if f.tryCustom(v) {
return
}
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 Randomizer implementations, and
// returns true if it finds a match and successfully randomizes v.
func (f Fuzzer) tryCustom(v reflect.Value) bool {
// fmt.Printf("Trying thing with '%v' type\n", v.Type())
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Slice:
if v.IsNil() {
return false
}
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()))
}
}
if f, ok := f.customFuzz[v.Type()]; ok {
f(v)
return true
}
return false
}
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())
}

229
pkg/util/fuzz_test.go Normal file
View File

@ -0,0 +1,229 @@
/*
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 (
"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)
}
// Try fuzzing up to 20 times. Fail if check() never passes.
func tryFuzz(t *testing.T, obj interface{}, check func() (stage int, passed bool)) {
maxStage := 0
for i := 0; i < 20; i++ {
NewFuzzer().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, 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, 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
}{}
testPhrase := "gotcalled"
NewFuzzer(func(s *string) { *s = testPhrase }).Fuzz(obj)
if obj.A != testPhrase {
t.Errorf("A not set")
}
if obj.B == nil {
t.Fatalf("B is nil")
}
if *obj.B != testPhrase {
t.Errorf("B not set")
}
}

View File

@ -62,7 +62,7 @@ func (s StringSet) HasAll(items ...string) bool {
// IsSuperset returns true iff s1 is a superset of s2. // IsSuperset returns true iff s1 is a superset of s2.
func (s1 StringSet) IsSuperset(s2 StringSet) bool { func (s1 StringSet) IsSuperset(s2 StringSet) bool {
for item, _ := range s2 { for item := range s2 {
if !s1.Has(item) { if !s1.Has(item) {
return false return false
} }

View File

@ -43,13 +43,13 @@ func TestStringSet(t *testing.T) {
t.Errorf("Unexpected contents: %#v", s) t.Errorf("Unexpected contents: %#v", s)
} }
s.Insert("a") s.Insert("a")
if s.HasAll("a","b","d") { if s.HasAll("a", "b", "d") {
t.Errorf("Unexpected contents: %#v", s) t.Errorf("Unexpected contents: %#v", s)
} }
if !s.HasAll("a","b",) { if !s.HasAll("a", "b") {
t.Errorf("Missing contents: %#v", s) t.Errorf("Missing contents: %#v", s)
} }
s2.Insert("a","b","d") s2.Insert("a", "b", "d")
if s.IsSuperset(s2) { if s.IsSuperset(s2) {
t.Errorf("Unexpected contents: %#v", s) t.Errorf("Unexpected contents: %#v", s)
} }

View File

@ -132,3 +132,23 @@ func (intstr IntOrString) MarshalJSON() ([]byte, error) {
return []byte{}, fmt.Errorf("impossible IntOrString.Kind") return []byte{}, fmt.Errorf("impossible IntOrString.Kind")
} }
} }
// StringDiff diffs a and b and returns a human readable diff.
func StringDiff(a, b string) string {
ba := []byte(a)
bb := []byte(b)
out := []byte{}
i := 0
for ; i < len(ba) && i < len(bb); i++ {
if ba[i] != bb[i] {
break
}
out = append(out, ba[i])
}
out = append(out, []byte("\n\nA: ")...)
out = append(out, ba[i:]...)
out = append(out, []byte("\n\nB: ")...)
out = append(out, bb[i:]...)
out = append(out, []byte("\n\n")...)
return string(out)
}

View File

@ -153,3 +153,11 @@ func TestIntOrStringMarshalJSON(t *testing.T) {
} }
} }
} }
func TestStringDiff(t *testing.T) {
diff := StringDiff("aaabb", "aaacc")
expect := "aaa\n\nA: bb\n\nB: cc\n\n"
if diff != expect {
t.Errorf("diff returned %v", diff)
}
}