Add go fuzzer in preparation for testing. Also gofmt a few files that needed it.
This commit is contained in:
parent
8a5cc87df8
commit
aa92dd7fb2
263
pkg/util/fuzz.go
Normal file
263
pkg/util/fuzz.go
Normal 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
229
pkg/util/fuzz_test.go
Normal 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")
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ func (s StringSet) HasAll(items ...string) bool {
|
||||
|
||||
// IsSuperset returns true iff s1 is a superset of s2.
|
||||
func (s1 StringSet) IsSuperset(s2 StringSet) bool {
|
||||
for item, _ := range s2 {
|
||||
for item := range s2 {
|
||||
if !s1.Has(item) {
|
||||
return false
|
||||
}
|
||||
|
@ -43,13 +43,13 @@ func TestStringSet(t *testing.T) {
|
||||
t.Errorf("Unexpected contents: %#v", s)
|
||||
}
|
||||
s.Insert("a")
|
||||
if s.HasAll("a","b","d") {
|
||||
if s.HasAll("a", "b", "d") {
|
||||
t.Errorf("Unexpected contents: %#v", s)
|
||||
}
|
||||
if !s.HasAll("a","b",) {
|
||||
if !s.HasAll("a", "b") {
|
||||
t.Errorf("Missing contents: %#v", s)
|
||||
}
|
||||
s2.Insert("a","b","d")
|
||||
s2.Insert("a", "b", "d")
|
||||
if s.IsSuperset(s2) {
|
||||
t.Errorf("Unexpected contents: %#v", s)
|
||||
}
|
||||
|
@ -132,3 +132,23 @@ func (intstr IntOrString) MarshalJSON() ([]byte, error) {
|
||||
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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user