blob: 6f3e53d4dd3a7b340d8a60071efac2b2a26df669 [file] [log] [blame]
// Copyright 2021 The Fuchsia 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 dynfidl
import (
"bytes"
_ "embed"
"fmt"
"strconv"
"strings"
"text/template"
"go.fuchsia.dev/fuchsia/tools/fidl/gidl/lib/config"
"go.fuchsia.dev/fuchsia/tools/fidl/gidl/lib/ir"
"go.fuchsia.dev/fuchsia/tools/fidl/gidl/lib/mixer"
"go.fuchsia.dev/fuchsia/tools/fidl/gidl/lib/rust"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
var (
//go:embed conformance.tmpl
conformanceTmplText string
conformanceTmpl = template.Must(template.New("conformanceTmpl").Parse(conformanceTmplText))
)
type conformanceTmplInput struct {
EncodeSuccessCases []encodeSuccessCase
}
type encodeSuccessCase struct {
Name, Value, Bytes string
}
func GenerateConformanceTests(gidl ir.All, fidl fidlgen.Root, config config.GeneratorConfig) ([]byte, error) {
schema := mixer.BuildSchema(fidl)
// dynfidl only supports encode tests (it's an encoder)
encodeSuccessCases, err := encodeSuccessCases(gidl.EncodeSuccess, schema)
if err != nil {
return nil, err
}
input := conformanceTmplInput{
EncodeSuccessCases: encodeSuccessCases,
}
var buf bytes.Buffer
err = conformanceTmpl.Execute(&buf, input)
return buf.Bytes(), err
}
func encodeSuccessCases(gidlEncodeSuccesses []ir.EncodeSuccess, schema mixer.Schema) ([]encodeSuccessCase, error) {
var encodeSuccessCases []encodeSuccessCase
for _, encodeSuccess := range gidlEncodeSuccesses {
decl, err := schema.ExtractDeclarationEncodeSuccess(encodeSuccess.Value, encodeSuccess.HandleDefs)
if err != nil {
return nil, fmt.Errorf("encode success %s: %s", encodeSuccess.Name, err)
}
if !isSupported(decl) {
continue
}
visited := visit(encodeSuccess.Value, decl)
for _, encoding := range encodeSuccess.Encodings {
name := fidlgen.ToSnakeCase(fmt.Sprintf("%s_%s", encodeSuccess.Name, encoding.WireFormat))
encodeSuccessCases = append(encodeSuccessCases, encodeSuccessCase{
Name: name,
Value: visited.ValueStr,
Bytes: rust.BuildBytes(encoding.Bytes),
})
}
}
return encodeSuccessCases, nil
}
// check whether dynfidl supports the layout specified by the decl
func isSupported(decl mixer.Declaration) bool {
if decl.IsNullable() {
return false
}
switch decl := decl.(type) {
case *mixer.StructDecl:
if decl.IsResourceType() {
return false
}
for _, fieldName := range decl.FieldNames() {
if !isSupportedStructField(decl.Field(fieldName)) {
return false
}
}
return true
case *mixer.ArrayDecl, *mixer.BitsDecl, *mixer.BoolDecl, *mixer.EnumDecl,
*mixer.FloatDecl, *mixer.HandleDecl, *mixer.IntegerDecl, *mixer.StringDecl,
*mixer.TableDecl, *mixer.UnionDecl, *mixer.VectorDecl:
return false
default:
panic(fmt.Sprintf("unrecognized type %s", decl))
}
}
// check whether dynfidl supports the layout specified by the decl as the field of a struct
func isSupportedStructField(decl mixer.Declaration) bool {
if decl.IsNullable() {
return false
}
switch decl := decl.(type) {
case *mixer.BoolDecl, *mixer.IntegerDecl, *mixer.StringDecl:
return true
case *mixer.StructDecl:
if decl.IsResourceType() {
return false
}
for _, fieldName := range decl.FieldNames() {
if !isSupportedStructField(decl.Field(fieldName)) {
return false
}
}
return true
case *mixer.VectorDecl:
return isSupportedVectorElement(decl.Elem())
case *mixer.ArrayDecl, *mixer.BitsDecl, *mixer.EnumDecl, *mixer.FloatDecl,
*mixer.HandleDecl, *mixer.TableDecl, *mixer.UnionDecl:
return false
default:
panic(fmt.Sprintf("unrecognized type %s", decl))
}
}
// check whether dynfidl supports the layout specified by the decl as an element in a vector
func isSupportedVectorElement(decl mixer.Declaration) bool {
if decl.IsNullable() {
return false
}
switch decl := decl.(type) {
case *mixer.BoolDecl, *mixer.IntegerDecl, *mixer.StringDecl:
return true
case *mixer.VectorDecl:
// dynfidl only supports vectors-of-vectors-of-bytes
switch decl := decl.Elem().(type) {
case mixer.PrimitiveDeclaration:
switch decl.Subtype() {
case fidlgen.Uint8:
return true
default:
return false
}
default:
return false
}
case *mixer.ArrayDecl, *mixer.BitsDecl, *mixer.EnumDecl, *mixer.FloatDecl,
*mixer.HandleDecl, *mixer.StructDecl, *mixer.TableDecl, *mixer.UnionDecl:
return false
default:
panic(fmt.Sprintf("unrecognized type %s", decl))
}
}
type visitResult struct {
ValueStr string
OuterVariant outerVariant
InnerEnumAndVariant string
}
type outerVariant int
const (
_ outerVariant = iota
basicVariant
vectorVariant
structVariant
unsupportedVariant
)
func (v outerVariant) String() string {
switch v {
case basicVariant:
return "Basic"
case vectorVariant:
return "Vector"
case structVariant:
return "Struct"
case unsupportedVariant:
return "UNSUPPORTED"
default:
return fmt.Sprintf("invalid outerVariant %d", v)
}
}
// panics on any values which aren't supported by dynfidl
// should be guarded by a call to `isSupported`
func visit(value ir.Value, decl mixer.Declaration) visitResult {
switch value := value.(type) {
case bool:
return visitResult{
ValueStr: strconv.FormatBool(value),
OuterVariant: basicVariant,
InnerEnumAndVariant: "BasicField::Bool",
}
case int64, uint64, float64:
suffix, basicOuterVariant := primitiveTypeName(decl.(mixer.PrimitiveDeclaration).Subtype())
return visitResult{
ValueStr: fmt.Sprintf("%v%s", value, suffix),
OuterVariant: basicVariant,
InnerEnumAndVariant: fmt.Sprintf("BasicField::%s", basicOuterVariant),
}
case string:
var valueStr string
if fidlgen.PrintableASCII(value) {
valueStr = fmt.Sprintf("String::from(%q).into_bytes()", value)
} else {
valueStr = fmt.Sprintf("b\"%s\".to_vec()", rust.EscapeStr(value))
}
return visitResult{
ValueStr: valueStr,
OuterVariant: vectorVariant,
InnerEnumAndVariant: "VectorField::UInt8Vector",
}
case ir.Record:
decl := decl.(*mixer.StructDecl)
valueStr := "Structure::default()"
for _, field := range value.Fields {
fieldResult := visit(field.Value, decl.Field(field.Key.Name))
if fieldResult.InnerEnumAndVariant != "" {
valueStr += fmt.Sprintf(
".field(Field::%s(%s(%s)))",
fieldResult.OuterVariant,
fieldResult.InnerEnumAndVariant,
fieldResult.ValueStr)
} else {
valueStr += fmt.Sprintf(
".field(Field::%s(%s))",
fieldResult.OuterVariant,
fieldResult.ValueStr)
}
}
return visitResult{
ValueStr: valueStr,
OuterVariant: structVariant,
InnerEnumAndVariant: "",
}
case []ir.Value:
elemDecl := decl.(*mixer.VectorDecl).Elem()
var elements []string
for _, item := range value {
visited := visit(item, elemDecl)
elements = append(elements, visited.ValueStr)
}
valueStr := fmt.Sprintf("vec![%s]", strings.Join(elements, ", "))
var innerEnumAndVariant string
switch decl := elemDecl.(type) {
case mixer.PrimitiveDeclaration:
_, basicOuterVariant := primitiveTypeName(decl.Subtype())
innerEnumAndVariant = fmt.Sprintf("VectorField::%sVector", basicOuterVariant)
case *mixer.StringDecl, *mixer.VectorDecl:
// vectors are only supported as vector elements if they're vector<uint8>
// let rustc error if we're wrong about that assumption
innerEnumAndVariant = "VectorField::UInt8VectorVector"
default:
panic(fmt.Sprintf("unexpected type for a vector element %s", decl))
}
return visitResult{
ValueStr: valueStr,
OuterVariant: vectorVariant,
InnerEnumAndVariant: innerEnumAndVariant,
}
default:
panic(fmt.Sprintf("unsupported type: %T", value))
}
}
// panics on any values which aren't supported by dynfidl
// should be guarded by a call to `isSupported`
func primitiveTypeName(subtype fidlgen.PrimitiveSubtype) (string, string) {
switch subtype {
case fidlgen.Bool:
return "bool", "Bool"
case fidlgen.Int8:
return "i8", "Int8"
case fidlgen.Uint8:
return "u8", "UInt8"
case fidlgen.Int16:
return "i16", "Int16"
case fidlgen.Uint16:
return "u16", "UInt16"
case fidlgen.Int32:
return "i32", "Int32"
case fidlgen.Uint32:
return "u32", "UInt32"
case fidlgen.Int64:
return "i64", "Int64"
case fidlgen.Uint64:
return "u64", "UInt64"
default:
panic(fmt.Sprintf("unsupported subtype %v", subtype))
}
}