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" )
type Generator interface {
Generate (rand *rand .Rand , size int ) reflect .Value
}
func randFloat32(rand *rand .Rand ) float32 {
f := rand .Float64 () * math .MaxFloat32
if rand .Int ()&1 == 1 {
f = -f
}
return float32 (f )
}
func randFloat64(rand *rand .Rand ) float64 {
f := rand .Float64 () * math .MaxFloat64
if rand .Int ()&1 == 1 {
f = -f
}
return f
}
func randInt64(rand *rand .Rand ) int64 {
return int64 (rand .Uint64 ())
}
const complexSize = 50
func Value (t reflect .Type , rand *rand .Rand ) (value reflect .Value , ok bool ) {
return sizedValue (t , rand , complexSize )
}
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 .SetZero ()
} 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 ()
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
}
type Config struct {
MaxCount int
MaxCountScale float64
Rand *rand .Rand
Values func ([]reflect .Value , *rand .Rand )
}
var defaultConfig Config
func (c *Config ) getRand () *rand .Rand {
if c .Rand == nil {
return rand .New (rand .NewSource (time .Now ().UnixNano ()))
}
return c .Rand
}
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
}
type SetupError string
func (s SetupError ) Error () string { return string (s ) }
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 ))
}
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 ))
}
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
}
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
}
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 , ", " )
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .