blob: 424bf172da2d74eb8f5c36811be64e85ab3780a5 [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build fuchsia
package fidl_test
import (
"fmt"
"math/rand"
"reflect"
"strings"
"syscall/zx"
"testing"
. "syscall/zx/fidl"
. "syscall/zx/fidl/bindingstest"
)
type marshalFunc struct {
name string
fn func(Message, []byte, []zx.Handle) (int, int, error)
}
var marshalFuncs = []marshalFunc{
{"Marshal", func(input Message, respb []byte, resph []zx.Handle) (int, int, error) {
return Marshal(input, respb, resph)
}},
{"MarshalNew", MarshalNew},
}
type unmarshalFunc struct {
name string
fn func([]byte, []zx.Handle, Message) error
}
var unmarshalFuncs = []unmarshalFunc{
{"Unmarshal", func(respb []byte, resph []zx.Handle, output Message) error {
return Unmarshal(respb, resph, output)
}},
{"UnmarshalNew", UnmarshalNew},
}
type example struct {
name string
input Message
expectSize int
output Message
}
// general provides test cases used to verify correctness, and
// benchmark against.
//
// Keep these as a slice to preserve consistent ordering when running.
func general() []example {
var (
s = "bye"
s256 = strings.Repeat("hello!!!", 64)
s8192 = strings.Repeat("hello!!!", 1024)
v1 = []int64{-1}
v2 = []string{"x", "hello"}
v3 = []int64{101010}
u1 = Union1{}
)
u1.SetB(TestSimple{X: 555})
vmo, err := zx.NewVMO(10, 0)
if err != nil {
panic(fmt.Sprintf("failed to create vmo: %v", err))
}
defer vmo.Close()
h0, h1, err := zx.NewChannel(0)
defer h0.Close()
defer h1.Close()
if err != nil {
panic(fmt.Sprintf("failed to create vmo: %v", err))
}
st1 := SimpleTable{}
st1.SetX(42)
st1.SetY(67)
return []example{
// simple (to get started)
{"simple", &TestSimple{X: 124}, 8, &TestSimple{}},
{"simplebool", &TestSimpleBool{X: true}, 8, &TestSimpleBool{}},
// alignement
{"align1", &TestAlignment1{X: -36, Y: -10, Z: 51}, 8, &TestAlignment1{}},
{"align2", &TestAlignment2{
A: 1212141,
B: 908935,
C: -1,
D: 125,
E: -22,
F: 111,
G: 1515,
H: 65535,
I: 1515,
}, 24, &TestAlignment2{}},
// floats
{"float1", &TestFloat1{A: -36.0}, 8, &TestFloat1{}},
{"float2", &TestFloat2{A: -1254918271.0}, 8, &TestFloat2{}},
{"float3", &TestFloat3{A: 1241.1, B: 0.2141, C: 20, D: 0.0}, 32, &TestFloat3{}},
// arrays
{"array1", &TestArray1{A: [5]int8{1, 77, 2, 4, 89}}, 8, &TestArray1{}},
{"array2", &TestArray2{A: -1.0, B: [1]float32{0.2}}, 16, &TestArray2{}},
{"array3", &TestArray3{
A: -999,
B: [3]uint16{11, 12, 13},
C: 1021,
}, 24, &TestArray3{}},
{"array4", &TestArray4{
A: [9]bool{true, false, false, true, false, true, true, true, true},
}, 16, &TestArray4{}},
// strings
{"string1", &TestString1{A: "str", B: nil}, 40, &TestString1{}},
{"string1-longer256", &TestString1{A: s256, B: &s256}, 1056, &TestString1{}},
{"string1-longer8192", &TestString1{A: s8192, B: &s8192}, 16416, &TestString1{}},
{"string2", &TestString2{A: [2]string{"hello", "g"}}, 48, &TestString2{}},
{"string3", &TestString3{
A: [2]string{"boop", "g"},
B: [2]*string{&s, nil},
}, 88, &TestString3{}},
// vectors
{"vector1", &TestVector1{
A: []int8{1, 2, 3, 4},
B: nil,
C: []int32{99},
D: &v1,
}, 88, &TestVector1{}},
{"vector2", &TestVector2{
A: [2][]int8{{9, -1}, {}},
B: [][]int8{{-111, 41}, {-1, -1, -1, -1}},
C: []*[]string{nil, &v2},
}, 200, &TestVector2{}},
// structs
{"struct1", &TestStruct1{
A: TestSimple{
X: -9999,
},
B: &TestSimple{
X: 1254125,
},
}, 24, &TestStruct1{}},
{"struct2", &TestStruct2{
A: TestArray1{
A: [5]int8{1, 77, 2, 4, 5},
},
B: TestFloat1{
A: 2.81212,
},
C: TestVector1{
A: []int8{1, 2, 3, 4},
B: nil,
C: []int32{99},
D: &v3,
},
D: &TestString1{
A: "str",
B: nil,
},
}, 152, &TestStruct2{}},
// unions
{"union1", &TestUnion1{
A: u1,
B: nil,
}, 24, &TestUnion1{}},
{"union1-bis", &TestUnion1{
A: u1,
B: &u1,
}, 40, &TestUnion1{}},
{"union2", &TestUnion2{
A: []Union1{u1, u1, u1},
B: []*Union1{&u1, nil, nil},
}, 120, &TestUnion2{}},
// handles
{"handle1", &TestHandle1{
A: zx.Handle(22),
B: zx.HandleInvalid,
C: vmo,
D: zx.VMO(zx.HandleInvalid),
}, 16, &TestHandle1{}},
{"handle2", &TestHandle2{
A: []zx.Handle{zx.Handle(vmo)},
B: []zx.VMO{zx.VMO(zx.HandleInvalid)},
}, 48, &TestHandle2{}},
// interfaces
{"interface1", &TestInterface1{
A: Test1Interface(Proxy{Channel: h0}),
B: Test1Interface(Proxy{Channel: zx.Channel(zx.HandleInvalid)}),
C: Test1InterfaceRequest(InterfaceRequest{Channel: h1}),
D: Test1InterfaceRequest(InterfaceRequest{
Channel: zx.Channel(zx.HandleInvalid),
}),
}, 16, &TestInterface1{}},
// tables
{"table1", &TestSimpleTable{
Table: st1,
}, 112, &TestSimpleTable{}},
{"opt-table1", &TestOptSimpleTable{
OptTable: &st1,
}, 120, &TestOptSimpleTable{}},
}
}
// Using a fixed seed for the randomizer for deterministic behavior of the
// tests, all the while using 'random looking' data.
var r = rand.New(rand.NewSource(524287))
func randBytes(num int) []byte {
bytes := make([]byte, num, num)
if _, err := r.Read(bytes); err != nil {
panic(err)
}
return bytes
}
// fuchsia provides test cases from fuchsia verify correctness, and
// benchmark against.
//
// Keep these as a slice to preserve consistent ordering when running.
func fuchsia() []example {
var (
bytes16 = randBytes(16)
bytes64 = randBytes(64)
bytes256 = randBytes(256)
bytes1024 = randBytes(1024)
bytes4096 = randBytes(4096)
bytes8192 = randBytes(8192)
)
return []example{
// fuchsia.io, ReadAt response
{"fuchsia.io-readAt-response0", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: []byte{},
}, 24 + 0, &TestFuchsiaIoReadAtResponse{}},
{"fuchsia.io-readAt-response16", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes16,
}, 24 + 16, &TestFuchsiaIoReadAtResponse{}},
{"fuchsia.io-readAt-response64", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes64,
}, 24 + 64, &TestFuchsiaIoReadAtResponse{}},
{"fuchsia.io-readAt-response256", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes256,
}, 24 + 256, &TestFuchsiaIoReadAtResponse{}},
{"fuchsia.io-readAt-response1024", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes1024,
}, 24 + 1024, &TestFuchsiaIoReadAtResponse{}},
{"fuchsia.io-readAt-response4096", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes4096,
}, 24 + 4096, &TestFuchsiaIoReadAtResponse{}},
{"fuchsia.io-readAt-response8192", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes8192,
}, 24 + 8192, &TestFuchsiaIoReadAtResponse{}},
// fuchsia.io, WriteAt request
{"fuchsia.io-writeAt-request0", &TestFuchsiaIoWriteAtRequest{
Data: []byte{},
Offset: 5,
}, 24 + 0, &TestFuchsiaIoWriteAtRequest{}},
{"fuchsia.io-writeAt-request16", &TestFuchsiaIoWriteAtRequest{
Data: bytes16,
Offset: 5,
}, 24 + 16, &TestFuchsiaIoWriteAtRequest{}},
{"fuchsia.io-writeAt-request64", &TestFuchsiaIoWriteAtRequest{
Data: bytes64,
Offset: 5,
}, 24 + 64, &TestFuchsiaIoWriteAtRequest{}},
{"fuchsia.io-writeAt-request256", &TestFuchsiaIoWriteAtRequest{
Data: bytes256,
Offset: 5,
}, 24 + 256, &TestFuchsiaIoWriteAtRequest{}},
{"fuchsia.io-writeAt-request1024", &TestFuchsiaIoWriteAtRequest{
Data: bytes1024,
Offset: 5,
}, 24 + 1024, &TestFuchsiaIoWriteAtRequest{}},
{"fuchsia.io-writeAt-request4096", &TestFuchsiaIoWriteAtRequest{
Data: bytes4096,
Offset: 5,
}, 24 + 4096, &TestFuchsiaIoWriteAtRequest{}},
{"fuchsia.io-writeAt-request8192", &TestFuchsiaIoWriteAtRequest{
Data: bytes8192,
Offset: 5,
}, 24 + 8192, &TestFuchsiaIoWriteAtRequest{}},
}
}
type checker struct {
marshalFunc
unmarshalFunc
}
func (c checker) check(t *testing.T, input Message, expectSize int, output Message) {
t.Helper()
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
nb, nh, err := c.marshalFunc.fn(input, respb[:], resph[:])
if err != nil {
t.Fatalf("marshal: failed: %s", err)
}
if nb != expectSize {
t.Fatalf("marshal: expected size %d but got %d: %v", expectSize, nb, respb[:nb])
}
if err := c.unmarshalFunc.fn(respb[:nb], resph[:nh], output); err != nil {
t.Fatalf("unmarshal: failed: %s", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("unmarshal: expected: %v, got: %v", input, output)
}
}
func TestCorrectness(t *testing.T) {
for _, m := range marshalFuncs {
for _, u := range unmarshalFuncs {
c := checker{m, u}
for _, ex := range general() {
t.Run(fmt.Sprintf("%s-%s-%s", m.name, u.name, ex.name), func(t *testing.T) {
c.check(t, ex.input, ex.expectSize, ex.output)
})
}
for _, ex := range fuchsia() {
t.Run(fmt.Sprintf("%s-%s-%s", m.name, u.name, ex.name), func(t *testing.T) {
c.check(t, ex.input, ex.expectSize, ex.output)
})
}
}
}
}
func makeDefault(msg Message) Message {
typ := reflect.TypeOf(msg)
if typ.Kind() != reflect.Ptr && typ.Elem().Kind() != reflect.Struct {
panic("expecting *struct")
}
return reflect.New(typ.Elem()).Interface().(Message)
}
// successCase represents a golden test for a success case, where encoding
// and decoding should succceed.
type successCase struct {
name string
input Message
bytes []byte
}
func (ex successCase) check(t *testing.T) {
for _, mFn := range marshalFuncs {
for _, uFn := range unmarshalFuncs {
t.Run(ex.name+"_"+mFn.name+"_"+uFn.name, func(t *testing.T) {
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
nb, nh, err := mFn.fn(ex.input, respb[:], resph[:])
if err != nil {
t.Fatal(err)
}
if nh != 0 {
t.Fatalf("no handles expected, got %d", nh)
}
if !reflect.DeepEqual(ex.bytes, respb[:nb]) {
t.Fatalf("expected %v, got %v", ex.bytes, respb[:nb])
}
output := makeDefault(ex.input)
if err := uFn.fn(respb[:nb], resph[:nh], output); err != nil {
t.Fatalf("unmarshal: failed: %s", err)
}
if !reflect.DeepEqual(ex.input, output) {
t.Fatalf("unmarshal: expected: %v, got: %v", ex.input, output)
}
})
}
}
}
var simpleTableWithXY = []byte{
5, 0, 0, 0, 0, 0, 0, 0, // max ordinal
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
8, 0, 0, 0, 0, 0, 0, 0, // envelope 1: num bytes / num handles
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
0, 0, 0, 0, 0, 0, 0, 0, // envelope 2: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
0, 0, 0, 0, 0, 0, 0, 0, // envelope 3: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
0, 0, 0, 0, 0, 0, 0, 0, // envelope 4: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
8, 0, 0, 0, 0, 0, 0, 0, // envelope 5: num bytes / num handles
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
42, 0, 0, 0, 0, 0, 0, 0, // field X
67, 0, 0, 0, 0, 0, 0, 0, // field Y
}
var simpleTableWithY = []byte{
5, 0, 0, 0, 0, 0, 0, 0, // max ordinal
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
0, 0, 0, 0, 0, 0, 0, 0, // envelope 1: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
0, 0, 0, 0, 0, 0, 0, 0, // envelope 2: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
0, 0, 0, 0, 0, 0, 0, 0, // envelope 3: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
0, 0, 0, 0, 0, 0, 0, 0, // envelope 4: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
8, 0, 0, 0, 0, 0, 0, 0, // envelope 5: num bytes / num handles
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
67, 0, 0, 0, 0, 0, 0, 0, // field Y
}
func TestAllSuccessCases(t *testing.T) {
st1 := SimpleTable{}
st1.SetX(42)
st1.SetY(67)
successCase{
name: "simpletable-x-and-y",
input: &TestSimpleTable{
Table: st1,
},
bytes: simpleTableWithXY,
}.check(t)
st2 := SimpleTable{}
st2.SetY(67)
successCase{
name: "simpletable-just-y",
input: &TestSimpleTable{
Table: st2,
},
bytes: simpleTableWithY,
}.check(t)
st3 := SimpleTable{}
st3.SetX(42)
st3.SetY(67)
successCase{
name: "opt-simpletable-x-and-y",
input: &TestOptSimpleTable{
OptTable: &st3,
},
bytes: append([]byte{
255, 255, 255, 255, 255, 255, 255, 255, // alloc present,
},
simpleTableWithXY...),
}.check(t)
}
func TestTableCompatibilityWithXY(t *testing.T) {
ost := OlderSimpleTable{}
ost.SetX(42)
st := SimpleTable{}
st.SetX(42)
st.SetY(67)
nst := NewerSimpleTable{}
nst.SetX(42)
nst.SetY(67)
cases := []Message{
&TestOlderSimpleTable{Table: ost},
&TestSimpleTable{Table: st},
&TestNewerSimpleTable{Table: nst},
}
for _, expected := range cases {
for _, u := range unmarshalFuncs {
output := makeDefault(expected)
u.fn(simpleTableWithXY, nil, output)
if !reflect.DeepEqual(expected, output) {
t.Fatalf("unmarshal: expected: %v, got: %v", expected, output)
}
}
}
}
func TestTableCompatibilityWithY(t *testing.T) {
ost := OlderSimpleTable{}
st := SimpleTable{}
st.SetY(67)
nst := NewerSimpleTable{}
nst.SetY(67)
cases := []Message{
&TestOlderSimpleTable{Table: ost},
&TestSimpleTable{Table: st},
&TestNewerSimpleTable{Table: nst},
}
for _, expected := range cases {
for _, u := range unmarshalFuncs {
output := makeDefault(expected)
u.fn(simpleTableWithY, nil, output)
if !reflect.DeepEqual(expected, output) {
t.Fatalf("unmarshal: expected: %v, got: %v", expected, output)
}
}
}
}
func TestFailuresMarshal(t *testing.T) {
v1 := []int64{1, 2, 3}
cases := []struct {
name string
input Message
errorCode ErrorCode
}{
{"string3-string-too-long", &TestString3{
A: [2]string{
"too long!", // limit is 4, provided is longer(tm)
"g",
},
B: [2]*string{nil, nil},
}, ErrStringTooLong},
{"vector1-C-vector-too-long", &TestVector1{
A: []int8{1, 2, 3, 4},
B: nil,
C: []int32{99, 100, 101}, // limit is 2, provided is 3
D: nil,
}, ErrVectorTooLong},
{"vector1-D-vector-too-long", &TestVector1{
A: []int8{1, 2, 3, 4},
B: nil,
C: []int32{99},
D: &v1, // limit is 2, provided is 3
}, ErrVectorTooLong},
{"union1-A-uninitialized", &TestUnion1{
A: Union1{}, // Intentionally don't set any members of the union.
B: nil,
}, ErrInvalidUnionTag},
{"union1-A-tag-out-of-bounds", &TestUnion1{
A: Union1{Union1Tag: Union1D + 1},
B: nil,
}, ErrInvalidUnionTag},
}
for _, ex := range cases {
t.Run(ex.name, func(t *testing.T) {
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
_, _, err := MarshalNew(ex.input, respb[:], resph[:])
validationErr, ok := err.(ValidationError)
if !ok {
t.Fatalf("expected ValidationError, was %v", err)
}
if validationErr.Code() != ex.errorCode {
t.Fatalf("expected %s, was %s", ex.errorCode, validationErr.Code())
}
})
}
}
func TestFailuresUnmarshalNoHandles(t *testing.T) {
cases := []struct {
name string
input []byte
message Message
errorCode ErrorCode
}{
{"zero-bytes", []byte{}, &TestSimple{}, ErrPayloadTooSmall},
{"non-zero-or-one-bool", []byte{2}, &TestSimpleBool{}, ErrInvalidBoolValue},
{"wrong-tag", []byte{0, 0, 0, 4}, &TestUnion1{}, ErrInvalidUnionTag},
}
for _, ex := range cases {
t.Run(ex.name, func(t *testing.T) {
err := UnmarshalNew(ex.input, nil, ex.message)
validationErr, ok := err.(ValidationError)
if !ok {
t.Fatalf("expected ValidationError, was %v", err)
}
if validationErr.Code() != ex.errorCode {
t.Fatalf("expected %s, was %s", ex.errorCode, validationErr.Code())
}
})
}
}
func benchmarkMarshal(b *testing.B, ex example) {
for _, m := range marshalFuncs {
b.Run(fmt.Sprintf("%s-%s", m.name, ex.name), func(b *testing.B) {
b.StopTimer()
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
b.StartTimer()
for n := 0; n < b.N; n++ {
if _, _, err := m.fn(ex.input, respb[:], resph[:]); err != nil {
b.Fail()
}
}
})
}
}
func BenchmarkMarshal(b *testing.B) {
for _, ex := range general() {
benchmarkMarshal(b, ex)
}
for _, ex := range fuchsia() {
benchmarkMarshal(b, ex)
}
}
func benchmarkUnmarshal(b *testing.B, ex example) {
for _, u := range unmarshalFuncs {
b.Run(fmt.Sprintf("%s-%s", u.name, ex.name), func(b *testing.B) {
b.StopTimer()
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
nb, nh, err := Marshal(ex.input, respb[:], resph[:])
if err != nil {
b.Fail()
}
b.StartTimer()
for n := 0; n < b.N; n++ {
if err := u.fn(respb[:nb], resph[:nh], ex.output); err != nil {
b.Fail()
}
}
})
}
}
func BenchmarkUnmarshal(b *testing.B) {
for _, ex := range general() {
benchmarkUnmarshal(b, ex)
}
for _, ex := range fuchsia() {
benchmarkUnmarshal(b, ex)
}
}