Skip to content

Commit 27cdc47

Browse files
authored
Add tests for MonitorUnreachable (ansible#1386)
1 parent afc3c9b commit 27cdc47

2 files changed

Lines changed: 156 additions & 3 deletions

File tree

pkg/netceptor/conn.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ func (li *Listener) acceptLoop(ctx context.Context) {
283283
}
284284
rAddr, ok := conn.RemoteAddr().(Addr)
285285
if ok {
286-
go monitorUnreachable(li.pc, doneChan, rAddr, ccancel)
286+
go MonitorUnreachable(li.pc, doneChan, rAddr, ccancel)
287287
}
288288
go func() {
289289
select {
@@ -409,7 +409,7 @@ func (s *Netceptor) DialContext(ctx context.Context, node string, service string
409409
}
410410
}()
411411
doneChan := make(chan struct{}, 1)
412-
go monitorUnreachable(pc, doneChan, rAddr, ccancel)
412+
go MonitorUnreachable(pc, doneChan, rAddr, ccancel)
413413
_ = os.Setenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING", "1")
414414
statelessResetKey := make([]byte, 32)
415415
rand.Read(statelessResetKey)
@@ -470,7 +470,7 @@ func (s *Netceptor) DialContext(ctx context.Context, node string, service string
470470

471471
// monitorUnreachable receives unreachable messages from the underlying PacketConn, and ends the connection
472472
// if the remote service has gone away.
473-
func monitorUnreachable(pc PacketConner, doneChan chan struct{}, remoteAddr Addr, cancel context.CancelFunc) {
473+
func MonitorUnreachable(pc PacketConner, doneChan chan struct{}, remoteAddr Addr, cancel context.CancelFunc) {
474474
msgCh := pc.SubscribeUnreachable(doneChan)
475475
if msgCh == nil {
476476
cancel()

pkg/netceptor/conn_test.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,3 +738,156 @@ func TestNeceptorListen(t *testing.T) {
738738
assert.NotPanics(t, func() { time.AfterFunc(500*time.Millisecond, cancel) })
739739
})
740740
}
741+
742+
func TestMonitorUnreachable(t *testing.T) {
743+
testSetup := func(t *testing.T) (*gomock.Controller, *mock_netceptor.MockPacketConner, chan struct{}, *netceptor.Netceptor, netceptor.Addr, context.Context, context.CancelFunc) {
744+
ctrl := gomock.NewController(t)
745+
mockPC := mock_netceptor.NewMockPacketConner(ctrl)
746+
doneChan := make(chan struct{})
747+
n := netceptor.New(context.Background(), "test")
748+
remoteAddr := n.NewAddr("testnode", "testsvc")
749+
ctx, cancel := context.WithCancel(context.Background())
750+
751+
// Setup cleanup for this test
752+
t.Cleanup(func() {
753+
cancel()
754+
ctrl.Finish()
755+
n.Shutdown()
756+
})
757+
758+
return ctrl, mockPC, doneChan, n, remoteAddr, ctx, cancel
759+
}
760+
761+
t.Run("SubscribeUnreachable returns nil channel", func(t *testing.T) {
762+
_, mockPC, doneChan, _, remoteAddr, ctx, cancel := testSetup(t)
763+
764+
mockPC.EXPECT().SubscribeUnreachable(doneChan).Return(nil).Times(1)
765+
netceptor.MonitorUnreachable(mockPC, doneChan, remoteAddr, cancel)
766+
767+
// Check if context was cancelled
768+
select {
769+
case <-ctx.Done():
770+
// Cancel was called as expected
771+
default:
772+
t.Error("Expected cancel to be called when SubscribeUnreachable returns nil")
773+
}
774+
})
775+
776+
t.Run("Message matches and triggers cancellation", func(t *testing.T) {
777+
_, mockPC, doneChan, n, remoteAddr, ctx, cancel := testSetup(t)
778+
779+
msgCh := make(chan netceptor.UnreachableNotification, 1)
780+
mockPC.EXPECT().SubscribeUnreachable(doneChan).Return(msgCh).Times(1)
781+
mockPC.EXPECT().GetLogger().Return(n.GetLogger()).Times(1)
782+
783+
go func() {
784+
matchingMsg := netceptor.UnreachableNotification{
785+
UnreachableMessage: netceptor.UnreachableMessage{
786+
FromNode: "sourcenode",
787+
ToNode: "testnode",
788+
FromService: "sourcesvc",
789+
ToService: "testsvc",
790+
Problem: netceptor.ProblemServiceUnknown,
791+
},
792+
ReceivedFromNode: "sourcenode",
793+
}
794+
msgCh <- matchingMsg
795+
close(msgCh)
796+
}()
797+
798+
go netceptor.MonitorUnreachable(mockPC, doneChan, remoteAddr, cancel)
799+
800+
select {
801+
case <-ctx.Done():
802+
// Cancel was called as expected
803+
case <-time.After(100 * time.Millisecond):
804+
t.Error("Expected cancel to be called when matching message is received")
805+
}
806+
})
807+
808+
t.Run("Non-matching messages do not trigger cancellation", func(t *testing.T) {
809+
_, mockPC, doneChan, _, remoteAddr, ctx, cancel := testSetup(t)
810+
811+
msgCh := make(chan netceptor.UnreachableNotification, 3)
812+
mockPC.EXPECT().SubscribeUnreachable(doneChan).Return(msgCh).Times(1)
813+
814+
go func() {
815+
// Wrong node
816+
msgCh <- netceptor.UnreachableNotification{
817+
UnreachableMessage: netceptor.UnreachableMessage{
818+
ToNode: "wrongnode",
819+
ToService: "testsvc",
820+
Problem: netceptor.ProblemServiceUnknown,
821+
},
822+
}
823+
824+
// Wrong service
825+
msgCh <- netceptor.UnreachableNotification{
826+
UnreachableMessage: netceptor.UnreachableMessage{
827+
ToNode: "testnode",
828+
ToService: "wrongsvc",
829+
Problem: netceptor.ProblemServiceUnknown,
830+
},
831+
}
832+
833+
// Wrong problem type
834+
msgCh <- netceptor.UnreachableNotification{
835+
UnreachableMessage: netceptor.UnreachableMessage{
836+
ToNode: "testnode",
837+
ToService: "testsvc",
838+
Problem: "different problem",
839+
},
840+
}
841+
842+
close(msgCh)
843+
}()
844+
845+
go netceptor.MonitorUnreachable(mockPC, doneChan, remoteAddr, cancel)
846+
time.Sleep(10 * time.Millisecond)
847+
848+
// Check that context was NOT cancelled
849+
select {
850+
case <-ctx.Done():
851+
t.Error("Expected cancel NOT to be called for non-matching messages")
852+
default:
853+
// Expected - context should not be cancelled
854+
}
855+
})
856+
857+
t.Run("Channel closure terminates monitoring normally", func(t *testing.T) {
858+
_, mockPC, doneChan, _, remoteAddr, ctx, cancel := testSetup(t)
859+
860+
msgCh := make(chan netceptor.UnreachableNotification)
861+
mockPC.EXPECT().SubscribeUnreachable(doneChan).Return(msgCh).Times(1)
862+
863+
go func() {
864+
close(msgCh)
865+
}()
866+
867+
go func() {
868+
netceptor.MonitorUnreachable(mockPC, doneChan, remoteAddr, cancel)
869+
// Signal completion by closing doneChan
870+
select {
871+
case <-doneChan:
872+
// doneChan already closed, don't close again
873+
default:
874+
close(doneChan)
875+
}
876+
}()
877+
878+
select {
879+
case <-doneChan:
880+
// Function returned normally - verify context was NOT cancelled
881+
select {
882+
case <-ctx.Done():
883+
t.Error("Expected cancel NOT to be called on normal completion")
884+
default:
885+
// Expected - context should not be cancelled
886+
}
887+
case <-ctx.Done():
888+
t.Error("Context was cancelled unexpectedly during normal completion")
889+
case <-time.After(100 * time.Millisecond):
890+
t.Error("Function did not return after channel closure")
891+
}
892+
})
893+
}

0 commit comments

Comments
 (0)