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