1616
1717import pathlib
1818import threading
19+ import time
1920
2021from composition_interfaces .srv import LoadNode
2122
4445
4546class MockComponentContainer (rclpy .node .Node ):
4647
47- def __init__ (self , context ):
48+ def __init__ (self , context , delay = 0.0 ):
4849 # List of LoadNode requests received
4950 self .requests = []
51+ self .delay = delay
5052
5153 super ().__init__ (TEST_CONTAINER_NAME , context = context )
5254
@@ -57,6 +59,8 @@ def __init__(self, context):
5759 )
5860
5961 def load_node_callback (self , request , response ):
62+ if self .delay > 0.0 :
63+ time .sleep (self .delay )
6064 self .requests .append (request )
6165 response .success = True
6266 if request .node_namespace == '/' :
@@ -84,10 +88,12 @@ def _load_composable_node(
8488 condition = None ,
8589 parameters = None ,
8690 remappings = None ,
87- target_container = f'/{ TEST_CONTAINER_NAME } '
91+ target_container = f'/{ TEST_CONTAINER_NAME } ' ,
92+ load_node_timeout = None
8893):
8994 return LoadComposableNodes (
9095 target_container = target_container ,
96+ load_node_timeout = load_node_timeout ,
9197 composable_node_descriptions = [
9298 ComposableNode (
9399 condition = condition ,
@@ -119,6 +125,25 @@ def mock_component_container():
119125 thread .join ()
120126
121127
128+ @pytest .fixture
129+ def slow_mock_component_container ():
130+ """Mock container with 3-second delay for timeout testing."""
131+ context = rclpy .context .Context ()
132+ with rclpy .init (context = context ):
133+ executor = rclpy .executors .SingleThreadedExecutor (context = context )
134+
135+ container = MockComponentContainer (context , delay = 3.0 )
136+ executor .add_node (container )
137+
138+ # Start spinning in a thread
139+ thread = threading .Thread (target = lambda executor : executor .spin (), args = (executor ,))
140+ thread .start ()
141+ yield container
142+ executor .remove_node (container )
143+ executor .shutdown ()
144+ thread .join ()
145+
146+
122147def test_load_node (mock_component_container ):
123148 """Test loading a node."""
124149 context = _assert_launch_no_errors ([
@@ -654,3 +679,45 @@ def test_load_node_with_condition_in_group(mock_component_container):
654679 assert len (request .remap_rules ) == 0
655680 assert len (request .parameters ) == 0
656681 assert len (request .extra_arguments ) == 0
682+
683+
684+ def test_load_node_with_timeout (slow_mock_component_container ):
685+ """Test that load_node_timeout actually times out slow services."""
686+ # Test 1: Timeout is shorter than service delay - should timeout and not load node
687+ start_time = time .time ()
688+ launch_context = _assert_launch_no_errors ([
689+ _load_composable_node (
690+ package = 'foo_package' ,
691+ plugin = 'bar_plugin' ,
692+ name = 'test_node_timeout' ,
693+ namespace = 'test_namespace' ,
694+ load_node_timeout = 0.5 # Shorter than the 3 second delay
695+ )
696+ ])
697+ elapsed_time = time .time () - start_time
698+
699+ # Should timeout before the 3 second service response
700+ assert elapsed_time < 3.0 , f'Expected quick timeout, but took { elapsed_time :.2f} seconds'
701+ # Node should NOT be registered since we skipped waiting for service response
702+ assert get_node_name_count (launch_context , '/test_namespace/test_node_timeout' ) == 0
703+
704+ # Test 2: Timeout is longer than service delay - should succeed
705+ start_time = time .time ()
706+ launch_context = _assert_launch_no_errors ([
707+ _load_composable_node (
708+ package = 'foo_package' ,
709+ plugin = 'bar_plugin' ,
710+ name = 'test_node_success' ,
711+ namespace = 'test_namespace' ,
712+ load_node_timeout = 5.0 # Longer than the 3 second delay
713+ )
714+ ])
715+ elapsed_time = time .time () - start_time
716+
717+ # Should wait for the full service response (around 3-4 seconds including overhead)
718+ assert elapsed_time >= 2.5 , f'Should have waited for service, took { elapsed_time :.2f} seconds'
719+ assert elapsed_time < 7.0 , f'Should not have timed out, took { elapsed_time :.2f} seconds'
720+ # Node SHOULD be registered since it completed successfully
721+ assert get_node_name_count (launch_context , '/test_namespace/test_node_success' ) == 1
722+ # Verify the container received both requests
723+ assert len (slow_mock_component_container .requests ) == 2
0 commit comments