| // 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()) |
| } |
| } |