package p2p import ( "context" "errors" "testing" "time" "github.com/fortytw2/leaktest" "github.com/stretchr/testify/require" ) type channelInternal struct { In chan Envelope Out chan Envelope Error chan PeerError } func testChannel(size int) (*channelInternal, *Channel) { in := &channelInternal{ In: make(chan Envelope, size), Out: make(chan Envelope, size), Error: make(chan PeerError, size), } ch := &Channel{ inCh: in.In, outCh: in.Out, errCh: in.Error, } return in, ch } func TestChannel(t *testing.T) { t.Cleanup(leaktest.Check(t)) bctx, bcancel := context.WithCancel(context.Background()) defer bcancel() testCases := []struct { Name string Case func(context.Context, *testing.T) }{ { Name: "Send", Case: func(ctx context.Context, t *testing.T) { ins, ch := testChannel(1) require.NoError(t, ch.Send(ctx, Envelope{From: "kip", To: "merlin"})) res, ok := <-ins.Out require.True(t, ok) require.EqualValues(t, "kip", res.From) require.EqualValues(t, "merlin", res.To) }, }, { Name: "SendError", Case: func(ctx context.Context, t *testing.T) { ins, ch := testChannel(1) require.NoError(t, ch.SendError(ctx, PeerError{NodeID: "kip", Err: errors.New("merlin")})) res, ok := <-ins.Error require.True(t, ok) require.EqualValues(t, "kip", res.NodeID) require.EqualValues(t, "merlin", res.Err.Error()) }, }, { Name: "SendWithCanceledContext", Case: func(ctx context.Context, t *testing.T) { _, ch := testChannel(0) cctx, ccancel := context.WithCancel(ctx) ccancel() require.Error(t, ch.Send(cctx, Envelope{From: "kip", To: "merlin"})) }, }, { Name: "SendErrorWithCanceledContext", Case: func(ctx context.Context, t *testing.T) { _, ch := testChannel(0) cctx, ccancel := context.WithCancel(ctx) ccancel() require.Error(t, ch.SendError(cctx, PeerError{NodeID: "kip", Err: errors.New("merlin")})) }, }, { Name: "ReceiveEmptyIteratorBlocks", Case: func(ctx context.Context, t *testing.T) { _, ch := testChannel(1) iter := ch.Receive(ctx) require.NotNil(t, iter) out := make(chan bool) go func() { defer close(out) select { case <-ctx.Done(): case out <- iter.Next(ctx): } }() select { case <-time.After(10 * time.Millisecond): case <-out: require.Fail(t, "iterator should not advance") } require.Nil(t, iter.Envelope()) }, }, { Name: "ReceiveWithData", Case: func(ctx context.Context, t *testing.T) { ins, ch := testChannel(1) ins.In <- Envelope{From: "kip", To: "merlin"} iter := ch.Receive(ctx) require.NotNil(t, iter) require.True(t, iter.Next(ctx)) res := iter.Envelope() require.EqualValues(t, "kip", res.From) require.EqualValues(t, "merlin", res.To) }, }, { Name: "ReceiveWithCanceledContext", Case: func(ctx context.Context, t *testing.T) { _, ch := testChannel(0) cctx, ccancel := context.WithCancel(ctx) ccancel() iter := ch.Receive(cctx) require.NotNil(t, iter) require.False(t, iter.Next(cctx)) require.Nil(t, iter.Envelope()) }, }, { Name: "IteratorWithCanceledContext", Case: func(ctx context.Context, t *testing.T) { _, ch := testChannel(0) iter := ch.Receive(ctx) require.NotNil(t, iter) cctx, ccancel := context.WithCancel(ctx) ccancel() require.False(t, iter.Next(cctx)) require.Nil(t, iter.Envelope()) }, }, { Name: "IteratorCanceledAfterFirstUseBecomesNil", Case: func(ctx context.Context, t *testing.T) { ins, ch := testChannel(1) ins.In <- Envelope{From: "kip", To: "merlin"} iter := ch.Receive(ctx) require.NotNil(t, iter) require.True(t, iter.Next(ctx)) res := iter.Envelope() require.EqualValues(t, "kip", res.From) require.EqualValues(t, "merlin", res.To) cctx, ccancel := context.WithCancel(ctx) ccancel() require.False(t, iter.Next(cctx)) require.Nil(t, iter.Envelope()) }, }, { Name: "IteratorMultipleNextCalls", Case: func(ctx context.Context, t *testing.T) { ins, ch := testChannel(1) ins.In <- Envelope{From: "kip", To: "merlin"} iter := ch.Receive(ctx) require.NotNil(t, iter) require.True(t, iter.Next(ctx)) res := iter.Envelope() require.EqualValues(t, "kip", res.From) require.EqualValues(t, "merlin", res.To) res1 := iter.Envelope() require.Equal(t, res, res1) }, }, { Name: "IteratorProducesNilObjectBeforeNext", Case: func(ctx context.Context, t *testing.T) { ins, ch := testChannel(1) iter := ch.Receive(ctx) require.NotNil(t, iter) require.Nil(t, iter.Envelope()) ins.In <- Envelope{From: "kip", To: "merlin"} require.NotNil(t, iter) require.True(t, iter.Next(ctx)) res := iter.Envelope() require.NotNil(t, res) require.EqualValues(t, "kip", res.From) require.EqualValues(t, "merlin", res.To) }, }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { t.Cleanup(leaktest.Check(t)) ctx, cancel := context.WithCancel(bctx) defer cancel() tc.Case(ctx, t) }) } }