| // 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 dispatch_test |
| |
| import ( |
| "sync" |
| "syscall/zx" |
| "syscall/zx/dispatch" |
| "testing" |
| ) |
| |
| func dispatcherTest(t *testing.T, name string, f func(t *testing.T, d *dispatch.Dispatcher)) { |
| t.Run(name, func(t *testing.T) { |
| d, err := dispatch.NewDispatcher() |
| if err != nil { |
| t.Fatal("creating dispatcher: ", err) |
| } |
| ch := make(chan struct{}) |
| go func() { |
| defer close(ch) |
| |
| d.Serve() |
| }() |
| defer func() { |
| d.Close() |
| <-ch |
| }() |
| f(t, d) |
| }) |
| } |
| |
| func TestCloseDispatcher(t *testing.T) { |
| d, err := dispatch.NewDispatcher() |
| if err != nil { |
| t.Fatal("creating dispatcher: ", err) |
| } |
| var wg sync.WaitGroup |
| const serveCalls = 5 |
| for i := 0; i < serveCalls; i++ { |
| wg.Add(1) |
| go func() { |
| d.Serve() |
| wg.Done() |
| }() |
| } |
| d.WaitForNServeLoops(serveCalls) |
| d.Close() |
| wg.Wait() |
| } |
| |
| func TestShutdownOne(t *testing.T) { |
| d, err := dispatch.NewDispatcher() |
| if err != nil { |
| t.Fatal("creating dispatcher: ", err) |
| } |
| var wg sync.WaitGroup |
| wg.Add(1) |
| go func() { |
| d.Serve() |
| wg.Done() |
| }() |
| d.WaitForNServeLoops(1) |
| if err := d.ShutdownOne(); err != nil { |
| t.Fatalf("ShutdownOne(): %s", err) |
| } |
| wg.Wait() |
| } |
| |
| func TestWait(t *testing.T) { |
| t.Parallel() |
| dispatcherTest(t, "WaitOnce", func(t *testing.T, d *dispatch.Dispatcher) { |
| h0, h1, err := zx.NewChannel(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| _ = h0.Close() |
| _ = h1.Close() |
| }() |
| |
| done := make(chan struct{}) |
| |
| handler := func(error, *zx.PacketSignal) dispatch.WaitResult { |
| close(done) |
| return dispatch.WaitFinished |
| } |
| |
| if _, err := d.BeginWait(zx.Handle(h0), zx.SignalChannelPeerClosed, handler); err != nil { |
| t.Fatal(err) |
| } |
| |
| if err := h1.Close(); err != nil { |
| t.Fatal(err) |
| } |
| |
| <-done |
| }) |
| dispatcherTest(t, "WaitMultipleTimes", func(t *testing.T, d *dispatch.Dispatcher) { |
| h0, h1, err := zx.NewChannel(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| _ = h0.Close() |
| _ = h1.Close() |
| }() |
| |
| done := make(chan struct{}) |
| |
| handler := func(error, *zx.PacketSignal) dispatch.WaitResult { |
| var b [zx.ChannelMaxMessageBytes]byte |
| var h [zx.ChannelMaxMessageHandles]zx.Handle |
| bn, hn, err := h0.Read(b[:], h[:], 0) |
| if err != nil { |
| t.Fatal("error reading: ", err) |
| } |
| if bn != 1 || hn != 0 { |
| t.Fatalf("unexpected read %d bytes %d handles", bn, hn) |
| } |
| if b[0] == 1 { |
| return dispatch.WaitAgain |
| } |
| close(done) |
| return dispatch.WaitFinished |
| } |
| |
| if _, err := d.BeginWait(zx.Handle(h0), zx.SignalChannelReadable, handler); err != nil { |
| t.Fatal(err) |
| } |
| |
| for i := 0; i < 5; i++ { |
| if err := h1.Write([]byte{1}, nil, 0); err != nil { |
| t.Fatal(err) |
| } |
| } |
| if err := h1.Write([]byte{0}, nil, 0); err != nil { |
| t.Fatal(err) |
| } |
| |
| <-done |
| }) |
| dispatcherTest(t, "CancelWait", func(t *testing.T, d *dispatch.Dispatcher) { |
| h0, h1, err := zx.NewChannel(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| _ = h0.Close() |
| _ = h1.Close() |
| }() |
| |
| handler := func(err error, sigs *zx.PacketSignal) dispatch.WaitResult { |
| t.Fatalf("unexpected call to handler err=%v sigs=%+v", err, sigs) |
| return dispatch.WaitFinished |
| } |
| |
| id, err := d.BeginWait(zx.Handle(h0), zx.SignalChannelPeerClosed, handler) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if err := d.CancelWait(id); err != nil { |
| t.Fatal(err) |
| } |
| |
| if err := h1.Close(); err != nil { |
| t.Fatal(err) |
| } |
| }) |
| dispatcherTest(t, "Shutdown", func(t *testing.T, d *dispatch.Dispatcher) { |
| h0, h1, err := zx.NewChannel(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| _ = h0.Close() |
| _ = h1.Close() |
| }() |
| |
| d.Close() |
| |
| if _, err := d.BeginWait(zx.Handle(h0), zx.SignalChannelPeerClosed, nil); err == nil { |
| t.Fatal("unexpected success for BeginWait on shut down dispatcher") |
| } |
| }) |
| dispatcherTest(t, "MultipleGoroutines", func(t *testing.T, d *dispatch.Dispatcher) { |
| // Spin up 5 goroutines all serving the dispatcher. |
| var wg sync.WaitGroup |
| for i := 0; i < 5; i++ { |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| |
| d.Serve() |
| }() |
| } |
| defer func() { |
| d.Close() |
| wg.Wait() |
| }() |
| |
| done := make(chan struct{}, 20) |
| defer close(done) |
| |
| var ends []zx.Channel |
| for i := 0; i < cap(done); i++ { |
| h0, h1, err := zx.NewChannel(0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { |
| _ = h0.Close() |
| _ = h1.Close() |
| }() |
| |
| handler := func(error, *zx.PacketSignal) dispatch.WaitResult { |
| done <- struct{}{} |
| return dispatch.WaitFinished |
| } |
| |
| if _, err := d.BeginWait(zx.Handle(h0), zx.SignalChannelPeerClosed, handler); err != nil { |
| t.Fatal(err) |
| } |
| ends = append(ends, h1) |
| } |
| for _, e := range ends { |
| if err := e.Close(); err != nil { |
| t.Fatal(err) |
| } |
| } |
| for i := 0; i < 20; i++ { |
| <-done |
| } |
| }) |
| } |