Skip to content

Commit 5d9dcf3

Browse files
committed
Refactor tests to use mock component container
Signed-off-by: Jacob Perron <[email protected]>
1 parent a12906b commit 5d9dcf3

File tree

1 file changed

+194
-58
lines changed

1 file changed

+194
-58
lines changed

test_launch_ros/test/test_launch_ros/actions/test_load_composable_nodes.py

Lines changed: 194 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
"""Tests for the LoadComposableNodes Action."""
1616

1717
import asyncio
18+
import threading
19+
20+
from composition_interfaces.srv import LoadNode
1821

1922
from launch import LaunchDescription
2023
from launch import LaunchService
2124
from launch.actions import GroupAction
22-
from launch_ros.actions import ComposableNodeContainer
2325
from launch_ros.actions import LoadComposableNodes
2426
from launch_ros.actions import PushRosNamespace
2527
from launch_ros.actions import SetRemap
@@ -28,10 +30,58 @@
2830

2931
import 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'
3243
TEST_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+
3585
def _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-
62100
def _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

Comments
 (0)