blob: faf9be0c479abeaf7186110249703aa970c54caf [file] [log] [blame]
// Copyright 2018 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 dart
import (
"bytes"
"fmt"
"strings"
"text/template"
gidlconfig "go.fuchsia.dev/fuchsia/tools/fidl/gidl/config"
gidlir "go.fuchsia.dev/fuchsia/tools/fidl/gidl/ir"
gidlmixer "go.fuchsia.dev/fuchsia/tools/fidl/gidl/mixer"
fidl "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
var tmpl = template.Must(template.New("tmpls").Parse(`
// @dart = 2.8
// Ignore unused imports so that GIDL tests can be commented out without error.
// ignore_for_file: unused_import
import 'dart:typed_data';
import 'package:test/test.dart';
import 'package:fidl/fidl.dart' as fidl;
import 'package:topaz.lib.gidl/gidl.dart';
import 'package:topaz.lib.gidl/handles.dart';
import 'package:zircon/zircon.dart';
import 'package:fidl_conformance/fidl_async.dart';
void main() {
group('conformance', () {
group('encode success cases', () {
{{ range .EncodeSuccessCases }}
{{- if .HandleDefs }}
EncodeSuccessCase.runWithHandles(
{{ .EncoderName }},
{{ .Name }},
(List<Handle> handleDefs) => {{ .Value }},
{{ .ValueType }},
{{ .Bytes }},
{{ .HandleDefs }},
{{ .Handles }});
{{- else }}
EncodeSuccessCase.run(
{{ .EncoderName }},
{{ .Name }},
{{ .Value }},
{{ .ValueType }},
{{ .Bytes }});
{{- end }}
{{ end }}
});
group('decode success cases', () {
{{ range .DecodeSuccessCases }}
{{- if .HandleDefs }}
DecodeSuccessCase.runWithHandles(
{{ .DecoderName }},
{{ .Name }},
(List<Handle> handleDefs) => {{ .Value }},
{{ .ValueType }},
{{ .Bytes }},
{{ .HandleDefs }},
{{ .Handles }},
{{ .UnusedHandles }});
{{- else }}
DecodeSuccessCase.run(
{{ .DecoderName }},
{{ .Name }},
{{ .Value }},
{{ .ValueType }},
{{ .Bytes }});
{{- end }}
{{ end }}
});
group('encode failure cases', () {
{{ range .EncodeFailureCases }}
{{- if .HandleDefs }}
EncodeFailureCase.runWithHandles(
{{ .EncoderName }},
{{ .Name }},
(List<Handle> handleDefs) => {{ .Value }},
{{ .ValueType }},
{{ .ErrorCode }},
{{ .HandleDefs }});
{{- else }}
EncodeFailureCase.run(
{{ .EncoderName }},
{{ .Name }},
() => {{ .Value }},
{{ .ValueType }},
{{ .ErrorCode }});
{{- end }}
{{ end }}
});
group('decode failure cases', () {
{{ range .DecodeFailureCases }}
DecodeFailureCase.run(
{{ .DecoderName }},
{{ .Name }},
{{ .ValueType }},
{{ .Bytes }},
{{- if .HandleDefs }}
{{ .ErrorCode }},
{{ .HandleDefs }},
{{ .Handles }});
{{- else }}
{{ .ErrorCode }});
{{- end }}
{{ end }}
});
});
}
`))
type tmplInput struct {
EncodeSuccessCases []encodeSuccessCase
DecodeSuccessCases []decodeSuccessCase
EncodeFailureCases []encodeFailureCase
DecodeFailureCases []decodeFailureCase
}
type encodeSuccessCase struct {
EncoderName, Name, Value, ValueType, Bytes, HandleDefs, Handles string
}
type decodeSuccessCase struct {
DecoderName, Name, Value, ValueType, Bytes, HandleDefs, Handles, UnusedHandles string
}
type encodeFailureCase struct {
EncoderName, Name, Value, ValueType, ErrorCode, HandleDefs string
}
type decodeFailureCase struct {
DecoderName, Name, ValueType, Bytes, ErrorCode, HandleDefs, Handles string
}
// Generate generates dart tests.
func GenerateConformanceTests(gidl gidlir.All, fidl fidl.Root, config gidlconfig.GeneratorConfig) ([]byte, error) {
schema := gidlmixer.BuildSchema(fidl)
encodeSuccessCases, err := encodeSuccessCases(gidl.EncodeSuccess, schema)
if err != nil {
return nil, err
}
decodeSuccessCases, err := decodeSuccessCases(gidl.DecodeSuccess, schema)
if err != nil {
return nil, err
}
encodeFailureCases, err := encodeFailureCases(gidl.EncodeFailure, schema)
if err != nil {
return nil, err
}
decodeFailureCases, err := decodeFailureCases(gidl.DecodeFailure, schema)
if err != nil {
return nil, err
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, tmplInput{
EncodeSuccessCases: encodeSuccessCases,
DecodeSuccessCases: decodeSuccessCases,
EncodeFailureCases: encodeFailureCases,
DecodeFailureCases: decodeFailureCases,
})
return buf.Bytes(), err
}
func encodeSuccessCases(gidlEncodeSuccesses []gidlir.EncodeSuccess, schema gidlmixer.Schema) ([]encodeSuccessCase, error) {
var encodeSuccessCases []encodeSuccessCase
for _, encodeSuccess := range gidlEncodeSuccesses {
decl, err := schema.ExtractDeclaration(encodeSuccess.Value, encodeSuccess.HandleDefs)
if err != nil {
return nil, fmt.Errorf("encode success %s: %s", encodeSuccess.Name, err)
}
valueStr := visit(encodeSuccess.Value, decl)
valueType := typeName(decl)
for _, encoding := range encodeSuccess.Encodings {
if !wireFormatSupported(encoding.WireFormat) {
continue
}
encodeSuccessCases = append(encodeSuccessCases, encodeSuccessCase{
EncoderName: encoderName(encoding.WireFormat),
Name: testCaseName(encodeSuccess.Name, encoding.WireFormat),
Value: valueStr,
ValueType: valueType,
Bytes: buildBytes(encoding.Bytes),
HandleDefs: buildHandleDefs(encodeSuccess.HandleDefs),
Handles: toDartIntList(encoding.Handles),
})
}
}
return encodeSuccessCases, nil
}
func decodeSuccessCases(gidlDecodeSuccesses []gidlir.DecodeSuccess, schema gidlmixer.Schema) ([]decodeSuccessCase, error) {
var decodeSuccessCases []decodeSuccessCase
for _, decodeSuccess := range gidlDecodeSuccesses {
decl, err := schema.ExtractDeclaration(decodeSuccess.Value, decodeSuccess.HandleDefs)
if err != nil {
return nil, fmt.Errorf("decode success %s: %s", decodeSuccess.Name, err)
}
valueStr := visit(decodeSuccess.Value, decl)
valueType := typeName(decl)
for _, encoding := range decodeSuccess.Encodings {
if !wireFormatSupported(encoding.WireFormat) {
continue
}
decodeSuccessCases = append(decodeSuccessCases, decodeSuccessCase{
DecoderName: decoderName(encoding.WireFormat),
Name: testCaseName(decodeSuccess.Name, encoding.WireFormat),
Value: valueStr,
ValueType: valueType,
Bytes: buildBytes(encoding.Bytes),
HandleDefs: buildHandleDefs(decodeSuccess.HandleDefs),
Handles: toDartIntList(encoding.Handles),
UnusedHandles: toDartIntList(gidlir.GetUnusedHandles(decodeSuccess.Value, encoding.Handles)),
})
}
}
return decodeSuccessCases, nil
}
func encodeFailureCases(gidlEncodeFailures []gidlir.EncodeFailure, schema gidlmixer.Schema) ([]encodeFailureCase, error) {
var encodeFailureCases []encodeFailureCase
for _, encodeFailure := range gidlEncodeFailures {
decl, err := schema.ExtractDeclarationUnsafe(encodeFailure.Value)
if err != nil {
return nil, fmt.Errorf("encode failure %s: %s", encodeFailure.Name, err)
}
if gidlir.ContainsUnknownField(encodeFailure.Value) {
continue
}
errorCode, err := dartErrorCode(encodeFailure.Err)
if err != nil {
return nil, fmt.Errorf("encode failure %s: %s", encodeFailure.Name, err)
}
valueStr := visit(encodeFailure.Value, decl)
valueType := typeName(decl)
for _, wireFormat := range encodeFailure.WireFormats {
if !wireFormatSupported(wireFormat) {
continue
}
encodeFailureCases = append(encodeFailureCases, encodeFailureCase{
EncoderName: encoderName(wireFormat),
Name: testCaseName(encodeFailure.Name, wireFormat),
Value: valueStr,
ValueType: valueType,
ErrorCode: errorCode,
HandleDefs: buildHandleDefs(encodeFailure.HandleDefs),
})
}
}
return encodeFailureCases, nil
}
func decodeFailureCases(gidlDecodeFailures []gidlir.DecodeFailure, schema gidlmixer.Schema) ([]decodeFailureCase, error) {
var decodeFailureCases []decodeFailureCase
for _, decodeFailure := range gidlDecodeFailures {
_, err := schema.ExtractDeclarationByName(decodeFailure.Type)
if err != nil {
return nil, fmt.Errorf("decode failure %s: %s", decodeFailure.Name, err)
}
errorCode, err := dartErrorCode(decodeFailure.Err)
if err != nil {
return nil, fmt.Errorf("decode failure %s: %s", decodeFailure.Name, err)
}
valueType := dartTypeName(decodeFailure.Type)
for _, encoding := range decodeFailure.Encodings {
if !wireFormatSupported(encoding.WireFormat) {
continue
}
decodeFailureCases = append(decodeFailureCases, decodeFailureCase{
DecoderName: decoderName(encoding.WireFormat),
Name: testCaseName(decodeFailure.Name, encoding.WireFormat),
ValueType: valueType,
Bytes: buildBytes(encoding.Bytes),
ErrorCode: errorCode,
HandleDefs: buildHandleDefs(decodeFailure.HandleDefs),
Handles: toDartIntList(encoding.Handles),
})
}
}
return decodeFailureCases, nil
}
func wireFormatSupported(wireFormat gidlir.WireFormat) bool {
return wireFormat == gidlir.V1WireFormat
}
func testCaseName(baseName string, wireFormat gidlir.WireFormat) string {
return fidl.SingleQuote(fmt.Sprintf("%s_%s", baseName, wireFormat))
}
func encoderName(wireFormat gidlir.WireFormat) string {
return fmt.Sprintf("Encoders.%s", wireFormat)
}
func decoderName(wireFormat gidlir.WireFormat) string {
return fmt.Sprintf("Decoders.%s", wireFormat)
}
func dartTypeName(inputType string) string {
return fmt.Sprintf("k%s_Type", inputType)
}
func buildBytes(bytes []byte) string {
var builder strings.Builder
builder.WriteString("Uint8List.fromList([\n")
for i, b := range bytes {
builder.WriteString(fmt.Sprintf("0x%02x,", b))
if i%8 == 7 {
// Note: empty comments are written to preserve formatting. See:
// https://github.com/dart-lang/dart_style/wiki/FAQ#why-does-the-formatter-mess-up-my-collection-literals
builder.WriteString(" //\n")
}
}
builder.WriteString("])")
return builder.String()
}
func toDartStr(value string) string {
var buf bytes.Buffer
buf.WriteRune('\'')
for _, r := range value {
if 0x20 <= r && r <= 0x7e { // printable ASCII rune
buf.WriteRune(r)
} else {
buf.WriteString(fmt.Sprintf(`\u{%x}`, r))
}
}
buf.WriteRune('\'')
return buf.String()
}
func toDartIntList(handles []gidlir.Handle) string {
var builder strings.Builder
builder.WriteString("[\n")
for i, handle := range handles {
builder.WriteString(fmt.Sprintf("%d,", handle))
if i%8 == 7 {
// Note: empty comments are written to preserve formatting. See:
// https://github.com/dart-lang/dart_style/wiki/FAQ#why-does-the-formatter-mess-up-my-collection-literals
builder.WriteString(" //\n")
}
}
builder.WriteString("]")
return builder.String()
}
// Dart error codes defined in: topaz/public/dart/fidl/lib/src/error.dart.
var dartErrorCodeNames = map[gidlir.ErrorCode]string{
gidlir.StringTooLong: "fidlStringTooLong",
gidlir.StringNotUtf8: "unknown",
gidlir.NonEmptyStringWithNullBody: "fidlNonEmptyStringWithNullBody",
gidlir.StrictUnionFieldNotSet: "fidlStrictXUnionFieldNotSet",
gidlir.StrictUnionUnknownField: "fidlStrictXUnionUnknownField",
gidlir.StrictBitsUnknownBit: "fidlInvalidBit",
gidlir.StrictEnumUnknownValue: "fidlInvalidEnumValue",
gidlir.InvalidPaddingByte: "unknown",
gidlir.TooFewHandles: "fidlTooFewHandles",
gidlir.NonResourceUnknownHandles: "fidlNonResourceHandle",
}
func dartErrorCode(code gidlir.ErrorCode) (string, error) {
if str, ok := dartErrorCodeNames[code]; ok {
return fmt.Sprintf("fidl.FidlErrorCode.%s", str), nil
}
return "", fmt.Errorf("no dart error string defined for error code %s", code)
}