1515"""Tests for the LoadComposableNodes Action."""
1616
1717import asyncio
18+ import threading
19+
20+ from composition_interfaces .srv import LoadNode
1821
1922from launch import LaunchDescription
2023from launch import LaunchService
2124from launch .actions import GroupAction
22- from launch_ros .actions import ComposableNodeContainer
2325from launch_ros .actions import LoadComposableNodes
2426from launch_ros .actions import PushRosNamespace
2527from launch_ros .actions import SetRemap
2830
2931import osrf_pycommon .process_utils
3032
31- TEST_CONTAINER_NAME = 'test_load_composable_nodes_container'
33+ import pytest
34+
35+ from rcl_interfaces .msg import ParameterType
36+
37+ import rclpy
38+ import rclpy .context
39+ import rclpy .executors
40+ import rclpy .node
41+
42+ TEST_CONTAINER_NAME = 'mock_component_container'
3243TEST_NODE_NAME = 'test_load_composable_nodes_node'
3344
3445
46+ class MockComponentContainer (rclpy .node .Node ):
47+
48+ def __init__ (self ):
49+ # List of LoadNode requests received
50+ self .requests = []
51+
52+ self ._context = rclpy .context .Context ()
53+ rclpy .init (context = self ._context )
54+
55+ super ().__init__ (TEST_CONTAINER_NAME , context = self ._context )
56+
57+ self .load_node_service = self .create_service (
58+ LoadNode ,
59+ '~/_container/load_node' ,
60+ self .load_node_callback
61+ )
62+
63+ self ._executor = rclpy .executors .SingleThreadedExecutor (context = self ._context )
64+
65+ # Start spinning in a thread
66+ self ._thread = threading .Thread (
67+ target = rclpy .spin ,
68+ args = (self , self ._executor ),
69+ daemon = True
70+ )
71+ self ._thread .start ()
72+
73+ def load_node_callback (self , request , response ):
74+ self .requests .append (request )
75+ response .success = True
76+ response .full_node_name = f'{ request .node_namespace } /{ request .node_name } '
77+ response .unique_id = len (self .requests )
78+ return response
79+
80+ def shutdown (self ):
81+ rclpy .shutdown (context = self ._context )
82+ self ._thread .join ()
83+
84+
3585def _assert_launch_no_errors (actions , * , timeout_sec = 1 ):
3686 ld = LaunchDescription (actions )
3787 ls = LaunchService (debug = True )
@@ -47,107 +97,193 @@ def _assert_launch_no_errors(actions, *, timeout_sec=1):
4797 return ls .context
4898
4999
50- def _create_node_container (* , parameters = None , remappings = None , namespace = '' ):
51- return ComposableNodeContainer (
52- package = 'rclcpp_components' ,
53- executable = 'component_container' ,
54- name = TEST_CONTAINER_NAME ,
55- namespace = namespace ,
56- output = 'screen' ,
57- parameters = parameters ,
58- remappings = remappings ,
59- )
60-
61-
62100def _load_composable_node (
63101 * ,
102+ package ,
103+ plugin ,
104+ name ,
105+ namespace = '' ,
64106 parameters = None ,
65107 remappings = None ,
66- target_container = TEST_CONTAINER_NAME
108+ target_container = f'/ { TEST_CONTAINER_NAME } '
67109):
68110 return LoadComposableNodes (
69111 target_container = target_container ,
70112 composable_node_descriptions = [
71113 ComposableNode (
72- package = 'composition' ,
73- plugin = 'composition::Listener' ,
74- name = TEST_NODE_NAME ,
114+ package = package ,
115+ plugin = plugin ,
116+ name = name ,
117+ namespace = namespace ,
75118 parameters = parameters ,
76119 remappings = remappings ,
77120 )
78121 ])
79122
80123
81- def test_load_invalid_node ():
82- """Test loading an invalid node."""
83- load_composable_nodes = LoadComposableNodes (
84- target_container = TEST_CONTAINER_NAME ,
85- composable_node_descriptions = [
86- ComposableNode (
87- package = 'nonexistent_package' ,
88- plugin = 'this_plugin_should_not_exist' ,
89- name = TEST_NODE_NAME ,
90- )
91- ])
92- context = _assert_launch_no_errors ([_create_node_container (), load_composable_nodes ])
93-
94- assert get_node_name_count (context , f'/{ TEST_NODE_NAME } ' ) == 0
95- assert get_node_name_count (context , f'/{ TEST_CONTAINER_NAME } ' ) == 1
124+ @pytest .fixture
125+ def mock_component_container ():
126+ container = MockComponentContainer ()
127+ yield container
128+ container .shutdown ()
96129
97130
98- def test_load_node ():
131+ def test_load_node (mock_component_container ):
99132 """Test loading a node."""
100133 context = _assert_launch_no_errors ([
101- _create_node_container (), _load_composable_node ()
134+ _load_composable_node (
135+ package = 'foo_package' ,
136+ plugin = 'bar_plugin' ,
137+ name = 'test_node_name' ,
138+ namespace = 'test_node_namespace'
139+ )
102140 ])
103141
104- assert get_node_name_count (context , f'/{ TEST_NODE_NAME } ' ) == 1
105- assert get_node_name_count (context , f'/{ TEST_CONTAINER_NAME } ' ) == 1
142+ # Check that launch is aware of loaded component
143+ assert get_node_name_count (context , '/test_node_namespace/test_node_name' ) == 1
144+
145+ # Check that container recieved correct request
146+ assert len (mock_component_container .requests ) == 1
147+ request = mock_component_container .requests [0 ]
148+ assert request .package_name == 'foo_package'
149+ assert request .plugin_name == 'bar_plugin'
150+ assert request .node_name == 'test_node_name'
151+ assert request .node_namespace == '/test_node_namespace'
152+ assert len (request .remap_rules ) == 0
153+ assert len (request .parameters ) == 0
154+ assert len (request .extra_arguments ) == 0
155+
156+
157+ def test_load_node_with_remaps (mock_component_container ):
158+ """Test loading a node with remappings."""
159+ context = _assert_launch_no_errors ([
160+ _load_composable_node (
161+ package = 'foo_package' ,
162+ plugin = 'bar_plugin' ,
163+ name = 'test_node_name' ,
164+ namespace = 'test_node_namespace' ,
165+ remappings = [
166+ ('test_topic1' , 'test_remap_topic1' ),
167+ ('test/topic/two' , 'test/remap_topic2' )
168+ ]
169+ )
170+ ])
171+
172+ # Check that launch is aware of loaded component
173+ assert get_node_name_count (context , '/test_node_namespace/test_node_name' ) == 1
174+
175+ # Check that container recieved correct request
176+ assert len (mock_component_container .requests ) == 1
177+ request = mock_component_container .requests [0 ]
178+ assert request .package_name == 'foo_package'
179+ assert request .plugin_name == 'bar_plugin'
180+ assert request .node_name == 'test_node_name'
181+ assert request .node_namespace == '/test_node_namespace'
182+ assert len (request .remap_rules ) == 2
183+ assert request .remap_rules [0 ] == 'test_topic1:=test_remap_topic1'
184+ assert request .remap_rules [1 ] == 'test/topic/two:=test/remap_topic2'
185+ assert len (request .parameters ) == 0
186+ assert len (request .extra_arguments ) == 0
187+
188+
189+ def test_load_node_with_params (mock_component_container ):
190+ """Test loading a node with parameters."""
191+ context = _assert_launch_no_errors ([
192+ _load_composable_node (
193+ package = 'foo_package' ,
194+ plugin = 'bar_plugin' ,
195+ name = 'test_node_name' ,
196+ namespace = 'test_node_namespace' ,
197+ parameters = [{
198+ 'test_param1' : 'test_value_param1' ,
199+ 'test.param2' : '42.0' ,
200+ }]
201+ )
202+ ])
203+
204+ # Check that launch is aware of loaded component
205+ assert get_node_name_count (context , '/test_node_namespace/test_node_name' ) == 1
206+
207+ # Check that container recieved correct request
208+ assert len (mock_component_container .requests ) == 1
209+ request = mock_component_container .requests [0 ]
210+ assert request .package_name == 'foo_package'
211+ assert request .plugin_name == 'bar_plugin'
212+ assert request .node_name == 'test_node_name'
213+ assert request .node_namespace == '/test_node_namespace'
214+ assert len (request .remap_rules ) == 0
215+ assert len (request .parameters ) == 2
216+ assert request .parameters [0 ].name == 'test_param1'
217+ assert request .parameters [0 ].value .type == ParameterType .PARAMETER_STRING
218+ assert request .parameters [0 ].value .string_value == 'test_value_param1'
219+ assert request .parameters [1 ].name == 'test.param2'
220+ # TODO(jacobperron): I would expect this to be a double value, but we can only pass strings
221+ # assert request.parameters[1].value.type == ParameterType.PARAMETER_DOUBLE
222+ # assert request.parameters[1].value.double_value == 42.0
223+ assert request .parameters [1 ].value .string_value == '42.0'
224+ assert len (request .extra_arguments ) == 0
106225
107226
108- def test_load_node_with_global_remaps_in_group ():
227+ def test_load_node_with_global_remaps_in_group (mock_component_container ):
109228 """Test loading a node with global remaps scoped to a group."""
110- load_composable_nodes_action = _load_composable_node ()
111229 context = _assert_launch_no_errors ([
112- _create_node_container (),
113230 GroupAction (
114231 [
115232 SetRemap ('chatter' , 'new_topic_name' ),
116- load_composable_nodes_action ,
233+ _load_composable_node (
234+ package = 'foo_package' ,
235+ plugin = 'bar_plugin' ,
236+ name = 'test_node_name' ,
237+ namespace = 'test_node_namespace'
238+ ),
117239 ],
118240 scoped = True ,
119241 ),
120242 ])
121243
122- assert get_node_name_count ( context , f'/ { TEST_NODE_NAME } ' ) == 1
123- assert get_node_name_count (context , f'/ { TEST_CONTAINER_NAME } ' ) == 1
244+ # Check that launch is aware of loaded component
245+ assert get_node_name_count (context , '/test_node_namespace/test_node_name ' ) == 1
124246
125- # Check the remaps in load service request
126- assert len (load_composable_nodes_action ._LoadComposableNodes__load_node_requests ) == 1
127- request = load_composable_nodes_action ._LoadComposableNodes__load_node_requests [0 ]
247+ # Check that container recieved correct request
248+ assert len (mock_component_container .requests ) == 1
249+ request = mock_component_container .requests [0 ]
250+ assert request .package_name == 'foo_package'
251+ assert request .plugin_name == 'bar_plugin'
252+ assert request .node_name == 'test_node_name'
253+ assert request .node_namespace == '/test_node_namespace'
128254 assert len (request .remap_rules ) == 1
129255 assert request .remap_rules [0 ] == 'chatter:=new_topic_name'
256+ assert len (request .parameters ) == 0
257+ assert len (request .extra_arguments ) == 0
130258
131259
132- def test_load_node_with_namespace_in_group ():
260+ def test_load_node_with_namespace_in_group (mock_component_container ):
133261 """Test loading a node with namespace scoped to a group."""
134- namespace = '/foo'
135- load_composable_nodes_action = _load_composable_node ()
136262 context = _assert_launch_no_errors ([
137- _create_node_container (),
138263 GroupAction (
139264 [
140- PushRosNamespace (namespace ),
141- load_composable_nodes_action ,
265+ PushRosNamespace ('foo' ),
266+ _load_composable_node (
267+ package = 'foo_package' ,
268+ plugin = 'bar_plugin' ,
269+ name = 'test_node_name' ,
270+ namespace = 'test_node_namespace'
271+ ),
142272 ],
143273 scoped = True ,
144274 ),
145275 ])
146276
147- assert get_node_name_count ( context , f' { namespace } / { TEST_NODE_NAME } ' ) == 1
148- assert get_node_name_count (context , f'/ { TEST_CONTAINER_NAME } ' ) == 1
277+ # Check that launch is aware of loaded component
278+ assert get_node_name_count (context , '/foo/test_node_namespace/test_node_name ' ) == 1
149279
150- # Check the namespace in load service request
151- assert len (load_composable_nodes_action ._LoadComposableNodes__load_node_requests ) == 1
152- request = load_composable_nodes_action ._LoadComposableNodes__load_node_requests [0 ]
153- assert request .node_namespace == f'{ namespace } '
280+ # Check that container recieved correct request
281+ assert len (mock_component_container .requests ) == 1
282+ request = mock_component_container .requests [0 ]
283+ assert request .package_name == 'foo_package'
284+ assert request .plugin_name == 'bar_plugin'
285+ assert request .node_name == 'test_node_name'
286+ assert request .node_namespace == '/foo/test_node_namespace'
287+ assert len (request .remap_rules ) == 0
288+ assert len (request .parameters ) == 0
289+ assert len (request .extra_arguments ) == 0
0 commit comments