@@ -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