| // Copyright 2020 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. |
| |
| // +build !build_with_native_toolchain |
| |
| package component_test |
| |
| import ( |
| "context" |
| "errors" |
| "sync" |
| "syscall/zx" |
| "syscall/zx/fidl" |
| "syscall/zx/zxwait" |
| "testing" |
| "time" |
| |
| "go.fuchsia.dev/fuchsia/src/lib/component" |
| |
| "fidl/bindingstest" |
| ) |
| |
| var _ bindingstest.Test1WithCtx = (*test1Impl)(nil) |
| |
| type test1Impl struct{} |
| |
| func (*test1Impl) Echo(_ fidl.Context, s *string) (*string, error) { |
| return s, nil |
| } |
| |
| func (*test1Impl) NoResponse(fidl.Context) error { |
| return nil |
| } |
| |
| func (*test1Impl) EmptyResponse(fidl.Context) error { |
| return nil |
| } |
| |
| func (*test1Impl) EchoHandleRights(_ fidl.Context, h zx.Port) (uint32, error) { |
| infoHandleBasic, err := h.Handle().GetInfoHandleBasic() |
| if err != nil { |
| return 0, err |
| } |
| return uint32(infoHandleBasic.Rights), h.Close() |
| } |
| |
| func TestEmptyWriteErrors(t *testing.T) { |
| var wg sync.WaitGroup |
| |
| ch, sh, err := zx.NewChannel(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| if err := ch.Close(); err != nil { |
| t.Error(err) |
| } |
| // Wait for the serving goroutine to exit, then confirm that ServeExclusive |
| // closed the channel. |
| wg.Wait() |
| err := sh.Close() |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrBadHandle { |
| return |
| } |
| t.Errorf("got sh.Close() = %v, want %q", err, zx.ErrBadHandle) |
| }() |
| |
| errChan := make(chan error, 1) |
| |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| component.ServeExclusive(context.Background(), &bindingstest.Test1WithCtxStub{ |
| Impl: &test1Impl{}, |
| }, sh, func(err error) { |
| errChan <- err |
| }) |
| }() |
| |
| if err := ch.Write(nil, nil, 0); err != nil { |
| t.Fatal(err) |
| } |
| |
| if _, err := zxwait.Wait(zx.Handle(ch), zx.SignalChannelPeerClosed, zx.TimensecInfinite); err != nil { |
| t.Fatal(err) |
| } |
| |
| close(errChan) |
| for err := range errChan { |
| if !errors.Is(err, fidl.ErrPayloadTooSmall) { |
| t.Errorf("got ServeExclusive error = %v, want %q", err, fidl.ErrPayloadTooSmall) |
| } |
| } |
| } |
| |
| func TestEcho(t *testing.T) { |
| var wg sync.WaitGroup |
| |
| ch, sh, err := zx.NewChannel(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| // Expect the final test to have closed the client. |
| func() { |
| err := ch.Close() |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrBadHandle { |
| return |
| } |
| t.Errorf("got ch.Close() = %v, want %q", err, zx.ErrBadHandle) |
| }() |
| // Wait for the serving goroutine to exit, then confirm that ServeExclusive |
| // closed the channel. |
| wg.Wait() |
| func() { |
| err := sh.Close() |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrBadHandle { |
| return |
| } |
| t.Errorf("got sh.Close() = %v, want %q", err, zx.ErrBadHandle) |
| }() |
| }() |
| |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| component.ServeExclusive(context.Background(), &bindingstest.Test1WithCtxStub{ |
| Impl: &test1Impl{}, |
| }, sh, func(err error) { |
| t.Error(err) |
| }) |
| }() |
| |
| client := bindingstest.Test1WithCtxInterface{Channel: ch} |
| |
| t.Run("Basic", func(t *testing.T) { |
| str := "Hello World!" |
| r, err := client.Echo(context.Background(), &str) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if r == nil { |
| t.Fatalf("got Echo(%q) = nil", str) |
| } |
| if *r != str { |
| t.Fatalf("got Echo(%q) = %q", str, *r) |
| } |
| }) |
| |
| t.Run("NoResponse", func(t *testing.T) { |
| if err := client.NoResponse(context.Background()); err != nil { |
| t.Fatal(err) |
| } |
| }) |
| |
| t.Run("EmptyResponse", func(t *testing.T) { |
| if err := client.EmptyResponse(context.Background()); err != nil { |
| t.Fatal(err) |
| } |
| }) |
| |
| t.Run("Event", func(t *testing.T) { |
| const str = "Surprise!" |
| pxy := bindingstest.Test1EventProxy{Channel: sh} |
| if err := pxy.Surprise(str); err != nil { |
| t.Fatal(err) |
| } |
| s, err := client.ExpectSurprise(context.Background()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if s != str { |
| t.Fatalf("got Event = %q, want %q", str, s) |
| } |
| }) |
| |
| t.Run("ReducingHandleRights", func(t *testing.T) { |
| const expectedRights = zx.RightTransfer | zx.RightDuplicate | zx.RightRead |
| port, err := zx.NewPort(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| err := port.Close() |
| // Ownership should have moved to the server, and it should have closed |
| // the handle. |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrBadHandle { |
| return |
| } |
| t.Errorf("got port.Close() = %v, want %q", err, zx.ErrBadHandle) |
| }() |
| handleInfo, err := port.Handle().GetInfoHandleBasic() |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !handleInfo.Rights.StrictSupersetOf(expectedRights) { |
| t.Fatalf("got !(%b).StrictSupersetOf(%b)", handleInfo.Rights, expectedRights) |
| } |
| rights, err := client.EchoHandleRights(context.Background(), port) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rights := zx.Rights(rights); rights != expectedRights { |
| t.Fatalf("got EchoHandleRights(...) = %b, want %b", rights, expectedRights) |
| } |
| }) |
| |
| t.Run("MissingExpectedRightsSendingSide", func(t *testing.T) { |
| // This test tests the sending side when there are missing expected rights. |
| // The receiving side is harder to test because there needs to be a |
| // mismatch of rights on the sending and receiving side. |
| port, err := zx.NewPort(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| err := port.Close() |
| // This handle should have been consumed by the replace below. |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrBadHandle { |
| return |
| } |
| t.Errorf("got port.Close() = %v, want %q", err, zx.ErrBadHandle) |
| }() |
| const reducedRights = zx.RightTransfer | zx.RightDuplicate |
| h, err := port.Handle().Replace(reducedRights) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| err := h.Close() |
| // Ownership should have moved to the server, and it should have closed |
| // the handle. |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrBadHandle { |
| return |
| } |
| t.Errorf("got h.Close() = %v, want %q", err, zx.ErrBadHandle) |
| }() |
| handleInfo, err := h.GetInfoHandleBasic() |
| if err != nil { |
| t.Fatal(err) |
| } |
| if handleInfo.Rights != reducedRights { |
| t.Fatalf("got Rights = %b, want %b", handleInfo.Rights, reducedRights) |
| } |
| { |
| _, err := client.EchoHandleRights(context.Background(), zx.Port(h)) |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrInvalidArgs { |
| return |
| } |
| t.Fatalf("got EchoHandleRights(reducedRights) = %v, want %q", err, zx.ErrInvalidArgs) |
| } |
| }) |
| } |
| |
| func TestServeExclusive_MagicNumberCheck(t *testing.T) { |
| var wg sync.WaitGroup |
| |
| ch, sh, err := zx.NewChannel(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| if err := ch.Close(); err != nil { |
| t.Error(err) |
| } |
| // Wait for the serving goroutine to exit, then confirm that ServeExclusive |
| // closed the channel. |
| wg.Wait() |
| err := sh.Close() |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrBadHandle { |
| return |
| } |
| t.Errorf("got sh.Close() = %v, want %q", err, zx.ErrBadHandle) |
| }() |
| |
| errChan := make(chan error, 1) |
| |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| component.ServeExclusive(context.Background(), &bindingstest.Test1WithCtxStub{ |
| Impl: &test1Impl{}, |
| }, sh, func(err error) { |
| errChan <- err |
| }) |
| }() |
| |
| // Send an event with an unknown magic number. The method ordinal and message |
| // body can also be invalid but magic numbers are checked first. |
| request := []byte{ |
| 0, 0, 0, 0, // txid |
| 0, 0, 0, // flags |
| 0, // magic number |
| 0, 0, 0, 0, 0, 0, 0, 1, // method ordinal |
| 0, 0, 0, 0, 0, 0, 0, 0, // empty struct data |
| } |
| |
| if err := ch.Write(request, nil, 0); err != nil { |
| t.Fatal(err) |
| } |
| |
| if _, err := zxwait.Wait(zx.Handle(ch), zx.SignalChannelPeerClosed, zx.TimensecInfinite); err != nil { |
| t.Fatal(err) |
| } |
| |
| close(errChan) |
| for err := range errChan { |
| if !errors.Is(err, fidl.ErrUnknownMagic) { |
| t.Errorf("got ServeExclusive error = %v, want %q", err, fidl.ErrUnknownMagic) |
| } |
| } |
| } |
| |
| var _ bindingstest.Test1WithCtx = (*hangingImpl)(nil) |
| |
| type hangingImpl struct { |
| test1Impl |
| |
| // Used to coordinate blocking. |
| sem chan struct{} |
| } |
| |
| func (impl *hangingImpl) NoResponse(ctx fidl.Context) error { |
| defer func() { |
| <-impl.sem |
| }() |
| |
| for { |
| select { |
| case <-impl.sem: |
| case <-ctx.Done(): |
| return ctx.Err() |
| } |
| } |
| } |
| |
| func TestServeExclusiveConcurrent(t *testing.T) { |
| ch, sh, err := zx.NewChannel(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| if err := ch.Close(); err != nil { |
| t.Error(err) |
| } |
| }() |
| |
| impl := hangingImpl{ |
| sem: make(chan struct{}), |
| } |
| defer close(impl.sem) |
| |
| errChan := make(chan error, 1) |
| |
| done := make(chan struct{}) |
| go func() { |
| defer close(done) |
| component.ServeExclusiveConcurrent(context.Background(), &bindingstest.Test1WithCtxStub{ |
| Impl: &impl, |
| }, sh, func(err error) { |
| errChan <- err |
| }) |
| }() |
| |
| client := bindingstest.Test1WithCtxInterface{Channel: ch} |
| if err := client.NoResponse(context.Background()); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Ensure the handler is blocked. |
| impl.sem <- struct{}{} |
| |
| // Closing the channel cancels the handler's context. |
| if err := ch.Close(); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Handler should now be blocked on deferred channel read. |
| select { |
| case <-done: |
| t.Error("serving returned while handler running") |
| case <-time.After(100 * time.Millisecond): |
| } |
| |
| impl.sem <- struct{}{} |
| <-done |
| |
| // Serving should have closed the channel. |
| func() { |
| err := sh.Close() |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrBadHandle { |
| return |
| } |
| t.Errorf("got sh.Close() = %v, want %q", err, zx.ErrBadHandle) |
| }() |
| |
| close(errChan) |
| for err := range errChan { |
| if err != nil { |
| t.Errorf("ServeExclusiveConcurrent(...) = %s", err) |
| } |
| } |
| } |