blob: b7664d20ad526ad5312f46f12a93f0ac078fe3d5 [file] [log] [blame]
// Copyright 2019 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 mixer
import (
"encoding/json"
"fmt"
"io/ioutil"
"math"
"os"
"path/filepath"
"strings"
"testing"
fidlir "fidl/compiler/backend/types"
gidlir "gidl/ir"
)
var testDataPath = func() string {
path, err := filepath.Abs(os.Args[0])
if err != nil {
panic(err)
}
return filepath.Join(filepath.Dir(path), "test_data", "gidl")
}()
var fidlRoot = func() fidlir.Root {
path := filepath.Join(testDataPath, "mixer.test.fidl.json")
bytes, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
var root fidlir.Root
err = json.Unmarshal(bytes, &root)
if err != nil {
panic(fmt.Sprintf("failed to unmarshal %s: %s", path, err))
}
return root
}()
// checkStruct is a helper function to test the Declaration for a struct.
func checkStruct(t *testing.T, decl Declaration, expectedName string, expectedNullable bool) {
t.Helper()
structDecl, ok := decl.(*StructDecl)
if !ok {
t.Fatalf("expected StructDecl, got %T\n\ndecl: %#v", decl, decl)
}
name := structDecl.Name.Parts()
if name.Name != fidlir.Identifier(expectedName) {
t.Errorf("expected name to be %s, got %s\n\ndecl: %#v", expectedName, name.Name, decl)
}
if structDecl.nullable != expectedNullable {
t.Errorf("expected nullable to be %v, got %v\n\ndecl: %#v",
expectedNullable, structDecl.nullable, decl)
}
}
func TestLookupDeclByNameNonNullable(t *testing.T) {
decl, ok := schema(fidlRoot).LookupDeclByName("ExampleStruct", false)
if !ok {
t.Fatalf("LookupDeclByName failed")
}
checkStruct(t, decl, "ExampleStruct", false)
}
func TestLookupDeclByNameNullable(t *testing.T) {
decl, ok := schema(fidlRoot).LookupDeclByName("ExampleStruct", true)
if !ok {
t.Fatalf("LookupDeclByName failed")
}
checkStruct(t, decl, "ExampleStruct", true)
}
func TestLookupDeclByNameFailure(t *testing.T) {
decl, ok := schema(fidlRoot).LookupDeclByName("ThisIsNotAStruct", false)
if ok {
t.Fatalf("LookupDeclByName unexpectedly succeeded: %#v", decl)
}
}
func TestLookupDeclByTypeSuccess(t *testing.T) {
typ := fidlir.Type{
Kind: fidlir.PrimitiveType,
PrimitiveSubtype: fidlir.Bool,
}
decl, ok := schema(fidlRoot).LookupDeclByType(typ)
if !ok {
t.Fatalf("LookupDeclByType failed")
}
if _, ok := decl.(*BoolDecl); !ok {
t.Fatalf("expected BoolDecl, got %T\n\ndecl: %#v", decl, decl)
}
}
func TestExtractDeclarationSuccess(t *testing.T) {
value := gidlir.Object{
Name: "ExampleStruct",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "s"}, Value: "foo"},
},
}
decl, err := ExtractDeclaration(value, fidlRoot)
if err != nil {
t.Fatalf("ExtractDeclaration failed: %s", err)
}
checkStruct(t, decl, "ExampleStruct", false)
}
func TestExtractDeclarationNotDefined(t *testing.T) {
value := gidlir.Object{
Name: "ThisIsNotAStruct",
Fields: []gidlir.Field{},
}
decl, err := ExtractDeclaration(value, fidlRoot)
if err == nil {
t.Fatalf("ExtractDeclaration unexpectedly succeeded: %#v", decl)
}
if !strings.Contains(err.Error(), "unknown") {
t.Fatalf("expected err to contain 'unknown', got '%s'", err)
}
}
func TestExtractDeclarationDoesNotConform(t *testing.T) {
value := gidlir.Object{
Name: "ExampleStruct",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "ThisIsNotAField"}, Value: "foo"},
},
}
decl, err := ExtractDeclaration(value, fidlRoot)
if err == nil {
t.Fatalf("ExtractDeclaration unexpectedly succeeded: %#v", decl)
}
if !strings.Contains(err.Error(), "conform") {
t.Fatalf("expected err to contain 'conform', got '%s'", err)
}
}
func TestExtractDeclarationUnsafeSuccess(t *testing.T) {
value := gidlir.Object{
Name: "ExampleStruct",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "ThisIsNotAField"}, Value: "foo"},
},
}
decl, err := ExtractDeclarationUnsafe(value, fidlRoot)
if err != nil {
t.Fatalf("ExtractDeclarationUnsafe failed: %s", err)
}
checkStruct(t, decl, "ExampleStruct", false)
}
// conformTest describes a test case for the Declaration.conforms method.
type conformTest interface {
shouldConform() bool
value() interface{}
}
type conformOk struct{ val interface{} }
type conformFail struct{ val interface{} }
func (c conformOk) shouldConform() bool { return true }
func (c conformOk) value() interface{} { return c.val }
func (c conformFail) shouldConform() bool { return false }
func (c conformFail) value() interface{} { return c.val }
// checkConforms is a helper function to test the Declaration.conforms method.
func checkConforms(t *testing.T, decl Declaration, tests []conformTest) {
t.Helper()
for _, test := range tests {
value, expected := test.value(), test.shouldConform()
if err := decl.conforms(value); (err == nil) != expected {
if expected {
t.Errorf(
"value failed to conform to declaration\n\nvalue: %#v\n\nerr: %s\n\ndecl: %#v",
value, err, decl)
} else {
t.Errorf(
"value unexpectedly conformed to declaration\n\nvalue: %#v\n\ndecl: %#v",
value, decl)
}
}
}
}
func TestBoolDeclConforms(t *testing.T) {
checkConforms(t,
&BoolDecl{},
[]conformTest{
conformOk{false},
conformOk{true},
conformFail{nil},
conformFail{"foo"},
conformFail{42},
conformFail{int64(42)},
},
)
}
func TestNumberDeclConforms(t *testing.T) {
checkConforms(t,
&NumberDecl{Typ: fidlir.Uint8, lower: 0, upper: 255},
[]conformTest{
conformOk{uint64(0)},
conformOk{uint64(128)},
conformOk{uint64(255)},
conformFail{int64(-1)},
conformFail{int64(256)},
conformFail{nil},
// Must be uint64 or int64, not any integer type.
conformFail{0},
conformFail{uint(0)},
conformFail{int8(0)},
conformFail{uint8(0)},
conformFail{"foo"},
conformFail{1.5},
},
)
checkConforms(t,
&NumberDecl{Typ: fidlir.Int64, lower: -5, upper: 10},
[]conformTest{
conformOk{int64(-5)},
conformOk{int64(10)},
conformFail{int64(-6)},
conformFail{int64(11)},
},
)
}
func TestFloatDeclConforms(t *testing.T) {
tests := []conformTest{
conformOk{0.0},
conformOk{1.5},
conformOk{-1.0},
conformFail{nil},
// Must be float64, not float32.
conformFail{float32(0.0)},
conformFail{0},
conformFail{"foo"},
// TODO(fxb/43020): Allow these once each backend supports them.
conformFail{math.Inf(1)},
conformFail{math.Inf(-1)},
conformFail{math.NaN()},
}
checkConforms(t, &FloatDecl{Typ: fidlir.Float32}, tests)
checkConforms(t, &FloatDecl{Typ: fidlir.Float64}, tests)
}
func TestStringDeclConforms(t *testing.T) {
checkConforms(t,
&StringDecl{bound: nil, nullable: false},
[]conformTest{
conformOk{""},
conformOk{"the quick brown fox"},
conformFail{nil},
conformFail{0},
},
)
checkConforms(t,
&StringDecl{bound: nil, nullable: true},
[]conformTest{
conformOk{"foo"},
conformOk{nil},
conformFail{0},
},
)
two := 2
checkConforms(t,
&StringDecl{bound: &two, nullable: false},
[]conformTest{
conformOk{""},
conformOk{"1"},
conformOk{"12"},
conformFail{"123"},
conformFail{"the quick brown fox"},
},
)
}
func TestStructDeclConformsNonNullable(t *testing.T) {
decl, ok := schema(fidlRoot).LookupDeclByName("ExampleStruct", false)
if !ok {
t.Fatalf("LookupDeclByName failed")
}
structDecl := decl.(*StructDecl)
checkConforms(t,
structDecl,
[]conformTest{
conformOk{gidlir.Object{
Name: "ExampleStruct",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "s"}, Value: "foo"},
},
}},
conformFail{gidlir.Object{
Name: "ExampleStruct",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "DefinitelyNotS"}, Value: "foo"},
},
}},
conformFail{gidlir.Object{
Name: "DefinitelyNotExampleStruct",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "s"}, Value: "foo"},
},
}},
conformFail{nil},
conformFail{"foo"},
conformFail{0},
},
)
}
func TestStructDeclConformsNullable(t *testing.T) {
decl, ok := schema(fidlRoot).LookupDeclByName("ExampleStruct", true)
if !ok {
t.Fatalf("LookupDeclByName failed")
}
structDecl := decl.(*StructDecl)
checkConforms(t,
structDecl,
[]conformTest{
conformOk{gidlir.Object{
Name: "ExampleStruct",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "s"}, Value: "foo"},
},
}},
conformOk{nil},
},
)
}
func TestTableDeclConforms(t *testing.T) {
decl, ok := schema(fidlRoot).LookupDeclByName("ExampleTable", false)
if !ok {
t.Fatalf("LookupDeclByName failed")
}
tableDecl := decl.(*TableDecl)
checkConforms(t,
tableDecl,
[]conformTest{
conformOk{gidlir.Object{
Name: "ExampleTable",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "t"}, Value: "foo"},
},
}},
conformFail{gidlir.Object{
Name: "ExampleTable",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "DefinitelyNotT"}, Value: "foo"},
},
}},
conformFail{gidlir.Object{
Name: "DefinitelyNotExampleTable",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "t"}, Value: "foo"},
},
}},
conformFail{nil},
conformFail{"foo"},
conformFail{0},
},
)
}
func TestUnionConvertsToXUnionDecl(t *testing.T) {
decl, ok := schema(fidlRoot).LookupDeclByName("ExampleUnion", false)
if !ok {
t.Fatalf("LookupDeclByName failed")
}
if _, ok := decl.(*XUnionDecl); !ok {
t.Fatalf("expected XUnionDecl, got %T", decl)
}
}
func TestXUnionDeclConformsNonNullable(t *testing.T) {
decl, ok := schema(fidlRoot).LookupDeclByName("ExampleXUnion", false)
if !ok {
t.Fatalf("LookupDeclByName failed")
}
xunionDecl := decl.(*XUnionDecl)
checkConforms(t,
xunionDecl,
[]conformTest{
conformOk{gidlir.Object{
Name: "ExampleXUnion",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "x"}, Value: "foo"},
},
}},
conformFail{gidlir.Object{
Name: "ExampleXUnion",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "DefinitelyNotX"}, Value: "foo"},
},
}},
conformFail{gidlir.Object{
Name: "DefinitelyNotExampleXUnion",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "x"}, Value: "foo"},
},
}},
conformFail{nil},
conformFail{"foo"},
conformFail{0},
},
)
}
func TestXUnionDeclConformsNullable(t *testing.T) {
decl, ok := schema(fidlRoot).LookupDeclByName("ExampleXUnion", true)
if !ok {
t.Fatalf("LookupDeclByName failed")
}
xunionDecl := decl.(*XUnionDecl)
checkConforms(t,
xunionDecl,
[]conformTest{
conformOk{gidlir.Object{
Name: "ExampleXUnion",
Fields: []gidlir.Field{
{Key: gidlir.FieldKey{Name: "x"}, Value: "foo"},
},
}},
conformOk{nil},
},
)
}
func TestArrayDeclConforms(t *testing.T) {
two := 2
checkConforms(t,
&ArrayDecl{
schema: schema(fidlRoot),
typ: fidlir.Type{
Kind: fidlir.ArrayType,
ElementCount: &two,
ElementType: &fidlir.Type{
Kind: fidlir.PrimitiveType,
PrimitiveSubtype: fidlir.Uint8,
},
},
},
[]conformTest{
conformOk{[]interface{}{uint64(1), uint64(2)}},
conformFail{[]interface{}{}},
conformFail{[]interface{}{uint64(1)}},
conformFail{[]interface{}{uint64(1), uint64(1), uint64(1)}},
conformFail{[]interface{}{"a", "b"}},
conformFail{[]interface{}{nil, nil}},
},
)
}
func TestVectorDeclConforms(t *testing.T) {
two := 2
checkConforms(t,
&VectorDecl{
schema: schema(fidlRoot),
typ: fidlir.Type{
Kind: fidlir.VectorType,
ElementCount: &two,
ElementType: &fidlir.Type{
Kind: fidlir.PrimitiveType,
PrimitiveSubtype: fidlir.Uint8,
},
},
},
[]conformTest{
conformOk{[]interface{}{}},
conformOk{[]interface{}{uint64(1)}},
conformOk{[]interface{}{uint64(1), uint64(2)}},
conformFail{[]interface{}{uint64(1), uint64(1), uint64(1)}},
conformFail{[]interface{}{"a", "b"}},
conformFail{[]interface{}{nil, nil}},
},
)
}
type visitor struct {
visited string
}
func (v *visitor) OnBool(bool) { v.visited = "Bool" }
func (v *visitor) OnInt64(int64, fidlir.PrimitiveSubtype) { v.visited = "Int64" }
func (v *visitor) OnUint64(uint64, fidlir.PrimitiveSubtype) { v.visited = "Uint64" }
func (v *visitor) OnFloat64(float64, fidlir.PrimitiveSubtype) { v.visited = "Float64" }
func (v *visitor) OnString(string, *StringDecl) { v.visited = "String" }
func (v *visitor) OnStruct(gidlir.Object, *StructDecl) { v.visited = "Struct" }
func (v *visitor) OnTable(gidlir.Object, *TableDecl) { v.visited = "Table" }
func (v *visitor) OnXUnion(gidlir.Object, *XUnionDecl) { v.visited = "XUnion" }
func (v *visitor) OnArray([]interface{}, *ArrayDecl) { v.visited = "Array" }
func (v *visitor) OnVector([]interface{}, *VectorDecl) { v.visited = "Vector" }
func (v *visitor) OnNull(Declaration) { v.visited = "Null" }
func TestVisit(t *testing.T) {
tests := []struct {
value interface{}
decl Declaration
expected string
}{
{false, &BoolDecl{}, "Bool"},
{int64(1), &NumberDecl{Typ: fidlir.Int8}, "Int64"},
{uint64(1), &NumberDecl{Typ: fidlir.Uint8}, "Uint64"},
{1.23, &FloatDecl{Typ: fidlir.Float32}, "Float64"},
{"foo", &StringDecl{}, "String"},
{nil, &StringDecl{nullable: true}, "Null"},
// These values and decls are not fully initialized, but for the
// purposes of Visit() it should not matter.
{gidlir.Object{}, &StructDecl{}, "Struct"},
{gidlir.Object{}, &TableDecl{}, "Table"},
{gidlir.Object{}, &XUnionDecl{}, "XUnion"},
}
for _, test := range tests {
var v visitor
Visit(&v, test.value, test.decl)
if v.visited != test.expected {
t.Errorf("expected dispatch to %q, got %q\n\nvalue: %#v\n\ndecl:%#v",
test.expected, v.visited, test.value, test.decl)
}
}
}