blob: 09c382033f92771809b0ccb8a47216187e524a8e [file] [log] [blame]
// Copyright 2016 The Vanadium 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 vdltest
import (
"fmt"
"math"
"math/rand"
"time"
"unicode/utf8"
"v.io/v23/vdl"
)
// ValueGenerator generates values.
type ValueGenerator struct {
Types []*vdl.Type
MaxLen int
MaxCycleDepth int
rng *rand.Rand
}
// NewValueGenerator returns a new ValueGenerator, which uses a random number
// generator seeded to the current time. The given types control what types are
// used to generate TypeObject and Any values.
func NewValueGenerator(types []*vdl.Type) *ValueGenerator {
return &ValueGenerator{
Types: types,
MaxLen: 3,
MaxCycleDepth: 3,
rng: rand.New(rand.NewSource(time.Now().Unix())),
}
}
// RandSeed sets the seed for the random number generator used by g.
func (g *ValueGenerator) RandSeed(seed int64) {
g.rng.Seed(seed)
}
// GenMode represents the mode used to generate values.
type GenMode int
const (
// GenFull generates deterministic values that are recursively non-zero,
// except for types that are part of a cycle, which must have a zero value in
// order to terminate.
GenFull GenMode = iota
GenPosMax // Generate maximal positive numbers.
GenPosMin // Generate minimal positive numbers.
GenNegMax // Generate maximal negative numbers.
GenNegMin // Generate minimal negative numbers.
// GenRandom generates random values that are never zero for the top-level
// type, but may be zero otherwise.
GenRandom
)
func (m GenMode) String() string {
switch m {
case GenFull:
return "Full"
case GenPosMax:
return "PosMax"
case GenPosMin:
return "PosMin"
case GenNegMax:
return "NegMax"
case GenNegMin:
return "NegMin"
case GenRandom:
return "Random"
}
panic(fmt.Errorf("vdltest: unhandled mode %d", m))
}
func needsGenPos(tt *vdl.Type) bool {
return tt.ContainsKind(vdl.WalkAll, vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64, vdl.Float32, vdl.Float64)
}
func needsGenNeg(tt *vdl.Type) bool {
return tt.ContainsKind(vdl.WalkAll, vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64, vdl.Float32, vdl.Float64)
}
func needsGenPosNeg(tt *vdl.Type, mode GenMode) bool {
switch mode {
case GenPosMax, GenPosMin:
return needsGenPos(tt)
case GenNegMax, GenNegMin:
return needsGenNeg(tt)
}
panic(fmt.Errorf("vdltest: unhandled mode %v", mode))
}
// needsGenFull returns true iff we should generate full values of type tt. We
// generate full values as long as there is more than one valid value of type
// tt; i.e. as long as the value can be non-zero.
func needsGenFull(tt *vdl.Type) bool {
switch kind := tt.Kind(); kind {
case vdl.Enum:
return tt.NumEnumLabel() > 1
case vdl.Array:
return needsGenFull(tt.Elem())
case vdl.Struct, vdl.Union:
if kind == vdl.Union && tt.NumField() > 1 {
// Any union with more than one field can be non-zero.
return true
}
for ix := 0; ix < tt.NumField(); ix++ {
if needsGenFull(tt.Field(ix).Type) {
return true
}
}
return false
default:
// Every other kind can be non-zero. Since cyclic types must have an
// Optional, List, Set or Map somewhere in the cycle, and those kinds are
// all handled here, there's no need for a seen map to avoid infinite loops.
return true
}
}
// needsGenRandom returns true iff we should generate random values of type tt.
// We don't generate random values if tt only has a small number of possible
// values.
func needsGenRandom(tt *vdl.Type) bool {
// TODO(toddw): Handle cyclic types.
switch kind := tt.Kind(); kind {
case vdl.Bool, vdl.Enum:
return false
case vdl.Array, vdl.List, vdl.Optional:
return needsGenRandom(tt.Elem())
case vdl.Set:
return needsGenRandom(tt.Key())
case vdl.Map:
return needsGenRandom(tt.Key()) || needsGenRandom(tt.Elem())
case vdl.Struct, vdl.Union:
for ix := 0; ix < tt.NumField(); ix++ {
if needsGenRandom(tt.Field(ix).Type) {
return true
}
}
return false
default:
// Every other kind needs GenRandom.
return true
}
}
// Gen returns a value of type tt, unless tt is Any, in which case the type of
// the returned value is picked randomly from all types. Use the mode to
// control properties of the returned value.
func (g *ValueGenerator) Gen(tt *vdl.Type, mode GenMode) *vdl.Value {
// The loop is for a special-case. If we're in GenRandom mode, and the value
// we generated is zero, and we're supposed to generate random values for tt,
// keep looping until we generate a non-zero value.
for {
value := g.gen(tt, mode, 0)
if mode != GenRandom || !value.IsZero() || !needsGenRandom(tt) {
return value
}
}
}
func (g *ValueGenerator) gen(tt *vdl.Type, mode GenMode, depth int) *vdl.Value {
if tt == vdl.AnyType {
if g.randomNil(tt, mode, depth) {
return vdl.ZeroValue(vdl.AnyType)
}
switch {
case mode == GenFull:
tt = vdl.Int64Type
default:
tt = g.randomNonAnyType()
}
}
if tt.Kind() == vdl.Optional {
if g.randomNil(tt, mode, depth) {
return vdl.ZeroValue(tt)
}
}
value := g.genNonNilValue(tt.NonOptional(), mode, depth)
if tt == vdl.ErrorType {
// HACK: The codegen for the error "ParamList []any" field doesn't work.
// We've hard-coded the vdl tool to represent any as interface{} in the
// vdltest package, but vdl.WireError expects ParamList as []*vdl.Value.
// This is hard to fix, so we just clear out the ParamList so that it
// doesn't show up in generated code.
//
// TODO(toddw): Fix this.
value.StructFieldByName("ParamList").AssignLen(0)
}
if tt.Kind() == vdl.Optional {
value = vdl.OptionalValue(value)
}
return value
}
func (g *ValueGenerator) genNonNilValue(tt *vdl.Type, mode GenMode, depth int) *vdl.Value {
bitlen := uint(tt.Kind().BitLen())
switch kind := tt.Kind(); kind {
case vdl.Bool:
switch mode {
case GenPosMax, GenPosMin, GenNegMax, GenNegMin:
return vdl.ZeroValue(tt)
case GenFull:
return vdl.BoolValue(tt, true)
default:
return vdl.BoolValue(tt, g.randomPercentage(50))
}
case vdl.String:
switch mode {
case GenPosMax, GenPosMin, GenNegMax, GenNegMin:
return vdl.ZeroValue(tt)
case GenFull:
return vdl.StringValue(tt, fullString)
default:
return vdl.StringValue(tt, g.randomString())
}
case vdl.Enum:
switch mode {
case GenPosMax, GenPosMin, GenNegMax, GenNegMin:
return vdl.ZeroValue(tt)
case GenFull:
return vdl.EnumValue(tt, tt.NumEnumLabel()-1)
default:
return vdl.EnumValue(tt, g.rng.Intn(tt.NumEnumLabel()))
}
case vdl.TypeObject:
switch mode {
case GenPosMax, GenPosMin, GenNegMax, GenNegMin:
return vdl.ZeroValue(tt)
case GenFull:
return vdl.TypeObjectValue(vdl.Int64Type)
default:
return vdl.TypeObjectValue(g.randomType())
}
case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64:
switch mode {
case GenNegMax, GenNegMin:
return vdl.ZeroValue(tt)
case GenPosMax:
return vdl.UintValue(tt, (uint64(1)<<bitlen)-1)
case GenPosMin:
return vdl.UintValue(tt, 1)
case GenFull:
return vdl.UintValue(tt, 11)
default:
return vdl.UintValue(tt, g.randomUint(bitlen))
}
case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64:
switch mode {
case GenPosMax:
return vdl.IntValue(tt, (int64(1)<<(bitlen-1))-1)
case GenNegMax:
return vdl.IntValue(tt, int64(-1)<<(bitlen-1))
case GenPosMin:
return vdl.IntValue(tt, 1)
case GenNegMin:
return vdl.IntValue(tt, -1)
case GenFull:
return vdl.IntValue(tt, -22)
default:
return vdl.IntValue(tt, g.randomInt(bitlen))
}
case vdl.Float32, vdl.Float64:
switch mode {
case GenPosMax:
return vdl.FloatValue(tt, g.maxFloat(kind))
case GenNegMax:
return vdl.FloatValue(tt, -g.maxFloat(kind))
case GenPosMin:
return vdl.FloatValue(tt, g.smallFloat(kind))
case GenNegMin:
return vdl.FloatValue(tt, -g.smallFloat(kind))
case GenFull:
return vdl.FloatValue(tt, 1.23)
default:
return vdl.FloatValue(tt, g.randomFloat())
}
case vdl.Array, vdl.List:
return g.genArrayList(tt, mode, depth)
case vdl.Set:
return g.genSet(tt, mode, depth)
case vdl.Map:
return g.genMap(tt, mode, depth)
case vdl.Struct:
return g.genStruct(tt, mode, depth)
case vdl.Union:
return g.genUnion(tt, mode, depth)
}
panic(fmt.Errorf("genNonNilValue unhandled type %v", tt))
}
func (g *ValueGenerator) genArrayList(tt *vdl.Type, mode GenMode, depth int) *vdl.Value {
value := vdl.ZeroValue(tt)
if tt.Kind() == vdl.List {
value.AssignLen(g.randomLen(tt, mode, depth))
}
for i := 0; i < value.Len(); i++ {
value.AssignIndex(i, g.gen(tt.Elem(), mode, depth+1))
}
return value
}
func (g *ValueGenerator) genSet(tt *vdl.Type, mode GenMode, depth int) *vdl.Value {
value := vdl.ZeroValue(tt)
for i, len := 0, g.randomLen(tt, mode, depth); i < len; i++ {
value.AssignSetKey(g.gen(tt.Key(), mode, depth+1))
}
return value
}
func (g *ValueGenerator) genMap(tt *vdl.Type, mode GenMode, depth int) *vdl.Value {
value := vdl.ZeroValue(tt)
for i, len := 0, g.randomLen(tt, mode, depth); i < len; i++ {
value.AssignMapIndex(g.gen(tt.Key(), mode, depth+1), g.gen(tt.Elem(), mode, depth+1))
}
return value
}
func (g *ValueGenerator) genStruct(tt *vdl.Type, mode GenMode, depth int) *vdl.Value {
value := vdl.ZeroValue(tt)
for i := 0; i < tt.NumField(); i++ {
value.AssignField(i, g.gen(tt.Field(i).Type, mode, depth+1))
}
return value
}
func (g *ValueGenerator) genUnion(tt *vdl.Type, mode GenMode, depth int) *vdl.Value {
var index int
switch mode {
case GenFull:
index = tt.NumField() - 1
case GenPosMax, GenPosMin, GenNegMax, GenNegMin:
if !needsGenPosNeg(tt, mode) {
return vdl.ZeroValue(tt)
}
index = findUnionPosNegField(tt, mode)
default:
index = g.rng.Intn(tt.NumField())
}
return vdl.UnionValue(tt, index, g.gen(tt.Field(index).Type, mode, depth+1))
}
// findUnionPosNegField returns the first field in tt that satisfies the mode.
//
// REQUIRES: tt must be a union, and must have a field that satisfies the mode.
// The mode must be one of GenPosMax, GenPosMin, GenNegMax, GenNegMin.
func findUnionPosNegField(tt *vdl.Type, mode GenMode) int {
for index := 0; index < tt.NumField(); index++ {
if needsGenPosNeg(tt.Field(index).Type, mode) {
return index
}
}
panic(fmt.Errorf("vdltest: no field in %v satisfies %v", tt, mode))
}
func (g *ValueGenerator) randomPercentage(p int) bool {
return g.rng.Intn(100) < p
}
func (g *ValueGenerator) randomUint(bitlen uint) uint64 {
u := uint64(g.rng.Uint32())<<32 + uint64(g.rng.Uint32())
shift := 64 - bitlen
return (u << shift) >> shift
}
func (g *ValueGenerator) randomInt(bitlen uint) int64 {
neg := int64(-1)
if g.randomPercentage(50) {
neg = 1
}
u := uint64(g.rng.Uint32())<<32 + uint64(g.rng.Uint32())
// The shift uses 65 since the topmost bit is the sign bit. I.e. 32 bit
// numbers should be shifted by 33 rather than 32.
shift := 65 - bitlen
return (int64(u<<shift) >> shift) * neg
}
func (g *ValueGenerator) maxFloat(kind vdl.Kind) float64 {
// TODO(toddw): Fix floating-point issues. Currently the MaxFloat* values
// don't convert correctly. There are Go bugs related to this:
// https://github.com/golang/go/issues/14651
if kind == vdl.Float32 {
return math.MaxFloat32 / 2
}
return math.MaxFloat64 / 2
}
func (g *ValueGenerator) smallFloat(kind vdl.Kind) float64 {
// TODO(toddw): Fix floating-point issues. Currently the Smallest* values
// don't convert correctly. There are Go bugs related to this:
// https://github.com/golang/go/issues/14651
if kind == vdl.Float32 {
return math.SmallestNonzeroFloat32 * 10
}
return math.SmallestNonzeroFloat64 * 10
}
func (g *ValueGenerator) randomFloat() float64 {
// Float64 returns in the range [0.0,1.0), so we adjust to a larger range.
neg := float64(-1)
if g.randomPercentage(50) {
neg = 1
}
return g.rng.Float64() * float64(g.rng.Uint32()) * neg
}
const fullString = "abcdefghijklmnopΔΘΠΣΦ王普澤世界"
func (g *ValueGenerator) randomString() string {
rLen := utf8.RuneCountInString(fullString)
r0, r1 := g.rng.Intn(rLen), g.rng.Intn(rLen)
if r0 > r1 {
r0, r1 = r1, r0
}
// Translate the startR and endR rune counts into byte indices.
rCount, start, end := 0, 0, len(fullString)
for i := range fullString {
if rCount == r0 {
start = i
}
if rCount == r1 {
end = i
}
rCount++
}
return fullString[start:end]
}
func (g *ValueGenerator) randomNil(tt *vdl.Type, mode GenMode, depth int) bool {
if tt.IsPartOfCycle() && depth > g.MaxCycleDepth {
return true
}
switch mode {
case GenFull:
return false
case GenPosMax, GenPosMin, GenNegMax, GenNegMin:
return !needsGenPosNeg(tt, mode)
}
return g.randomPercentage(30)
}
func (g *ValueGenerator) randomLen(tt *vdl.Type, mode GenMode, depth int) int {
if g.MaxLen == 0 {
return 0
}
if tt.IsPartOfCycle() && depth > g.MaxCycleDepth {
return 0
}
switch mode {
case GenFull:
return 1
case GenPosMax, GenPosMin, GenNegMax, GenNegMin:
if !needsGenPosNeg(tt, mode) {
return 0
}
return 1
}
return g.rng.Intn(g.MaxLen)
}
func (g *ValueGenerator) randomType() *vdl.Type {
if len(g.Types) == 0 {
return ttBuiltIn[g.rng.Intn(len(ttBuiltIn))]
}
return g.Types[g.rng.Intn(len(g.Types))]
}
func (g *ValueGenerator) randomNonAnyType() *vdl.Type {
for {
if tt := g.randomType(); tt != vdl.AnyType {
return tt
}
}
}