| // 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 contains the total set of types used for creating any and typeobject |
| // values. It's initialized by NewValueGenerator. |
| Types []*vdl.Type |
| // RandomZeroPercentage specifies the percentage from [0,100] that we will |
| // return a zero value. |
| RandomZeroPercentage int |
| // MaxLen limits the maximum length of lists, sets and maps. |
| MaxLen int |
| // MaxCycleDepth limits the depth for values of cyclic types. |
| 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, |
| RandomZeroPercentage: 20, |
| 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 { |
| if mode == GenRandom && depth > 0 && g.randomZero() { |
| return vdl.ZeroValue(tt) |
| } |
| kind := tt.Kind() |
| bitlen := uint(kind.BitLen()) |
| switch 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, 123) |
| 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, -123) |
| 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.5) |
| 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) randomZero() bool { |
| return g.randomPercentage(g.RandomZeroPercentage) |
| } |
| |
| 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 = "abcdeΔΘΠΣΦ王普澤世界" |
| |
| 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 |
| } |
| if r0 == r1 { |
| r1 = rLen |
| } |
| // 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.randomZero() |
| } |
| |
| 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 1 + g.rng.Intn(g.MaxLen) |
| } |
| |
| func (g *ValueGenerator) randomType() *vdl.Type { |
| if g.randomZero() { |
| return vdl.AnyType |
| } |
| return g.randomNonAnyType() |
| } |
| |
| func (g *ValueGenerator) randomNonAnyType() *vdl.Type { |
| for { |
| var tt *vdl.Type |
| if len(g.Types) > 0 { |
| tt = g.Types[g.rng.Intn(len(g.Types))] |
| } else { |
| tt = ttBuiltIn[g.rng.Intn(len(ttBuiltIn))] |
| } |
| if tt != vdl.AnyType { |
| return tt |
| } |
| } |
| } |