blob: 26214530a84f2bf80e7a6922f650964e077a52fd [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/rand"
"sort"
"strconv"
"strings"
"time"
"v.io/v23/vdl"
)
// TypeGenerator generates types.
type TypeGenerator struct {
// NamePrefix is the type name prefix for named types.
NamePrefix string
// BaseTypesPerKind is the number of base types of each kind to use when
// generating composite types at each depth. Numbers are considered to be a
// single kind.
//
// Each element corresponds to the value at that depth, starting at depth 1.
// Use -1 to indicate "unlimited".
BaseTypesPerKind []int
// FieldsPerKind is like BaseTypesPerKind, but limits the number of fields to
// generate for structs and unions.
FieldsPerKind []int
// MaxArrayLen is the maximum array length; the actual length is chosen
// randomly up to this max.
MaxArrayLen int
rng *rand.Rand
}
// NewTypeGenerator returns a new TypeGenerator, which uses a random number
// generator seeded to the current time.
func NewTypeGenerator() *TypeGenerator {
return &TypeGenerator{
NamePrefix: "V",
BaseTypesPerKind: []int{3, 1},
FieldsPerKind: []int{-1, 2, 1},
MaxArrayLen: 3,
rng: rand.New(rand.NewSource(time.Now().Unix())),
}
}
// RandSeed sets the seed for the random number generator used by g.
func (g *TypeGenerator) RandSeed(seed int64) {
g.rng.Seed(seed)
}
var ttBuiltIn = []*vdl.Type{
vdl.AnyType,
vdl.BoolType,
vdl.StringType,
vdl.TypeObjectType,
vdl.ByteType,
vdl.Uint16Type,
vdl.Uint32Type,
vdl.Uint64Type,
vdl.Int8Type,
vdl.Int16Type,
vdl.Int32Type,
vdl.Int64Type,
vdl.Float32Type,
vdl.Float64Type,
}
// Gen generates types up to and including the given maxDepth.
//
// Depth 0 only includes scalar types. Depth N>0 includes composite types built
// out of types from depth N-1. Optional types are considered to be at the same
// depth as their elem type, except for depth 0; optional types are not scalar.
//
// See the TypeGenerator exported fields for additional configuration options.
func (g *TypeGenerator) Gen(maxDepth int) []*vdl.Type {
if maxDepth < 0 {
return nil
}
depth0 := append([]*vdl.Type(nil), ttBuiltIn...)
depth0 = append(depth0, g.genNamed(depth0...)...)
unnamedEnums := []*vdl.Type{
vdl.EnumType("A", "B", "C"),
vdl.EnumType("B", "C", "D"),
}
depth0 = append(depth0, g.genNamed(unnamedEnums...)...) // enums must be named
// Depth 0 only includes scalars.
if maxDepth == 0 {
return joinTypes(splitDepth0Types(depth0))
}
// Special-cases that would have been included in depth 0, had we not
// special-cased depth 0 to only include scalars. We include a named empty
// struct and the error type.
depth0 = append(depth0, vdl.NamedType(g.NamePrefix+"StructEmpty", vdl.StructType()))
depth0 = append(depth0, vdl.ErrorType)
// NamedError is a type that is compatible with the built-in error type. It
// intentionally doesn't have the RetryCode or ParamList fields, to test error
// compatibility with other types.
depth0 = append(depth0, vdl.NamedType(g.NamePrefix+"NamedError", vdl.StructType(
vdl.Field{Name: "Id", Type: vdl.StringType},
vdl.Field{Name: "Msg", Type: vdl.StringType})))
// Generate composite types for each depth N, based on the base types from
// depth N-1. We keep the base types in separate buckets per kind, so that
// when we prune types we get a good distribution of different kinds.
base := splitDepth0Types(depth0)
base = append(base, g.genOptional(base, 1))
result := joinTypes(base)
for depth := 1; depth <= maxDepth; depth++ {
var next [][]*vdl.Type
next = append(next, g.genArray(base, depth))
next = append(next, g.genList(base, depth))
next = append(next, g.genSetMap(vdl.Set, base, depth))
next = append(next, g.genSetMap(vdl.Map, base, depth))
next = append(next, g.genStructUnion(vdl.Struct, base, depth))
next = append(next, g.genStructUnion(vdl.Union, base, depth))
// Make optionals out of all the composite types we've generated. We
// consider optionals to be at the same depth as their elem type.
next = append(next, g.genOptional(next, depth))
// Append to our running result, and update our next base types.
result = append(result, joinTypes(next)...)
base = next
}
return result
}
func joinTypes(buckets [][]*vdl.Type) []*vdl.Type {
var types []*vdl.Type
for _, bucket := range buckets {
types = append(types, bucket...)
}
return types
}
// splitDepth0Types splits depth0 into separate buckets grouped by kind. All
// numbers are grouped together into a single bucket. The relative ordering of
// types is maintained in each bucket.
func splitDepth0Types(depth0 []*vdl.Type) [][]*vdl.Type {
// split[1] holds buckets for numbers, while split[0] holds buckets for
// everything else.
var split [2][][]*vdl.Type
CollectLoop:
for _, tt := range depth0 {
kind, isNum := tt.Kind(), 0
// We special-case byte, to ensure byte lists and arrays are generated.
if kind.IsNumber() && kind != vdl.Byte {
isNum = 1
}
for ix, bucket := range split[isNum] {
if kind == bucket[0].Kind() {
// We've seen a type of this kind before, add it to this bucket
split[isNum][ix] = append(split[isNum][ix], tt)
continue CollectLoop
}
}
// This is the first time we've seen a type of this kind, add a new bucket.
split[isNum] = append(split[isNum], []*vdl.Type{tt})
}
// Add all numbers at the end in their own bucket.
return append(split[0], joinTypes(split[1]))
}
func valueAtDepth(values []int, depth int) int {
depth-- // values starts at depth 1
switch len := len(values); {
case len == 0:
return -1
case depth < len:
return values[depth]
default:
return values[len-1] // use last element for subsequent depths.
}
}
func (g *TypeGenerator) basePerKind(depth int) int {
return valueAtDepth(g.BaseTypesPerKind, depth)
}
// randomChoose returns x sorted values out of [0,n).
// REQUIRES: x <= n
func (g *TypeGenerator) randomChoose(n, x int) []int {
perm := g.rng.Perm(n)[:x]
sort.Ints(perm)
return perm
}
// prune returns maxPerBucket randomly picked types from each slice in buckets.
// The returned types remain in the same relative order.
func (g *TypeGenerator) prune(buckets [][]*vdl.Type, maxPerBucket int) []*vdl.Type {
if maxPerBucket == 0 {
return nil
}
var result []*vdl.Type
for _, bucket := range buckets {
if maxPerBucket < 0 || maxPerBucket >= len(bucket) {
result = append(result, bucket...)
continue
}
max := maxPerBucket
// INVARIANT: max > 0 && max < len(bucket)
if k := bucket[0].Kind(); k == vdl.Struct || k == vdl.Union {
// Pick the first struct or union, which is the "all fields" type.
// Picking this type increases the odds that we'll be able to mimic values
// that are convertible.
result = append(result, bucket[0])
bucket = bucket[1:]
max--
}
// Choose the remaining types randomly, maintaining the relative order.
for _, p := range g.randomChoose(len(bucket), max) {
result = append(result, bucket[p])
}
}
return result
}
func typeName(tt *vdl.Type) string {
// Handle the special-cases.
switch {
case tt == vdl.TypeObjectType:
return "TypeObject" // by default we'd get Typeobject
case tt == vdl.ErrorType:
return "Error" // by default we'd get Opterror
case tt.Kind() == vdl.Optional:
return "Opt" + typeName(tt.Elem())
case tt.Name() != "":
return tt.Name()
}
name := strings.Title(tt.Kind().String())
switch tt.Kind() {
case vdl.Enum:
var tmp string
for i := 0; i < tt.NumEnumLabel(); i++ {
tmp += tt.EnumLabel(i)
}
// VDL prohibits acronyms in identifiers, so we use Abc rather than ABC.
name += strings.Title(strings.ToLower(tmp))
case vdl.Array:
name += strconv.Itoa(tt.Len()) + "_" + typeName(tt.Elem())
case vdl.List:
name += "_" + typeName(tt.Elem())
case vdl.Set:
name += "_" + typeName(tt.Key())
case vdl.Map:
name += "_" + typeName(tt.Key()) + "_" + typeName(tt.Elem())
case vdl.Struct, vdl.Union:
panic(fmt.Errorf("vdltest: structs must be named in genStructUnion: %v", tt))
}
return name
}
func (g *TypeGenerator) genNamed(base ...*vdl.Type) []*vdl.Type {
var named []*vdl.Type
for _, tt := range base {
// TODO(toddw): Resolve bug in vdl to disallow Optional from being named.
if !tt.CanBeNamed() || tt.Kind() == vdl.Optional {
continue
}
named = append(named, vdl.NamedType(g.NamePrefix+typeName(tt), tt))
}
return named
}
func (g *TypeGenerator) genArray(base [][]*vdl.Type, depth int) []*vdl.Type {
// Create arrays out of pruned buckets. There's no need to collect all arrays
// that can be created first, since all types may be used as array elems.
var unnamed []*vdl.Type
for _, tt := range g.prune(base, g.basePerKind(depth)) {
len := 1 + g.rng.Intn(g.MaxArrayLen)
unnamed = append(unnamed, vdl.ArrayType(len, tt))
}
return g.genNamed(unnamed...) // arrays must be named
}
func (g *TypeGenerator) genList(base [][]*vdl.Type, depth int) []*vdl.Type {
// Create lists out of pruned buckets. There's no need to collect all lists
// that can be created first, since all types may be used as list elems.
var unnamed, named []*vdl.Type
for _, tt := range g.prune(base, g.basePerKind(depth)) {
unnamed = append(unnamed, vdl.ListType(tt))
}
for _, tt := range g.prune(base, g.basePerKind(depth)) {
named = append(named, g.genNamed(vdl.ListType(tt))...)
}
return append(unnamed, named...)
}
func (g *TypeGenerator) genSetMap(kind vdl.Kind, base [][]*vdl.Type, depth int) []*vdl.Type {
// First collect all sets and maps that can be created, by bucket. We can't
// prune before this, since many types can't be used as keys.
var allUnnamed, allNamed [][]*vdl.Type
for _, bucket := range base {
var unnamed []*vdl.Type
for _, tt := range bucket {
if tt.CanBeKey() {
if kind == vdl.Set {
unnamed = append(unnamed, vdl.SetType(tt))
} else {
unnamed = append(unnamed, vdl.MapType(tt, tt))
}
}
}
allUnnamed = append(allUnnamed, unnamed)
allNamed = append(allNamed, g.genNamed(unnamed...))
}
// Return pruned types.
max := g.basePerKind(depth)
return append(g.prune(allUnnamed, max), g.prune(allNamed, max)...)
}
func (g *TypeGenerator) genStructUnion(kind vdl.Kind, base [][]*vdl.Type, depth int) []*vdl.Type {
var res []*vdl.Type
prefix := g.NamePrefix
prefix += fmt.Sprintf("%sDepth%d_", strings.Title(kind.String()), depth)
// First collect all fields, with sequentially numbered field names. Using
// the same field names between the single-field and all-fields types ensures
// that the values are convertible.
var allFields []vdl.Field
fieldsPerKind := valueAtDepth(g.FieldsPerKind, depth)
for i, tt := range g.prune(base, fieldsPerKind) {
field := vdl.Field{Name: "F" + strconv.Itoa(i), Type: tt}
allFields = append(allFields, field)
}
// Create a type with all fields. We do this first so that subsequent depths
// will prefer to choose this type in g.prune.
if numAll := len(allFields); numAll > 1 {
name := prefix + "All"
res = append(res, vdl.NamedType(name, makeStructUnion(kind, allFields...)))
}
// Create single-field types.
for _, field := range allFields {
name := prefix + typeName(field.Type)
res = append(res, vdl.NamedType(name, makeStructUnion(kind, field)))
}
return res
}
func makeStructUnion(kind vdl.Kind, fields ...vdl.Field) *vdl.Type {
if kind == vdl.Struct {
return vdl.StructType(fields...)
}
return vdl.UnionType(fields...)
}
func (g *TypeGenerator) genOptional(base [][]*vdl.Type, depth int) []*vdl.Type {
// First collect all optionals that can be created, by bucket. We can't prune
// before this, since many types can't be optional. Optionals are unnamed.
var allUnnamed [][]*vdl.Type
for _, bucket := range base {
var unnamed []*vdl.Type
for _, tt := range bucket {
if tt.CanBeOptional() {
unnamed = append(unnamed, vdl.OptionalType(tt))
}
}
allUnnamed = append(allUnnamed, unnamed)
}
// Return pruned items.
return g.prune(allUnnamed, g.basePerKind(depth))
}