blob: d6a057e16b55025b2b02aff615e35724ff474de5 [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 golang
import (
"fmt"
"strings"
gidlir "go.fuchsia.dev/fuchsia/tools/fidl/gidl/ir"
gidlmixer "go.fuchsia.dev/fuchsia/tools/fidl/gidl/mixer"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
func BuildEqualityCheck(actualExpr string, expectedValue gidlir.Value, decl gidlmixer.Declaration, koidArrayVar string) string {
builder := equalityCheckBuilder{
koidArrayVar: koidArrayVar,
}
builder.visit(actualExpr, expectedValue, decl)
return builder.String()
}
// Generator of new variable names from a sequence.
type varSeq int
func (v *varSeq) next() string {
*v++
return fmt.Sprintf("f%d", *v)
}
type equalityCheckBuilder struct {
strings.Builder
varSeq varSeq
koidArrayVar string
}
func (b *equalityCheckBuilder) write(format string, vals ...interface{}) {
b.WriteString(fmt.Sprintf(format, vals...))
}
func (b *equalityCheckBuilder) createAndAssignVar(val string) string {
varName := b.varSeq.next()
b.write("%s := %s\n", varName, val)
b.write("ignore_unused_warning(%s)\n", varName)
return varName
}
func (b *equalityCheckBuilder) assertEquals(actual, expected string) {
b.write(
`if %[1]s != %[2]s {
t.Fatalf("unexpectedly unequal: %%s and %%s", %[1]q, %[2]q)
}
`, actual, expected)
}
func (b *equalityCheckBuilder) expectEquals(actual, expected string) {
b.write(
`if %[1]s != %[2]s {
t.Errorf("unexpectedly unequal: %%s and %%s", %[1]q, %[2]q)
}
`, actual, expected)
}
func (b *equalityCheckBuilder) expectNotEquals(actual, expected string) {
b.write(
`if %[1]s == %[2]s {
t.Errorf("unexpectedly equal: %%s and %%s", %[1]q, %[2]q)
}
`, actual, expected)
}
func (b *equalityCheckBuilder) expectFalse(value string) {
b.write(
`if %[1]s {
t.Errorf("expected false but was true: %%s", %[1]q)
}
`, value)
}
func (b *equalityCheckBuilder) expectTrue(value string) {
b.write(
`if !(%[1]s) {
t.Errorf("expected true but was false: %%s", %[1]q)
}
`, value)
}
func (b *equalityCheckBuilder) assertTrue(value string) {
b.write(
`if !(%[1]s) {
t.Fatalf("expected true but was false: %%s", %[1]q)
}
`, value)
}
func (b *equalityCheckBuilder) expectNil(value string) {
b.write(
`if %[1]s != nil {
t.Errorf("expected nil but was non-nil: %%s", %[1]q)
}
`, value)
}
func (b *equalityCheckBuilder) expectKoidEquals(actual string, expectedHandle gidlir.Handle) {
var handleVar = fmt.Sprintf("&%s.Handle", b.createAndAssignVar(actual))
infoVar := b.varSeq.next()
b.write(
`if runtime.GOOS == "fuchsia" {
%s, err := handleGetBasicInfo(%s)
if err != nil {
t.Fatal(err)
}
`, infoVar, handleVar)
b.expectEquals(fmt.Sprintf("%s.Koid", infoVar), fmt.Sprintf("%s[%d]", b.koidArrayVar, expectedHandle))
b.write("}\n")
}
func (b *equalityCheckBuilder) visit(actualExpr string, expectedValue gidlir.Value, decl gidlmixer.Declaration) {
switch expectedValue := expectedValue.(type) {
case bool, int64, uint64, float64:
b.expectEquals(actualExpr, fmt.Sprintf("%v", expectedValue))
return
case gidlir.RawFloat:
switch decl.(*gidlmixer.FloatDecl).Subtype() {
case fidlgen.Float32:
b.expectEquals(fmt.Sprintf("math.Float32bits(%s)", actualExpr), fmt.Sprintf("%d", expectedValue))
return
case fidlgen.Float64:
b.expectEquals(fmt.Sprintf("math.Float64bits(%s)", actualExpr), fmt.Sprintf("%d", expectedValue))
return
}
case string:
if decl.IsNullable() {
actualExpr = b.createAndAssignVar(fmt.Sprintf("*(%s)", actualExpr))
}
b.expectEquals(actualExpr, fmt.Sprintf("%q", expectedValue))
return
case gidlir.HandleWithRights:
b.visitHandle(actualExpr, expectedValue, decl.(*gidlmixer.HandleDecl))
return
case gidlir.Record:
switch decl := decl.(type) {
case *gidlmixer.StructDecl:
b.visitStruct(actualExpr, expectedValue, decl)
return
case *gidlmixer.TableDecl:
b.visitTable(actualExpr, expectedValue, decl)
return
case *gidlmixer.UnionDecl:
b.visitUnion(actualExpr, expectedValue, decl)
return
}
case []gidlir.Value:
b.visitList(actualExpr, expectedValue, decl.(gidlmixer.ListDeclaration))
return
case nil:
switch decl.(type) {
case *gidlmixer.HandleDecl:
b.expectEquals(actualExpr, "zx.HandleInvalid")
return
default:
b.expectNil(actualExpr)
return
}
}
panic(fmt.Sprintf("not implemented: %T (decl: %T)", expectedValue, decl))
}
func (b *equalityCheckBuilder) visitHandle(actualExpr string, expectedValue gidlir.HandleWithRights, decl *gidlmixer.HandleDecl) {
var handleVar string
if decl.Subtype() == fidlgen.Handle {
handleVar = fmt.Sprintf("&%s", b.createAndAssignVar(actualExpr))
} else {
handleVar = fmt.Sprintf("%s.Handle()", actualExpr)
}
infoVar := b.varSeq.next()
b.write(
`if runtime.GOOS == "fuchsia" {
%s, err := handleGetBasicInfo(%s)
if err != nil {
t.Fatal(err)
}
`, infoVar, handleVar)
b.expectEquals(fmt.Sprintf("%s.Koid", infoVar), fmt.Sprintf("%s[%d]", b.koidArrayVar, expectedValue.Handle))
b.expectTrue(fmt.Sprintf("%[1]s.Type == %[2]d || %[2]d == zx.ObjectTypeNone", infoVar, expectedValue.Type))
b.expectTrue(fmt.Sprintf("%[1]s.Rights == %[2]d || %[2]d == zx.RightSameRights", infoVar, expectedValue.Rights))
b.write("}\n")
}
func (b *equalityCheckBuilder) visitStruct(actualExpr string, expectedValue gidlir.Record, decl *gidlmixer.StructDecl) {
if decl.IsNullable() {
actualExpr = b.createAndAssignVar(fmt.Sprintf("*(%s)", actualExpr))
}
actualVar := b.createAndAssignVar(actualExpr)
for _, field := range expectedValue.Fields {
fieldDecl, ok := decl.Field(field.Key.Name)
if !ok {
panic(fmt.Sprintf("field %q not found", field.Key.Name))
}
actualFieldExpr := fmt.Sprintf("%s.%s", actualVar, fidlgen.ToUpperCamelCase(field.Key.Name))
b.visit(actualFieldExpr, field.Value, fieldDecl)
}
}
func (b *equalityCheckBuilder) visitTable(actualExpr string, expectedValue gidlir.Record, decl *gidlmixer.TableDecl) {
actualVar := b.createAndAssignVar(actualExpr)
expectedFieldValues := map[string]gidlir.Value{}
for _, field := range expectedValue.Fields {
if field.Key.IsKnown() {
expectedFieldValues[field.Key.Name] = field.Value
continue
}
ud := field.Value.(gidlir.UnknownData)
b.write(
`if _, ok := %[1]s.GetUnknownData()[%[2]d]; !ok {
t.Fatalf("expected unknown data for %[1]s at ordinal: %[2]d")
}
`, actualVar, field.Key.UnknownOrdinal)
b.assertEquals(fmt.Sprintf("len(%s.GetUnknownData()[%d].Bytes)", actualVar, field.Key.UnknownOrdinal), fmt.Sprintf("%d", len(ud.Bytes)))
for i, byt := range ud.Bytes {
b.expectEquals(fmt.Sprintf("%s.GetUnknownData()[%d].Bytes[%d]", actualVar, field.Key.UnknownOrdinal, i), fmt.Sprintf("%d", byt))
}
b.assertEquals(fmt.Sprintf("len(%s.GetUnknownData()[%d].Handles)", actualVar, field.Key.UnknownOrdinal), fmt.Sprintf("%d", len(ud.Handles)))
for i, h := range ud.Handles {
b.expectKoidEquals(fmt.Sprintf("%s.GetUnknownData()[%d].Handles[%d]", actualVar, field.Key.UnknownOrdinal, i), h)
}
}
for _, fieldName := range decl.FieldNames() {
fieldDecl, ok := decl.Field(fieldName)
if !ok {
panic(fmt.Sprintf("field decl %s not found", fieldName))
}
goFieldName := fidlgen.ToUpperCamelCase(fieldName)
if expectedFieldValue, ok := expectedFieldValues[fieldName]; ok {
b.assertTrue(fmt.Sprintf("%s.Has%s()", actualVar, goFieldName))
fieldVar := b.createAndAssignVar(fmt.Sprintf("%s.Get%s()", actualVar, goFieldName))
b.visit(fieldVar, expectedFieldValue, fieldDecl)
} else {
b.expectFalse(fmt.Sprintf("%s.Has%s()", actualVar, goFieldName))
}
}
}
func (b *equalityCheckBuilder) visitUnion(actualExpr string, expectedValue gidlir.Record, decl *gidlmixer.UnionDecl) {
if len(expectedValue.Fields) != 1 {
panic("unions have exactly one assigned field")
}
actualVar := b.createAndAssignVar(actualExpr)
field := expectedValue.Fields[0]
if field.Key.IsUnknown() {
ud := field.Value.(gidlir.UnknownData)
b.assertEquals(fmt.Sprintf("len(%s.GetUnknownData().Bytes)", actualVar), fmt.Sprintf("%d", len(ud.Bytes)))
for i, byt := range ud.Bytes {
b.expectEquals(fmt.Sprintf("%s.GetUnknownData().Bytes[%d]", actualVar, i), fmt.Sprintf("%d", byt))
}
b.assertEquals(fmt.Sprintf("len(%s.GetUnknownData().Handles)", actualVar), fmt.Sprintf("%d", len(ud.Handles)))
for i, h := range ud.Handles {
b.expectKoidEquals(fmt.Sprintf("%s.GetUnknownData().Handles[%d]", actualVar, i), h)
}
return
}
fieldDecl, ok := decl.Field(field.Key.Name)
if !ok {
panic(fmt.Sprintf("field %q not found", field.Key.Name))
}
fieldName := fidlgen.ToUpperCamelCase(field.Key.Name)
b.assertEquals(fmt.Sprintf("%s.Which()", actualVar),
fmt.Sprintf("%s%s", declName(decl), fieldName))
fieldVar := b.createAndAssignVar(fmt.Sprintf("%s.%s", actualVar, fieldName))
b.visit(fieldVar, field.Value, fieldDecl)
}
func (b *equalityCheckBuilder) visitList(actualExpr string, expectedValue []gidlir.Value, decl gidlmixer.ListDeclaration) {
if decl.IsNullable() {
actualExpr = fmt.Sprintf("*(%s)", actualExpr)
}
actualVar := b.createAndAssignVar(actualExpr)
if _, ok := decl.(*gidlmixer.VectorDecl); ok {
b.assertEquals(fmt.Sprintf("len(%s)", actualVar), fmt.Sprintf("%d", len(expectedValue)))
}
for i, item := range expectedValue {
b.visit(fmt.Sprintf("%s[%d]", actualVar, i), item, decl.Elem())
}
}