diff options
Diffstat (limited to '')
-rw-r--r-- | src/testing/quick/quick.go | 385 | ||||
-rw-r--r-- | src/testing/quick/quick_test.go | 327 |
2 files changed, 712 insertions, 0 deletions
diff --git a/src/testing/quick/quick.go b/src/testing/quick/quick.go new file mode 100644 index 0000000..95a635b --- /dev/null +++ b/src/testing/quick/quick.go @@ -0,0 +1,385 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package quick implements utility functions to help with black box testing. +// +// The testing/quick package is frozen and is not accepting new features. +package quick + +import ( + "flag" + "fmt" + "math" + "math/rand" + "reflect" + "strings" + "time" +) + +var defaultMaxCount *int = flag.Int("quickchecks", 100, "The default number of iterations for each check") + +// A Generator can generate random values of its own type. +type Generator interface { + // Generate returns a random instance of the type on which it is a + // method using the size as a size hint. + Generate(rand *rand.Rand, size int) reflect.Value +} + +// randFloat32 generates a random float taking the full range of a float32. +func randFloat32(rand *rand.Rand) float32 { + f := rand.Float64() * math.MaxFloat32 + if rand.Int()&1 == 1 { + f = -f + } + return float32(f) +} + +// randFloat64 generates a random float taking the full range of a float64. +func randFloat64(rand *rand.Rand) float64 { + f := rand.Float64() * math.MaxFloat64 + if rand.Int()&1 == 1 { + f = -f + } + return f +} + +// randInt64 returns a random int64. +func randInt64(rand *rand.Rand) int64 { + return int64(rand.Uint64()) +} + +// complexSize is the maximum length of arbitrary values that contain other +// values. +const complexSize = 50 + +// Value returns an arbitrary value of the given type. +// If the type implements the Generator interface, that will be used. +// Note: To create arbitrary values for structs, all the fields must be exported. +func Value(t reflect.Type, rand *rand.Rand) (value reflect.Value, ok bool) { + return sizedValue(t, rand, complexSize) +} + +// sizedValue returns an arbitrary value of the given type. The size +// hint is used for shrinking as a function of indirection level so +// that recursive data structures will terminate. +func sizedValue(t reflect.Type, rand *rand.Rand, size int) (value reflect.Value, ok bool) { + if m, ok := reflect.Zero(t).Interface().(Generator); ok { + return m.Generate(rand, size), true + } + + v := reflect.New(t).Elem() + switch concrete := t; concrete.Kind() { + case reflect.Bool: + v.SetBool(rand.Int()&1 == 0) + case reflect.Float32: + v.SetFloat(float64(randFloat32(rand))) + case reflect.Float64: + v.SetFloat(randFloat64(rand)) + case reflect.Complex64: + v.SetComplex(complex(float64(randFloat32(rand)), float64(randFloat32(rand)))) + case reflect.Complex128: + v.SetComplex(complex(randFloat64(rand), randFloat64(rand))) + case reflect.Int16: + v.SetInt(randInt64(rand)) + case reflect.Int32: + v.SetInt(randInt64(rand)) + case reflect.Int64: + v.SetInt(randInt64(rand)) + case reflect.Int8: + v.SetInt(randInt64(rand)) + case reflect.Int: + v.SetInt(randInt64(rand)) + case reflect.Uint16: + v.SetUint(uint64(randInt64(rand))) + case reflect.Uint32: + v.SetUint(uint64(randInt64(rand))) + case reflect.Uint64: + v.SetUint(uint64(randInt64(rand))) + case reflect.Uint8: + v.SetUint(uint64(randInt64(rand))) + case reflect.Uint: + v.SetUint(uint64(randInt64(rand))) + case reflect.Uintptr: + v.SetUint(uint64(randInt64(rand))) + case reflect.Map: + numElems := rand.Intn(size) + v.Set(reflect.MakeMap(concrete)) + for i := 0; i < numElems; i++ { + key, ok1 := sizedValue(concrete.Key(), rand, size) + value, ok2 := sizedValue(concrete.Elem(), rand, size) + if !ok1 || !ok2 { + return reflect.Value{}, false + } + v.SetMapIndex(key, value) + } + case reflect.Pointer: + if rand.Intn(size) == 0 { + v.Set(reflect.Zero(concrete)) // Generate nil pointer. + } else { + elem, ok := sizedValue(concrete.Elem(), rand, size) + if !ok { + return reflect.Value{}, false + } + v.Set(reflect.New(concrete.Elem())) + v.Elem().Set(elem) + } + case reflect.Slice: + numElems := rand.Intn(size) + sizeLeft := size - numElems + v.Set(reflect.MakeSlice(concrete, numElems, numElems)) + for i := 0; i < numElems; i++ { + elem, ok := sizedValue(concrete.Elem(), rand, sizeLeft) + if !ok { + return reflect.Value{}, false + } + v.Index(i).Set(elem) + } + case reflect.Array: + for i := 0; i < v.Len(); i++ { + elem, ok := sizedValue(concrete.Elem(), rand, size) + if !ok { + return reflect.Value{}, false + } + v.Index(i).Set(elem) + } + case reflect.String: + numChars := rand.Intn(complexSize) + codePoints := make([]rune, numChars) + for i := 0; i < numChars; i++ { + codePoints[i] = rune(rand.Intn(0x10ffff)) + } + v.SetString(string(codePoints)) + case reflect.Struct: + n := v.NumField() + // Divide sizeLeft evenly among the struct fields. + sizeLeft := size + if n > sizeLeft { + sizeLeft = 1 + } else if n > 0 { + sizeLeft /= n + } + for i := 0; i < n; i++ { + elem, ok := sizedValue(concrete.Field(i).Type, rand, sizeLeft) + if !ok { + return reflect.Value{}, false + } + v.Field(i).Set(elem) + } + default: + return reflect.Value{}, false + } + + return v, true +} + +// A Config structure contains options for running a test. +type Config struct { + // MaxCount sets the maximum number of iterations. + // If zero, MaxCountScale is used. + MaxCount int + // MaxCountScale is a non-negative scale factor applied to the + // default maximum. + // A count of zero implies the default, which is usually 100 + // but can be set by the -quickchecks flag. + MaxCountScale float64 + // Rand specifies a source of random numbers. + // If nil, a default pseudo-random source will be used. + Rand *rand.Rand + // Values specifies a function to generate a slice of + // arbitrary reflect.Values that are congruent with the + // arguments to the function being tested. + // If nil, the top-level Value function is used to generate them. + Values func([]reflect.Value, *rand.Rand) +} + +var defaultConfig Config + +// getRand returns the *rand.Rand to use for a given Config. +func (c *Config) getRand() *rand.Rand { + if c.Rand == nil { + return rand.New(rand.NewSource(time.Now().UnixNano())) + } + return c.Rand +} + +// getMaxCount returns the maximum number of iterations to run for a given +// Config. +func (c *Config) getMaxCount() (maxCount int) { + maxCount = c.MaxCount + if maxCount == 0 { + if c.MaxCountScale != 0 { + maxCount = int(c.MaxCountScale * float64(*defaultMaxCount)) + } else { + maxCount = *defaultMaxCount + } + } + + return +} + +// A SetupError is the result of an error in the way that check is being +// used, independent of the functions being tested. +type SetupError string + +func (s SetupError) Error() string { return string(s) } + +// A CheckError is the result of Check finding an error. +type CheckError struct { + Count int + In []any +} + +func (s *CheckError) Error() string { + return fmt.Sprintf("#%d: failed on input %s", s.Count, toString(s.In)) +} + +// A CheckEqualError is the result CheckEqual finding an error. +type CheckEqualError struct { + CheckError + Out1 []any + Out2 []any +} + +func (s *CheckEqualError) Error() string { + return fmt.Sprintf("#%d: failed on input %s. Output 1: %s. Output 2: %s", s.Count, toString(s.In), toString(s.Out1), toString(s.Out2)) +} + +// Check looks for an input to f, any function that returns bool, +// such that f returns false. It calls f repeatedly, with arbitrary +// values for each argument. If f returns false on a given input, +// Check returns that input as a *CheckError. +// For example: +// +// func TestOddMultipleOfThree(t *testing.T) { +// f := func(x int) bool { +// y := OddMultipleOfThree(x) +// return y%2 == 1 && y%3 == 0 +// } +// if err := quick.Check(f, nil); err != nil { +// t.Error(err) +// } +// } +func Check(f any, config *Config) error { + if config == nil { + config = &defaultConfig + } + + fVal, fType, ok := functionAndType(f) + if !ok { + return SetupError("argument is not a function") + } + + if fType.NumOut() != 1 { + return SetupError("function does not return one value") + } + if fType.Out(0).Kind() != reflect.Bool { + return SetupError("function does not return a bool") + } + + arguments := make([]reflect.Value, fType.NumIn()) + rand := config.getRand() + maxCount := config.getMaxCount() + + for i := 0; i < maxCount; i++ { + err := arbitraryValues(arguments, fType, config, rand) + if err != nil { + return err + } + + if !fVal.Call(arguments)[0].Bool() { + return &CheckError{i + 1, toInterfaces(arguments)} + } + } + + return nil +} + +// CheckEqual looks for an input on which f and g return different results. +// It calls f and g repeatedly with arbitrary values for each argument. +// If f and g return different answers, CheckEqual returns a *CheckEqualError +// describing the input and the outputs. +func CheckEqual(f, g any, config *Config) error { + if config == nil { + config = &defaultConfig + } + + x, xType, ok := functionAndType(f) + if !ok { + return SetupError("f is not a function") + } + y, yType, ok := functionAndType(g) + if !ok { + return SetupError("g is not a function") + } + + if xType != yType { + return SetupError("functions have different types") + } + + arguments := make([]reflect.Value, xType.NumIn()) + rand := config.getRand() + maxCount := config.getMaxCount() + + for i := 0; i < maxCount; i++ { + err := arbitraryValues(arguments, xType, config, rand) + if err != nil { + return err + } + + xOut := toInterfaces(x.Call(arguments)) + yOut := toInterfaces(y.Call(arguments)) + + if !reflect.DeepEqual(xOut, yOut) { + return &CheckEqualError{CheckError{i + 1, toInterfaces(arguments)}, xOut, yOut} + } + } + + return nil +} + +// arbitraryValues writes Values to args such that args contains Values +// suitable for calling f. +func arbitraryValues(args []reflect.Value, f reflect.Type, config *Config, rand *rand.Rand) (err error) { + if config.Values != nil { + config.Values(args, rand) + return + } + + for j := 0; j < len(args); j++ { + var ok bool + args[j], ok = Value(f.In(j), rand) + if !ok { + err = SetupError(fmt.Sprintf("cannot create arbitrary value of type %s for argument %d", f.In(j), j)) + return + } + } + + return +} + +func functionAndType(f any) (v reflect.Value, t reflect.Type, ok bool) { + v = reflect.ValueOf(f) + ok = v.Kind() == reflect.Func + if !ok { + return + } + t = v.Type() + return +} + +func toInterfaces(values []reflect.Value) []any { + ret := make([]any, len(values)) + for i, v := range values { + ret[i] = v.Interface() + } + return ret +} + +func toString(interfaces []any) string { + s := make([]string, len(interfaces)) + for i, v := range interfaces { + s[i] = fmt.Sprintf("%#v", v) + } + return strings.Join(s, ", ") +} diff --git a/src/testing/quick/quick_test.go b/src/testing/quick/quick_test.go new file mode 100644 index 0000000..9df6dd4 --- /dev/null +++ b/src/testing/quick/quick_test.go @@ -0,0 +1,327 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package quick + +import ( + "math/rand" + "reflect" + "testing" +) + +func fArray(a [4]byte) [4]byte { return a } + +type TestArrayAlias [4]byte + +func fArrayAlias(a TestArrayAlias) TestArrayAlias { return a } + +func fBool(a bool) bool { return a } + +type TestBoolAlias bool + +func fBoolAlias(a TestBoolAlias) TestBoolAlias { return a } + +func fFloat32(a float32) float32 { return a } + +type TestFloat32Alias float32 + +func fFloat32Alias(a TestFloat32Alias) TestFloat32Alias { return a } + +func fFloat64(a float64) float64 { return a } + +type TestFloat64Alias float64 + +func fFloat64Alias(a TestFloat64Alias) TestFloat64Alias { return a } + +func fComplex64(a complex64) complex64 { return a } + +type TestComplex64Alias complex64 + +func fComplex64Alias(a TestComplex64Alias) TestComplex64Alias { return a } + +func fComplex128(a complex128) complex128 { return a } + +type TestComplex128Alias complex128 + +func fComplex128Alias(a TestComplex128Alias) TestComplex128Alias { return a } + +func fInt16(a int16) int16 { return a } + +type TestInt16Alias int16 + +func fInt16Alias(a TestInt16Alias) TestInt16Alias { return a } + +func fInt32(a int32) int32 { return a } + +type TestInt32Alias int32 + +func fInt32Alias(a TestInt32Alias) TestInt32Alias { return a } + +func fInt64(a int64) int64 { return a } + +type TestInt64Alias int64 + +func fInt64Alias(a TestInt64Alias) TestInt64Alias { return a } + +func fInt8(a int8) int8 { return a } + +type TestInt8Alias int8 + +func fInt8Alias(a TestInt8Alias) TestInt8Alias { return a } + +func fInt(a int) int { return a } + +type TestIntAlias int + +func fIntAlias(a TestIntAlias) TestIntAlias { return a } + +func fMap(a map[int]int) map[int]int { return a } + +type TestMapAlias map[int]int + +func fMapAlias(a TestMapAlias) TestMapAlias { return a } + +func fPtr(a *int) *int { + if a == nil { + return nil + } + b := *a + return &b +} + +type TestPtrAlias *int + +func fPtrAlias(a TestPtrAlias) TestPtrAlias { return a } + +func fSlice(a []byte) []byte { return a } + +type TestSliceAlias []byte + +func fSliceAlias(a TestSliceAlias) TestSliceAlias { return a } + +func fString(a string) string { return a } + +type TestStringAlias string + +func fStringAlias(a TestStringAlias) TestStringAlias { return a } + +type TestStruct struct { + A int + B string +} + +func fStruct(a TestStruct) TestStruct { return a } + +type TestStructAlias TestStruct + +func fStructAlias(a TestStructAlias) TestStructAlias { return a } + +func fUint16(a uint16) uint16 { return a } + +type TestUint16Alias uint16 + +func fUint16Alias(a TestUint16Alias) TestUint16Alias { return a } + +func fUint32(a uint32) uint32 { return a } + +type TestUint32Alias uint32 + +func fUint32Alias(a TestUint32Alias) TestUint32Alias { return a } + +func fUint64(a uint64) uint64 { return a } + +type TestUint64Alias uint64 + +func fUint64Alias(a TestUint64Alias) TestUint64Alias { return a } + +func fUint8(a uint8) uint8 { return a } + +type TestUint8Alias uint8 + +func fUint8Alias(a TestUint8Alias) TestUint8Alias { return a } + +func fUint(a uint) uint { return a } + +type TestUintAlias uint + +func fUintAlias(a TestUintAlias) TestUintAlias { return a } + +func fUintptr(a uintptr) uintptr { return a } + +type TestUintptrAlias uintptr + +func fUintptrAlias(a TestUintptrAlias) TestUintptrAlias { return a } + +func reportError(property string, err error, t *testing.T) { + if err != nil { + t.Errorf("%s: %s", property, err) + } +} + +func TestCheckEqual(t *testing.T) { + reportError("fArray", CheckEqual(fArray, fArray, nil), t) + reportError("fArrayAlias", CheckEqual(fArrayAlias, fArrayAlias, nil), t) + reportError("fBool", CheckEqual(fBool, fBool, nil), t) + reportError("fBoolAlias", CheckEqual(fBoolAlias, fBoolAlias, nil), t) + reportError("fFloat32", CheckEqual(fFloat32, fFloat32, nil), t) + reportError("fFloat32Alias", CheckEqual(fFloat32Alias, fFloat32Alias, nil), t) + reportError("fFloat64", CheckEqual(fFloat64, fFloat64, nil), t) + reportError("fFloat64Alias", CheckEqual(fFloat64Alias, fFloat64Alias, nil), t) + reportError("fComplex64", CheckEqual(fComplex64, fComplex64, nil), t) + reportError("fComplex64Alias", CheckEqual(fComplex64Alias, fComplex64Alias, nil), t) + reportError("fComplex128", CheckEqual(fComplex128, fComplex128, nil), t) + reportError("fComplex128Alias", CheckEqual(fComplex128Alias, fComplex128Alias, nil), t) + reportError("fInt16", CheckEqual(fInt16, fInt16, nil), t) + reportError("fInt16Alias", CheckEqual(fInt16Alias, fInt16Alias, nil), t) + reportError("fInt32", CheckEqual(fInt32, fInt32, nil), t) + reportError("fInt32Alias", CheckEqual(fInt32Alias, fInt32Alias, nil), t) + reportError("fInt64", CheckEqual(fInt64, fInt64, nil), t) + reportError("fInt64Alias", CheckEqual(fInt64Alias, fInt64Alias, nil), t) + reportError("fInt8", CheckEqual(fInt8, fInt8, nil), t) + reportError("fInt8Alias", CheckEqual(fInt8Alias, fInt8Alias, nil), t) + reportError("fInt", CheckEqual(fInt, fInt, nil), t) + reportError("fIntAlias", CheckEqual(fIntAlias, fIntAlias, nil), t) + reportError("fInt32", CheckEqual(fInt32, fInt32, nil), t) + reportError("fInt32Alias", CheckEqual(fInt32Alias, fInt32Alias, nil), t) + reportError("fMap", CheckEqual(fMap, fMap, nil), t) + reportError("fMapAlias", CheckEqual(fMapAlias, fMapAlias, nil), t) + reportError("fPtr", CheckEqual(fPtr, fPtr, nil), t) + reportError("fPtrAlias", CheckEqual(fPtrAlias, fPtrAlias, nil), t) + reportError("fSlice", CheckEqual(fSlice, fSlice, nil), t) + reportError("fSliceAlias", CheckEqual(fSliceAlias, fSliceAlias, nil), t) + reportError("fString", CheckEqual(fString, fString, nil), t) + reportError("fStringAlias", CheckEqual(fStringAlias, fStringAlias, nil), t) + reportError("fStruct", CheckEqual(fStruct, fStruct, nil), t) + reportError("fStructAlias", CheckEqual(fStructAlias, fStructAlias, nil), t) + reportError("fUint16", CheckEqual(fUint16, fUint16, nil), t) + reportError("fUint16Alias", CheckEqual(fUint16Alias, fUint16Alias, nil), t) + reportError("fUint32", CheckEqual(fUint32, fUint32, nil), t) + reportError("fUint32Alias", CheckEqual(fUint32Alias, fUint32Alias, nil), t) + reportError("fUint64", CheckEqual(fUint64, fUint64, nil), t) + reportError("fUint64Alias", CheckEqual(fUint64Alias, fUint64Alias, nil), t) + reportError("fUint8", CheckEqual(fUint8, fUint8, nil), t) + reportError("fUint8Alias", CheckEqual(fUint8Alias, fUint8Alias, nil), t) + reportError("fUint", CheckEqual(fUint, fUint, nil), t) + reportError("fUintAlias", CheckEqual(fUintAlias, fUintAlias, nil), t) + reportError("fUintptr", CheckEqual(fUintptr, fUintptr, nil), t) + reportError("fUintptrAlias", CheckEqual(fUintptrAlias, fUintptrAlias, nil), t) +} + +// This tests that ArbitraryValue is working by checking that all the arbitrary +// values of type MyStruct have x = 42. +type myStruct struct { + x int +} + +func (m myStruct) Generate(r *rand.Rand, _ int) reflect.Value { + return reflect.ValueOf(myStruct{x: 42}) +} + +func myStructProperty(in myStruct) bool { return in.x == 42 } + +func TestCheckProperty(t *testing.T) { + reportError("myStructProperty", Check(myStructProperty, nil), t) +} + +func TestFailure(t *testing.T) { + f := func(x int) bool { return false } + err := Check(f, nil) + if err == nil { + t.Errorf("Check didn't return an error") + } + if _, ok := err.(*CheckError); !ok { + t.Errorf("Error was not a CheckError: %s", err) + } + + err = CheckEqual(fUint, fUint32, nil) + if err == nil { + t.Errorf("#1 CheckEqual didn't return an error") + } + if _, ok := err.(SetupError); !ok { + t.Errorf("#1 Error was not a SetupError: %s", err) + } + + err = CheckEqual(func(x, y int) {}, func(x int) {}, nil) + if err == nil { + t.Errorf("#2 CheckEqual didn't return an error") + } + if _, ok := err.(SetupError); !ok { + t.Errorf("#2 Error was not a SetupError: %s", err) + } + + err = CheckEqual(func(x int) int { return 0 }, func(x int) int32 { return 0 }, nil) + if err == nil { + t.Errorf("#3 CheckEqual didn't return an error") + } + if _, ok := err.(SetupError); !ok { + t.Errorf("#3 Error was not a SetupError: %s", err) + } +} + +// Recursive data structures didn't terminate. +// Issues 8818 and 11148. +func TestRecursive(t *testing.T) { + type R struct { + Ptr *R + SliceP []*R + Slice []R + Map map[int]R + MapP map[int]*R + MapR map[*R]*R + SliceMap []map[int]R + } + + f := func(r R) bool { return true } + Check(f, nil) +} + +func TestEmptyStruct(t *testing.T) { + f := func(struct{}) bool { return true } + Check(f, nil) +} + +type ( + A struct{ B *B } + B struct{ A *A } +) + +func TestMutuallyRecursive(t *testing.T) { + f := func(a A) bool { return true } + Check(f, nil) +} + +// Some serialization formats (e.g. encoding/pem) cannot distinguish +// between a nil and an empty map or slice, so avoid generating the +// zero value for these. +func TestNonZeroSliceAndMap(t *testing.T) { + type Q struct { + M map[int]int + S []int + } + f := func(q Q) bool { + return q.M != nil && q.S != nil + } + err := Check(f, nil) + if err != nil { + t.Fatal(err) + } +} + +func TestInt64(t *testing.T) { + var lo, hi int64 + f := func(x int64) bool { + if x < lo { + lo = x + } + if x > hi { + hi = x + } + return true + } + cfg := &Config{MaxCount: 10000} + Check(f, cfg) + if uint64(lo)>>62 == 0 || uint64(hi)>>62 == 0 { + t.Errorf("int64 returned range %#016x,%#016x; does not look like full range", lo, hi) + } +} |