| // 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 ( |
| "bytes" |
| "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 |
| } |
| |
| // 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}, |
| {"simplebool", &TestSimpleBool{X: true}, 8}, |
| |
| // alignement |
| {"align1", &TestAlignment1{X: -36, Y: -10, Z: 51}, 8}, |
| {"align2", &TestAlignment2{ |
| A: 1212141, |
| B: 908935, |
| C: -1, |
| D: 125, |
| E: -22, |
| F: 111, |
| G: 1515, |
| H: 65535, |
| I: 1515, |
| }, 24}, |
| |
| // floats |
| {"float1", &TestFloat1{A: -36.0}, 8}, |
| {"float2", &TestFloat2{A: -1254918271.0}, 8}, |
| {"float3", &TestFloat3{A: 1241.1, B: 0.2141, C: 20, D: 0.0}, 32}, |
| |
| // arrays |
| {"array1", &TestArray1{A: [5]int8{1, 77, 2, 4, 89}}, 8}, |
| {"array2", &TestArray2{A: -1.0, B: [1]float32{0.2}}, 16}, |
| {"array3", &TestArray3{ |
| A: -999, |
| B: [3]uint16{11, 12, 13}, |
| C: 1021, |
| }, 24}, |
| {"array4", &TestArray4{ |
| A: [9]bool{true, false, false, true, false, true, true, true, true}, |
| }, 16}, |
| |
| // strings |
| {"string1", &TestString1{A: "str", B: nil}, 40}, |
| {"string1-longer256", &TestString1{A: s256, B: &s256}, 1056}, |
| {"string1-longer8192", &TestString1{A: s8192, B: &s8192}, 16416}, |
| {"string2", &TestString2{A: [2]string{"hello", "g"}}, 48}, |
| {"string3", &TestString3{ |
| A: [2]string{"boop", "g"}, |
| B: [2]*string{&s, nil}, |
| }, 88}, |
| {"string-with-bound", &TestStringWithBound{A: "str"}, 24}, |
| {"opt-string-with-bound", &TestOptStringWithBound{A: &s}, 24}, |
| |
| // vectors |
| {"vector1", &TestVector1{ |
| A: []int8{1, 2, 3, 4}, |
| B: nil, |
| C: []int32{99}, |
| D: &v1, |
| }, 88}, |
| {"vector2", &TestVector2{ |
| A: [2][]int8{{9, -1}, {}}, |
| B: [][]int8{{-111, 41}, {-1, -1, -1, -1}}, |
| C: []*[]string{nil, &v2}, |
| }, 200}, |
| |
| // structs |
| {"struct1", &TestStruct1{ |
| A: TestSimple{ |
| X: -9999, |
| }, |
| B: &TestSimple{ |
| X: 1254125, |
| }, |
| }, 24}, |
| {"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}, |
| |
| // unions |
| {"union1", &TestUnion1{ |
| A: u1, |
| B: nil, |
| }, 24}, |
| {"union1-bis", &TestUnion1{ |
| A: u1, |
| B: &u1, |
| }, 40}, |
| {"union2", &TestUnion2{ |
| A: []Union1{u1, u1, u1}, |
| B: []*Union1{&u1, nil, nil}, |
| }, 120}, |
| |
| // handles |
| {"handle1", &TestHandle1{ |
| A: zx.Handle(22), |
| B: zx.HandleInvalid, |
| C: vmo, |
| D: zx.VMO(zx.HandleInvalid), |
| }, 16}, |
| {"handle2", &TestHandle2{ |
| A: []zx.Handle{zx.Handle(vmo)}, |
| B: []zx.VMO{zx.VMO(zx.HandleInvalid)}, |
| }, 48}, |
| |
| // interfaces |
| {"interface1", &TestInterface1{ |
| A: Test1Interface(ChannelProxy{Channel: h0}), |
| B: Test1Interface(ChannelProxy{Channel: zx.Channel(zx.HandleInvalid)}), |
| C: Test1InterfaceRequest(InterfaceRequest{Channel: h1}), |
| D: Test1InterfaceRequest(InterfaceRequest{ |
| Channel: zx.Channel(zx.HandleInvalid), |
| }), |
| }, 16}, |
| |
| // tables |
| {"table1", &TestSimpleTable{ |
| Table: st1, |
| }, 112}, |
| } |
| } |
| |
| // 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}, |
| {"fuchsia.io-readAt-response16", &TestFuchsiaIoReadAtResponse{ |
| S: 5, |
| Data: bytes16, |
| }, 24 + 16}, |
| {"fuchsia.io-readAt-response64", &TestFuchsiaIoReadAtResponse{ |
| S: 5, |
| Data: bytes64, |
| }, 24 + 64}, |
| {"fuchsia.io-readAt-response256", &TestFuchsiaIoReadAtResponse{ |
| S: 5, |
| Data: bytes256, |
| }, 24 + 256}, |
| {"fuchsia.io-readAt-response1024", &TestFuchsiaIoReadAtResponse{ |
| S: 5, |
| Data: bytes1024, |
| }, 24 + 1024}, |
| {"fuchsia.io-readAt-response4096", &TestFuchsiaIoReadAtResponse{ |
| S: 5, |
| Data: bytes4096, |
| }, 24 + 4096}, |
| {"fuchsia.io-readAt-response8192", &TestFuchsiaIoReadAtResponse{ |
| S: 5, |
| Data: bytes8192, |
| }, 24 + 8192}, |
| |
| // fuchsia.io, WriteAt request |
| {"fuchsia.io-writeAt-request0", &TestFuchsiaIoWriteAtRequest{ |
| Data: []byte{}, |
| Offset: 5, |
| }, 24 + 0}, |
| {"fuchsia.io-writeAt-request16", &TestFuchsiaIoWriteAtRequest{ |
| Data: bytes16, |
| Offset: 5, |
| }, 24 + 16}, |
| {"fuchsia.io-writeAt-request64", &TestFuchsiaIoWriteAtRequest{ |
| Data: bytes64, |
| Offset: 5, |
| }, 24 + 64}, |
| {"fuchsia.io-writeAt-request256", &TestFuchsiaIoWriteAtRequest{ |
| Data: bytes256, |
| Offset: 5, |
| }, 24 + 256}, |
| {"fuchsia.io-writeAt-request1024", &TestFuchsiaIoWriteAtRequest{ |
| Data: bytes1024, |
| Offset: 5, |
| }, 24 + 1024}, |
| {"fuchsia.io-writeAt-request4096", &TestFuchsiaIoWriteAtRequest{ |
| Data: bytes4096, |
| Offset: 5, |
| }, 24 + 4096}, |
| {"fuchsia.io-writeAt-request8192", &TestFuchsiaIoWriteAtRequest{ |
| Data: bytes8192, |
| Offset: 5, |
| }, 24 + 8192}, |
| } |
| } |
| |
| type checker struct { |
| marshalFunc |
| unmarshalFunc |
| } |
| |
| func (c checker) check(t *testing.T, input Message, expectSize int) { |
| t.Helper() |
| defer func() { |
| if r := recover(); r != nil { |
| // When running tests on device, this bubbles up the error |
| // on the console launching the tests, rather than having |
| // to look at the device's kernel logs. |
| t.Fatalf("panic: %s", r) |
| panic(r) |
| } |
| }() |
| 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]) |
| } |
| output := makeDefault(input) |
| 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) |
| }) |
| } |
| 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) |
| }) |
| } |
| } |
| } |
| } |
| |
| 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 |
| handles []zx.Handle |
| } |
| |
| 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) { |
| defer func() { |
| if r := recover(); r != nil { |
| // When running tests on device, this bubbles up the error |
| // on the console launching the tests, rather than having |
| // to look at the device's kernel logs. |
| t.Fatalf("panic: %s", r) |
| panic(r) |
| } |
| }() |
| 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 !bytes.Equal(ex.bytes, respb[:nb]) { |
| t.Fatalf("expected %x, got %x", ex.bytes, respb[:nb]) |
| } |
| if len(ex.handles) == 0 { |
| if nh != 0 { |
| t.Fatalf("no handles expected, got %d", nh) |
| } |
| } else { |
| if !reflect.DeepEqual(ex.handles, resph[:nh]) { |
| t.Fatalf("expected %v, got %v", ex.handles, resph[:nh]) |
| } |
| } |
| 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) |
| } |
| }) |
| } |
| } |
| } |
| |
| type errorCaseUnmarshal struct { |
| name string |
| message Message |
| input []byte |
| errorCode ErrorCode |
| } |
| |
| var baseErrorCasesUnmarshal = []errorCaseUnmarshal{ |
| {"empty-array-TestSimple", &TestSimple{}, []byte{}, ErrPayloadTooSmall}, |
| {"nil-array-TestSimple", &TestSimple{}, nil, ErrPayloadTooSmall}, |
| {"small-array-TestSimple", &TestSimple{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"empty-array-TestSimpleBool", &TestSimpleBool{}, []byte{}, ErrPayloadTooSmall}, |
| {"nil-array-TestSimpleBool", &TestSimpleBool{}, nil, ErrPayloadTooSmall}, |
| {"empty-array-TestAlignment1", &TestAlignment1{}, []byte{}, ErrPayloadTooSmall}, |
| {"nil-array-TestAlignment1", &TestAlignment1{}, nil, ErrPayloadTooSmall}, |
| {"small-array-TestAlignment1", &TestAlignment1{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"empty-array-TestAlignment2", &TestAlignment2{}, []byte{}, ErrPayloadTooSmall}, |
| {"nil-array-TestAlignment2", &TestAlignment2{}, nil, ErrPayloadTooSmall}, |
| {"small-array-TestAlignment2", &TestAlignment2{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"small-array-TestAlignment2-2", &TestAlignment2{}, make([]byte, 10), ErrPayloadTooSmall}, |
| {"empty-array-TestFloat1", &TestFloat1{}, []byte{}, ErrPayloadTooSmall}, |
| {"nil-array-TestFloat1", &TestFloat1{}, nil, ErrPayloadTooSmall}, |
| {"small-array-TestFloat1", &TestFloat1{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"empty-array-TestFloat2", &TestFloat2{}, []byte{}, ErrPayloadTooSmall}, |
| {"nil-array-TestFloat2", &TestFloat2{}, nil, ErrPayloadTooSmall}, |
| {"small-array-TestFloat2", &TestFloat2{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"empty-array-TestFloat3", &TestFloat3{}, []byte{}, ErrPayloadTooSmall}, |
| {"nil-array-TestFloat3", &TestFloat3{}, nil, ErrPayloadTooSmall}, |
| {"small-array-TestFloat3", &TestFloat3{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"small-array-TestFloat3-2", &TestFloat3{}, make([]byte, 6), ErrPayloadTooSmall}, |
| {"nil-array-TestArray1", &TestArray1{}, nil, ErrPayloadTooSmall}, |
| {"empty-array-TestArray1", &TestArray1{}, []byte{}, ErrPayloadTooSmall}, |
| {"two-bytes-array-TestArray1", &TestArray1{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"nil-array-TestArray2", &TestArray2{}, nil, ErrPayloadTooSmall}, |
| {"empty-array-TestArray2", &TestArray2{}, []byte{}, ErrPayloadTooSmall}, |
| {"two-bytes-array-TestArray2", &TestArray2{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"six-bytes-array-TestArray2", &TestArray2{}, make([]byte, 6), ErrPayloadTooSmall}, |
| {"nil-array-TestArray3", &TestArray3{}, nil, ErrPayloadTooSmall}, |
| {"empty-array-TestArray3", &TestArray3{}, []byte{}, ErrPayloadTooSmall}, |
| {"two-bytes-array-TestArray3", &TestArray3{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"six-bytes-array-TestArray3", &TestArray3{}, make([]byte, 6), ErrPayloadTooSmall}, |
| {"thirteen-bytes-array-TestArray3", &TestArray3{}, make([]byte, 13), ErrPayloadTooSmall}, |
| {"nil-array-TestArray4", &TestArray4{}, nil, ErrPayloadTooSmall}, |
| {"empty-array-TestArray4", &TestArray4{}, []byte{}, ErrPayloadTooSmall}, |
| {"two-bytes-array-TestArray4", &TestArray4{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"nil-array-TestString1", &TestString1{}, nil, ErrPayloadTooSmall}, |
| {"empty-array-TestString1", &TestString1{}, []byte{}, ErrPayloadTooSmall}, |
| {"two-bytes-array-TestString1", &TestString1{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"nil-array-TestString2", &TestString2{}, nil, ErrPayloadTooSmall}, |
| {"empty-array-TestString2", &TestString2{}, []byte{}, ErrPayloadTooSmall}, |
| {"two-bytes-array-TestString2", &TestString2{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"nil-array-TestStruct2", &TestStruct2{}, nil, ErrPayloadTooSmall}, |
| {"empty-array-TestStruct2", &TestStruct2{}, []byte{}, ErrPayloadTooSmall}, |
| {"two-bytes-array-TestStruct2", &TestStruct2{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"six-bytes-array-TestStruct2", &TestStruct2{}, make([]byte, 6), ErrPayloadTooSmall}, |
| {"thirteen-bytes-array-TestStruct2", &TestStruct2{}, make([]byte, 13), ErrPayloadTooSmall}, |
| {"empty-array-TestUnion1", &TestUnion1{}, []byte{}, ErrPayloadTooSmall}, |
| {"nil-array-TestUnion1", &TestUnion1{}, nil, ErrPayloadTooSmall}, |
| {"small-array-TestUnion1", &TestUnion1{}, []byte{0x00, 0x0}, ErrPayloadTooSmall}, |
| {"non-zero-or-one-bool", &TestSimpleBool{}, []byte{2}, ErrInvalidBoolValue}, |
| {"wrong-tag", &TestUnion1{}, []byte{0, 0, 0, 4}, ErrInvalidUnionTag}, |
| {"empty-struct-non-zero", &TestEmptyStructSandwich{}, emptyStructSandwichWithOneInsteadOfZero, ErrInvalidEmptyStruct}, |
| } |
| |
| var allErrorCasesUnmarshal = append(baseErrorCasesUnmarshal, []errorCaseUnmarshal{ |
| {"string-wrong-ptr-no-alloc", &TestStringWithBound{}, []byte{ |
| 3, 0, 0, 0, 0, 0, 0, 0, // length |
| 0, 0, 0, 0, 0, 0, 0, 0, // ptr (no alloc) |
| // no data, unmarshal should fail before |
| }, ErrUnexpectedNullRef}, |
| {"string-wrong-ptr-incorrect", &TestStringWithBound{}, []byte{ |
| 3, 0, 0, 0, 0, 0, 0, 0, // length |
| 0, 0, 0, 0, 0, 0, 0, 1, // ptr (no alloc) |
| // no data, unmarshal should fail before |
| }, ErrBadRefEncoding}, |
| {"string-too-long", &TestStringWithBound{}, []byte{ |
| 9, 0, 0, 0, 0, 0, 0, 0, // length (too long) |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // ptr |
| // no data, unmarshal should fail before |
| }, ErrStringTooLong}, |
| |
| {"opt-string-wrong-ptr-incorrect", &TestOptStringWithBound{}, []byte{ |
| 3, 0, 0, 0, 0, 0, 0, 0, // length |
| 0, 0, 0, 0, 0, 0, 0, 1, // ptr (no alloc) |
| // no data, unmarshal should fail before |
| }, ErrBadRefEncoding}, |
| {"opt-string-too-long", &TestOptStringWithBound{}, []byte{ |
| 9, 0, 0, 0, 0, 0, 0, 0, // length (too long) |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // ptr |
| // no data, unmarshal should fail before |
| }, ErrStringTooLong}, |
| }...) |
| |
| 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 |
| } |
| |
| // TableWithStringAndVector: { |
| // foo: "hello", |
| // bar: 27, |
| // } |
| var tableWithStringAndVectorHello27 = []byte{ |
| 2, 0, 0, 0, 0, 0, 0, 0, // max ordinal |
| 255, 255, 255, 255, 255, 255, 255, 255, // alloc present |
| 24, 0, 0, 0, 0, 0, 0, 0, // envelope 1: num bytes / num handles |
| 255, 255, 255, 255, 255, 255, 255, 255, // envelope 1: alloc present |
| 8, 0, 0, 0, 0, 0, 0, 0, // envelope 2: num bytes / num handles |
| 255, 255, 255, 255, 255, 255, 255, 255, // envelope 2: alloc present |
| 5, 0, 0, 0, 0, 0, 0, 0, // element 1: length |
| 255, 255, 255, 255, 255, 255, 255, 255, // element 1: alloc present |
| 104, 101, 108, 108, 111, 0, 0, 0, // element 1: hello |
| 27, 0, 0, 0, 0, 0, 0, 0, // element 2: value |
| } |
| |
| var emptyTable = []byte{ |
| 0, 0, 0, 0, 0, 0, 0, 0, // max ordinal |
| 255, 255, 255, 255, 255, 255, 255, 255, // alloc present |
| } |
| |
| var emptyStructSandwich = []byte{ |
| 6, 0, 0, 0, 0, 0, 0, 0, // length of "before" |
| 255, 255, 255, 255, 255, 255, 255, 255, // "before" is present |
| 0, // empty struct zero field |
| 0, 0, 0, 0, 0, 0, 0, // 7 bytes of padding after empty struct, to align to 64 bits |
| 5, 0, 0, 0, 0, 0, 0, 0, // length of "world" |
| 255, 255, 255, 255, 255, 255, 255, 255, // "after" is present |
| 'b', 'e', 'f', 'o', 'r', 'e', |
| 0, 0, // 2 bytes of padding after "hello", to align to 64 bits |
| 'a', 'f', 't', 'e', 'r', // "world" string |
| 0, 0, 0, // 3 bytes of padding after "world", to align to 64 bits |
| } |
| |
| var emptyStructSandwichWithOneInsteadOfZero = []byte{ |
| 6, 0, 0, 0, 0, 0, 0, 0, // length of "before" |
| 255, 255, 255, 255, 255, 255, 255, 255, // "before" is present |
| 1, // empty struct with invalid value of 1 (instead of zero) |
| 0, 0, 0, 0, 0, 0, 0, // 7 bytes of padding after empty struct, to align to 64 bits |
| 5, 0, 0, 0, 0, 0, 0, 0, // length of "world" |
| 255, 255, 255, 255, 255, 255, 255, 255, // "after" is present |
| 'b', 'e', 'f', 'o', 'r', 'e', |
| 0, 0, // 2 bytes of padding after "hello", to align to 64 bits |
| 'a', 'f', 't', 'e', 'r', // "world" string |
| 0, 0, 0, // 3 bytes of padding after "world", to align to 64 bits |
| } |
| |
| func TestAllSuccessCases(t *testing.T) { |
| successCase{ |
| name: "empty-struct-sandwich", |
| input: &TestEmptyStructSandwich{ |
| Before: "before", |
| Es: EmptyStruct{}, |
| After: "after", |
| }, |
| bytes: emptyStructSandwich, |
| }.check(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) |
| |
| successCase{ |
| name: "table-with-string-and-vector-1", |
| input: &TestTableWithStringAndVector{ |
| Table: TableWithStringAndVector{ |
| Foo: "hello", |
| FooPresent: true, |
| Bar: 27, |
| BarPresent: true, |
| }, |
| }, |
| bytes: tableWithStringAndVectorHello27, |
| }.check(t) |
| |
| successCase{ |
| name: "empty-table", |
| input: &TestSimpleTable{}, |
| bytes: emptyTable, |
| }.check(t) |
| |
| var xu SampleXUnion |
| xu.SetU(0xdeadbeef) |
| successCase{ |
| name: "inline-xunion-in-struct", |
| input: &TestInlineXUnionInStruct{ |
| Before: "before", |
| Xu: xu, |
| After: "after", |
| }, |
| bytes: []byte{ |
| 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "before" length |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence |
| |
| 0x39, 0xdb, 0x88, 0x79, 0x00, 0x00, 0x00, 0x00, // xunion discriminator + padding |
| 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // num bytes + num handles |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope data is present |
| |
| 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "after" length |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence |
| |
| // secondary object 1: "before" |
| 'b', 'e', 'f', 'o', 'r', 'e', 0x00, 0x00, |
| |
| // secondary object 2: xunion content |
| 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, // xunion envelope content (0xdeadbeef) + padding |
| |
| // secondary object 3: "after" |
| 'a', 'f', 't', 'e', 'r', 0x00, 0x00, 0x00, |
| }, |
| }.check(t) |
| |
| successCase{ |
| name: "optional-xunion-in-struct-absent", |
| input: &TestOptionalXUnionInStruct{ |
| Before: "before", |
| // No xunion. |
| After: "after", |
| }, |
| bytes: []byte{ |
| 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "before" length |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence |
| |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // xunion discriminator + padding |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // num bytes + num handles |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // envelope data is absent |
| |
| 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "after" length |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence |
| |
| // secondary object 1: "before" |
| 'b', 'e', 'f', 'o', 'r', 'e', 0x00, 0x00, |
| |
| // secondary object 2: "after" |
| 'a', 'f', 't', 'e', 'r', 0x00, 0x00, 0x00, |
| }, |
| }.check(t) |
| |
| successCase{ |
| name: "optional-xunion-in-struct-present", |
| input: &TestOptionalXUnionInStruct{ |
| Before: "before", |
| Xu: &xu, |
| After: "after", |
| }, |
| bytes: []byte{ |
| 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "before" length |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence |
| |
| 0x39, 0xdb, 0x88, 0x79, 0x00, 0x00, 0x00, 0x00, // xunion discriminator + padding |
| 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // num bytes + num handles |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope data is present |
| |
| 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "after" length |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence |
| |
| // secondary object 1: "before" |
| 'b', 'e', 'f', 'o', 'r', 'e', 0x00, 0x00, |
| |
| // secondary object 2: xunion content |
| 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, // xunion envelope content (0xdeadbeef) + padding |
| |
| // secondary object 3: "after" |
| 'a', 'f', 't', 'e', 'r', 0x00, 0x00, 0x00, |
| }, |
| }.check(t) |
| |
| var xitAbsent XUnionInTable |
| xitAbsent.SetBefore("before") |
| xitAbsent.SetAfter("after") |
| successCase{ |
| name: "xunion-in-table-xunion-absent", |
| input: &TestXUnionInTable{ |
| Value: xitAbsent, |
| }, |
| bytes: []byte{ |
| // primary object |
| 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // vector<envelope> element count |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // vector<envelope> present |
| |
| // secondary object 1: vector data |
| // vector[0]: envelope<string before> |
| 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" is present |
| // vector[1]: envelope<SampleXUnion xu> |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // xunion is absent |
| // vector[2]: envelope<string after> |
| 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "after" is present |
| |
| // secondary object 2: "before" length + pointer |
| 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "before" length |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" present |
| |
| // secondary object 3: "before" |
| 'b', 'e', 'f', 'o', 'r', 'e', 0x00, 0x00, // "before" |
| |
| // secondary object 4: "after" length + pointer |
| 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "after" length |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "after" present |
| |
| // secondary object 5: "before" |
| 'a', 'f', 't', 'e', 'r', 0x00, 0x00, 0x00, // "after" |
| }, |
| }.check(t) |
| |
| var xitPresent XUnionInTable |
| xitPresent.SetBefore("before") |
| xitPresent.SetXu(xu) |
| xitPresent.SetAfter("after") |
| successCase{ |
| name: "xunion-in-table-xunion-present", |
| input: &TestXUnionInTable{ |
| Value: xitPresent, |
| }, |
| bytes: []byte{ |
| // primary object |
| 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // vector<envelope> element count |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // vector<envelope> present |
| |
| // secondary object 1: vector data |
| // vector[0]: envelope<string before> |
| 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" is present |
| // vector[1]: envelope<SampleXUnion xu> |
| 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // xunion is present |
| // vector[2]: envelope<string after> |
| 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "after" is present |
| |
| // secondary object 2: "before" length + pointer |
| 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "before" length |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" present |
| |
| // secondary object 3: "before" |
| 'b', 'e', 'f', 'o', 'r', 'e', 0x00, 0x00, // "before" |
| |
| // secondary object 4: xunion |
| 0x39, 0xdb, 0x88, 0x79, 0x00, 0x00, 0x00, 0x00, // xunion discriminator + padding |
| 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // num bytes + num handles |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope data is present |
| |
| // secondary object 5: xunion content |
| 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, // 0xdeadbeef + padding |
| |
| // secondary object 6: "after" length + pointer |
| 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "after" length |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "after" present |
| |
| // secondary object 7: "before" |
| 'a', 'f', 't', 'e', 'r', 0x00, 0x00, 0x00, // "after" |
| }, |
| }.check(t) |
| |
| var ipAddressConfig IpAddressConfig |
| ipAddressConfig.SetDhcp(true) |
| successCase{ |
| name: "add-ethernet-device-request", |
| input: &TestAddEthernetDeviceRequest{ |
| TopologicalPath: "@/dev/sys/pci/00:03.0/e1000/ethernet", |
| Config: InterfaceConfig{ |
| Name: "ethp0003", |
| IpAddressConfig: ipAddressConfig, |
| }, |
| Device: EthernetDeviceInterface{zx.Channel(0xdeadbeef)}, |
| }, |
| bytes: []byte{ |
| 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // topological_path |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // topological_path |
| 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // name |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // name |
| 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // subnet (dhcp variant) |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // padding |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // padding |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // padding |
| 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, // device (handle present) |
| 0x40, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x79, // @/dev/sy |
| 0x73, 0x2f, 0x70, 0x63, 0x69, 0x2f, 0x30, 0x30, // s/pci/00 |
| 0x3a, 0x30, 0x33, 0x2e, 0x30, 0x2f, 0x65, 0x31, // :03.0/e1 |
| 0x30, 0x30, 0x30, 0x2f, 0x65, 0x74, 0x68, 0x65, // 000/ethe |
| 0x72, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, 0x00, // rnet |
| 0x65, 0x74, 0x68, 0x70, 0x30, 0x30, 0x30, 0x33, // ethp0003 |
| }, |
| handles: []zx.Handle{ |
| zx.Handle(0xdeadbeef), |
| }, |
| }.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) { |
| for _, ex := range allErrorCasesUnmarshal { |
| 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() |
| } |
| output := makeDefault(ex.input) |
| b.StartTimer() |
| for n := 0; n < b.N; n++ { |
| if err := u.fn(respb[:nb], resph[:nh], output); err != nil { |
| b.Fail() |
| } |
| } |
| }) |
| } |
| } |
| |
| func BenchmarkUnmarshal(b *testing.B) { |
| for _, ex := range general() { |
| benchmarkUnmarshal(b, ex) |
| } |
| for _, ex := range fuchsia() { |
| benchmarkUnmarshal(b, ex) |
| } |
| } |