def test_dictionary_with_str(): orig = [{'foo': 'bar', 'fiz': ['b', 'u', 'z']}] norm = normalize_parameters(orig) expected = ({'foo': 'bar', 'fiz': ('b', 'u', 'z')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected orig = [{'foo': 'False', 'fiz': ['True', 'False', 'True']}] norm = normalize_parameters(orig) expected = ({'foo': 'False', 'fiz': ('True', 'False', 'True')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected orig = [{'foo': '1.2', 'fiz': ['2.3', '3.4', '4.5']}] norm = normalize_parameters(orig) expected = ({'foo': '1.2', 'fiz': ('2.3', '3.4', '4.5')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected orig = [{'foo': '1', 'fiz': ['2', '3', '4']}] norm = normalize_parameters(orig) expected = ({'foo': '1', 'fiz': ('2', '3', '4')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected orig = [{'strs': ['True', '2.0', '3']}] norm = normalize_parameters(orig) expected = ({'strs': ('True', '2.0', '3')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected
def test_include_launch_description_launch_file_location(): """Test the ability of the IncludeLaunchDescription class to set the launch file location.""" ld = LaunchDescription() action = IncludeLaunchDescription(LaunchDescriptionSource(ld, '<script>')) assert 'IncludeLaunchDescription' in action.describe() assert isinstance(action.describe_sub_entities(), list) assert isinstance(action.describe_conditional_sub_entities(), list) lc1 = LaunchContext() # Result should only contain the launch description as there are no launch arguments. assert action.visit(lc1) == [ld] assert lc1.locals.current_launch_file_directory == '<script>' assert action.get_asyncio_future() is None this_file = os.path.abspath(__file__) ld2 = LaunchDescription() action2 = IncludeLaunchDescription(LaunchDescriptionSource(ld2, this_file)) assert 'IncludeLaunchDescription' in action2.describe() assert isinstance(action2.describe_sub_entities(), list) assert isinstance(action2.describe_conditional_sub_entities(), list) lc2 = LaunchContext() # Result should only contain the launch description as there are no launch arguments. assert action2.visit(lc2) == [ld2] assert lc2.locals.current_launch_file_directory == os.path.dirname( this_file) assert action2.get_asyncio_future() is None
def test_dictionary_with_dissimilar_array(): orig = [{'foo': 1, 'fiz': [True, 2.0, 3]}] norm = normalize_parameters(orig) expected = ({'foo': 1, 'fiz': ('True', '2.0', '3')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected orig = [{'foo': 1, 'fiz': [True, 1, TextSubstitution(text='foo')]}] norm = normalize_parameters(orig) expected = ({'foo': 1, 'fiz': ('True', '1', 'foo')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected orig = [{'foo': 1, 'fiz': [TextSubstitution(text='foo'), True, 1]}] norm = normalize_parameters(orig) expected = ({'foo': 1, 'fiz': ('foo', 'True', '1')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected orig = [{'foo': 1, 'fiz': [True, 1, [TextSubstitution(text='foo')]]}] norm = normalize_parameters(orig) expected = ({'foo': 1, 'fiz': ('True', '1', 'foo')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected orig = [{'foo': 1, 'fiz': [[TextSubstitution(text='foo')], True, 1]}] norm = normalize_parameters(orig) expected = ({'foo': 1, 'fiz': ('foo', 'True', '1')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected
def test_launch_context_get_noninteractive(): """Test the getting of noninteractive flag in the LaunchContext class.""" lc = LaunchContext(noninteractive=True) assert lc.noninteractive lc = LaunchContext() assert not lc.noninteractive
def execute(self, context: launch.LaunchContext) -> Optional[List[Action]]: """ Execute the action. Delegated to :meth:`launch.actions.ExecuteProcess.execute`. """ self._perform_substitutions( context) # ensure self.node_name is expanded if '<node_name_unspecified>' in self.node_name: raise RuntimeError( 'node_name unexpectedly incomplete for lifecycle node') # Create a subscription to monitor the state changes of the subprocess. self.__rclpy_subscription = context.locals.launch_ros_node.create_subscription( lifecycle_msgs.msg.TransitionEvent, '{}/transition_event'.format(self.node_name), functools.partial(self._on_transition_event, context), 10) # Create a service client to change state on demand. self.__rclpy_change_state_client = context.locals.launch_ros_node.create_client( lifecycle_msgs.srv.ChangeState, '{}/change_state'.format(self.node_name)) # Register an event handler to change states on a ChangeState lifecycle event. context.register_event_handler( launch.EventHandler( matcher=lambda event: isinstance(event, ChangeState), entities=[ launch.actions.OpaqueFunction( function=self._on_change_state_event) ], )) # Delegate execution to Node and ExecuteProcess. return super().execute(context)
def test_launch_context_get_argv(): """Test the getting of argv in the LaunchContext class.""" argv = ['a', 'b'] lc = LaunchContext(argv=argv) assert lc.argv == argv lc = LaunchContext() assert lc.argv == []
def test_launch_context_perform_substitution(): """Test performing substitutions with LaunchContext class.""" lc = LaunchContext() from launch.substitutions import TextSubstitution sub = TextSubstitution(text='foo') assert lc.perform_substitution(sub) == 'foo'
def test_this_launch_file_path_methods(): """Test the methods of the ThisLaunchFileDir class.""" tlfp = ThisLaunchFileDir() lc = LaunchContext() with pytest.raises(SubstitutionFailure): tlfp.perform(lc) lc.extend_locals({'current_launch_file_directory': 'foo'}) assert tlfp.perform(lc) == 'foo'
def _on_change_state_event(self, context: launch.LaunchContext) -> None: typed_event = cast(ChangeState, context.locals.event) if not typed_event.lifecycle_node_matcher(self): return None request = lifecycle_msgs.srv.ChangeState.Request() request.transition.id = typed_event.transition_id context.add_completion_future( context.asyncio_loop.run_in_executor(None, self._call_change_state, request, context))
def test_declare_launch_argument_execute(): """Test the execute (or visit) of the DeclareLaunchArgument class.""" action1 = DeclareLaunchArgument('name') lc1 = LaunchContext() with pytest.raises(RuntimeError) as excinfo: action1.visit(lc1) assert 'Required launch argument' in str(excinfo.value) lc1.launch_configurations['name'] = 'value' assert action1.visit(lc1) is None action2 = DeclareLaunchArgument('name', default_value='value') lc2 = LaunchContext() assert action2.visit(lc2) is None assert lc1.launch_configurations['name'] == 'value' action3 = DeclareLaunchArgument('name', default_value='var1', choices=['var1', 'var2']) lc3 = LaunchContext() assert action3.visit(lc3) is None lc3.launch_configurations['name'] = 'invalid_value' with pytest.raises(RuntimeError) as excinfo: action3.visit(lc3) assert 'Valid options are: [var1, var2]' in str(excinfo.value) lc3.launch_configurations['name'] = 'var1' assert action3.visit(lc3) is None lc3.launch_configurations['name'] = 'var2' assert action3.visit(lc3) is None
def test_dictionary_with_substitution(): orig = [{TextSubstitution(text='bar'): TextSubstitution(text='baz')}] norm = normalize_parameters(orig) expected = ({'bar': 'baz'}, ) assert evaluate_parameters(LaunchContext(), norm) == expected # substitutions get yamlfied orig = [{TextSubstitution(text='false'): TextSubstitution(text='off')}] norm = normalize_parameters(orig) expected = ({'false': False}, ) assert evaluate_parameters(LaunchContext(), norm) == expected
def _on_change_state_event(self, context: launch.LaunchContext) -> None: print('System action "' + self.get_name() + '" caught state change request') typed_event = cast(ChangeState, context.locals.event) if not typed_event.system_part_matcher(self): return None request = lifecycle_msgs.srv.ChangeState.Request() request.transition.id = typed_event.transition_id print(' -> attempting state change of ' + self.get_name() + ' to ' + str(typed_event.transition_id)) context.add_completion_future( context.asyncio_loop.run_in_executor(None, self._call_change_state, request, context))
def _on_change_mode_event(self, context: launch.LaunchContext) -> None: print('System action "' + self.get_name() + '" caught mode change request') typed_event = cast(ChangeMode, context.locals.event) if not typed_event.system_part_matcher(self): return None request = system_modes_msgs.srv.ChangeMode.Request() request.mode_name = typed_event.mode_name print(' -> attempting mode change of ' + self.get_name() + ' to ' + typed_event.mode_name) context.add_completion_future( context.asyncio_loop.run_in_executor(None, self._call_change_mode, request, context))
def test_execute_process_prefix_filter_no_match(): lc = LaunchContext() lc._set_asyncio_loop(asyncio.get_event_loop()) SetLaunchConfiguration('launch-prefix', 'time').visit(lc) assert len(lc.launch_configurations) == 1 SetLaunchConfiguration('launch-prefix-filter', 'no-match').visit(lc) assert len(lc.launch_configurations) == 2 test_process = ExecuteProcess( cmd=[sys.executable, '-c', "print('action')"], output='screen') test_process.execute(lc) assert 'time' not in test_process.process_details['cmd']
def test_event_handler_handle_once(): """Test the option for handling events once for the EventHandler class.""" class MockEvent: ... mock_event = MockEvent() # Test handling multiple events with handle_once=False (default) eh_multiple = EventHandler(matcher=lambda event: True, handle_once=False) context_multiple = LaunchContext() context_multiple.register_event_handler(eh_multiple) eh_multiple.handle(mock_event, context_multiple) assert context_multiple.locals.event == mock_event # Attempt to handle a second event eh_multiple.handle(mock_event, context_multiple) # Test handling multiple events with handle_once=True eh_once = EventHandler(matcher=lambda event: True, handle_once=True) context_once = LaunchContext() context_once.register_event_handler(eh_once) eh_once.handle(mock_event, context_once) assert context_once.locals.event == mock_event # Attempt to handle a second event, this time expect ValueError because it is unregistered with pytest.raises(ValueError): eh_once.handle(mock_event, context_once)
def test_dictionary_with_substitution_list_value(): orig = [{ 'foo': [TextSubstitution(text='fiz'), TextSubstitution(text='buz')] }] norm = normalize_parameters(orig) expected = ({'foo': 'fizbuz'}, ) assert evaluate_parameters(LaunchContext(), norm) == expected orig = [{ 'foo': [[TextSubstitution(text='fiz')], [TextSubstitution(text='buz')]] }] norm = normalize_parameters(orig) expected = ({'foo': ('fiz', 'buz')}, ) assert evaluate_parameters(LaunchContext(), norm) == expected
def test_declare_launch_argument_execute(): """Test the execute (or visit) of the DeclareLaunchArgument class.""" action1 = DeclareLaunchArgument('name') lc1 = LaunchContext() with pytest.raises(RuntimeError) as excinfo: action1.visit(lc1) assert 'Required launch argument' in str(excinfo.value) lc1.launch_configurations['name'] = 'value' assert action1.visit(lc1) is None action2 = DeclareLaunchArgument('name', default_value='value') lc2 = LaunchContext() assert action2.visit(lc2) is None assert lc1.launch_configurations['name'] == 'value'
def test_mixed_path_dicts(): orig = ['/foo/bar', {'fiz': {'buz': 3}}, pathlib.Path('/tmp/baz')] norm = normalize_parameters(orig) expected = (pathlib.Path('/foo/bar'), { 'fiz.buz': 3 }, pathlib.Path('/tmp/baz')) assert evaluate_parameters(LaunchContext(), norm) == expected
def _function(self, context: launch.LaunchContext): try: rclpy.init(args=context.argv) except RuntimeError as exc: if 'rcl_init called while already initialized' in str(exc): pass raise self.__launch_ros_node = rclpy.create_node('launch_ros') context.extend_globals({ 'ros_startup_action': self, 'launch_ros_node': self.__launch_ros_node }) context.register_event_handler( launch.event_handlers.OnShutdown(on_shutdown=self._shutdown, )) self.__rclpy_spin_thread = threading.Thread(target=self._run) self.__rclpy_spin_thread.start()
def test_dictionary_with_int_and_float(): orig = [{'foo': 1, 'fiz': [2, 3.1, 4]}] norm = normalize_parameters(orig) expected = ({'foo': 1, 'fiz': (2.0, 3.1, 4.0)}, ) evaluated = evaluate_parameters(LaunchContext(), norm) assert evaluated == expected # pytest doesn't check int vs float type assert tuple(map(type, evaluated[0]['fiz'])) == (float, float, float) orig = [{'foo': 1, 'fiz': [2.0, 3, 4]}] norm = normalize_parameters(orig) expected = ({'foo': 1, 'fiz': (2.0, 3.0, 4.0)}, ) evaluated = evaluate_parameters(LaunchContext(), norm) assert evaluated == expected # pytest doesn't check int vs float type assert tuple(map(type, evaluated[0]['fiz'])) == (float, float, float)
def test_include_python(): """Test including Python, with and without explicit PythonLaunchDescriptionSource.""" this_dir = os.path.dirname(os.path.abspath(__file__)) simple_launch_file_path = os.path.join(this_dir, '..', 'launch_description_source', 'simple.launch.py') # Explicitly construct with PythonLaunchDescriptionSource plds = PythonLaunchDescriptionSource(simple_launch_file_path) action0 = IncludeLaunchDescription(plds) # Construct action with path instead of PythonLaunchDescriptionSource object action1 = IncludeLaunchDescription(simple_launch_file_path) # The two actions should be equivalent for action in [action0, action1]: assert 'IncludeLaunchDescription' in action.describe() assert isinstance(action.describe_sub_entities(), list) assert isinstance(action.describe_conditional_sub_entities(), list) # Result should only contain a single launch description as there are no launch arguments. assert len(action.visit(LaunchContext())) == 1 assert action.get_asyncio_future() is None assert len(action.launch_arguments) == 0 assert action.launch_description_source.location == simple_launch_file_path
def execute(self, context: LaunchContext) -> Optional[List[Action]]: """ Execute the ROS 2 sandbox inside Docker. This will start the Docker container and run each ROS 2 node from inside that container. There is no additional work required, so this function always returns None. """ context.register_event_handler( OnShutdown(on_shutdown=self.__on_shutdown)) self._completed_future = create_future(context.asyncio_loop) self._started_task = context.asyncio_loop.create_task( self._start_docker_nodes(context)) return None
def get_ros_adapter(context: launch.LaunchContext): """ Get the ROS adapter managed by the given launch context. If no adapter is found, one will be created. This function is reentrant but concurrent calls on the same `context` are not safe. """ if not hasattr(context.locals, 'ros_adapter'): ros_adapter = ROSAdapter() context.extend_globals({'ros_adapter': ros_adapter}) context.register_event_handler( launch.event_handlers.OnShutdown( on_shutdown=lambda *args, **kwargs: ros_adapter.shutdown())) return context.locals.ros_adapter
def test_set_and_unset_launch_configuration_execute(): """Test the execute() of the SetLaunchConfiguration and UnsetLaunchConfiguration classes.""" lc1 = LaunchContext() assert len(lc1.launch_configurations) == 0 SetLaunchConfiguration('name', 'value').visit(lc1) assert len(lc1.launch_configurations) == 1 assert 'name' in lc1.launch_configurations UnsetLaunchConfiguration('name').visit(lc1) assert len(lc1.launch_configurations) == 0 assert 'name' not in lc1.launch_configurations # use substitutions assert len(lc1.launch_configurations) == 0 SetLaunchConfiguration('foo', 'FOO').visit(lc1) SetLaunchConfiguration(['name-', LaunchConfiguration('foo')], 'value').visit(lc1) assert len(lc1.launch_configurations) == 2 assert any(c.startswith('name-') for c in lc1.launch_configurations) UnsetLaunchConfiguration(['name-', LaunchConfiguration('foo')]).visit(lc1) assert len(lc1.launch_configurations) == 1 assert not any(c.startswith('name-') for c in lc1.launch_configurations) UnsetLaunchConfiguration('foo').visit(lc1) assert len(lc1.launch_configurations) == 0 # unset a non-existent config does not fail assert len(lc1.launch_configurations) == 0 UnsetLaunchConfiguration('does_not_exist').visit(lc1)
def test_set_and_unset_environment_variable_execute(): """Test the execute() of the SetEnvironmentVariable and UnsetEnvironmentVariable classes.""" lc1 = LaunchContext() # can set and overwrite environment variables if 'NONEXISTENT_KEY' in os.environ: del os.environ['NONEXISTENT_KEY'] assert os.environ.get('NONEXISTENT_KEY') is None SetEnvironmentVariable('NONEXISTENT_KEY', 'value').visit(lc1) assert os.environ.get('NONEXISTENT_KEY') == 'value' SetEnvironmentVariable('NONEXISTENT_KEY', 'ANOTHER_NONEXISTENT_KEY').visit(lc1) assert os.environ.get('NONEXISTENT_KEY') == 'ANOTHER_NONEXISTENT_KEY' # can unset environment variables if 'ANOTHER_NONEXISTENT_KEY' in os.environ: del os.environ['ANOTHER_NONEXISTENT_KEY'] assert os.environ.get('ANOTHER_NONEXISTENT_KEY') is None SetEnvironmentVariable('ANOTHER_NONEXISTENT_KEY', 'some value').visit(lc1) assert os.environ.get('ANOTHER_NONEXISTENT_KEY') == 'some value' UnsetEnvironmentVariable('ANOTHER_NONEXISTENT_KEY').visit(lc1) assert os.environ.get('ANOTHER_NONEXISTENT_KEY') is None # set and unset with substitutions assert os.environ.get('ANOTHER_NONEXISTENT_KEY') is None SetEnvironmentVariable( 'ANOTHER_NONEXISTENT_KEY', EnvironmentVariable('NONEXISTENT_KEY')).visit(lc1) assert os.environ.get('ANOTHER_NONEXISTENT_KEY') == 'ANOTHER_NONEXISTENT_KEY' UnsetEnvironmentVariable(EnvironmentVariable('NONEXISTENT_KEY')).visit(lc1) assert os.environ.get('ANOTHER_NONEXISTENT_KEY') is None # cleanup environment variables if 'NONEXISTENT_KEY' in os.environ: del os.environ['NONEXISTENT_KEY']
def test_unset_nonexistent_key(): """Test that the UnsetEnvironmentVariable class doesn't raise an exception.""" lc1 = LaunchContext() assert os.environ.get('ANOTHER_NONEXISTENT_KEY') is None UnsetEnvironmentVariable('ANOTHER_NONEXISTENT_KEY').visit(lc1) assert os.environ.get('ANOTHER_NONEXISTENT_KEY') is None
def test_plain_text(): lc = LaunchContext() rule = ('foo', 'bar') output_rule = normalize_remap_rule(rule) assert isinstance(output_rule, tuple) assert len(output_rule) == 2 assert rule[0] == perform_substitutions(lc, output_rule[0]) assert rule[1] == perform_substitutions(lc, output_rule[1])
def test_launch_context_emit_events(): """Test emitting events in LaunchContext class.""" lc = LaunchContext() class MockEvent: name = 'MockEvent' assert lc._event_queue.qsize() == 0 mock_event = MockEvent() lc.emit_event_sync(mock_event) assert lc._event_queue.qsize() == 1 mock_event2 = MockEvent() loop = asyncio.get_event_loop() task = loop.create_task(lc.emit_event(mock_event2)) loop.run_until_complete(task) assert lc._event_queue.qsize() == 2
def test_mixed_substitutions(): lc = LaunchContext() rule = (('foo', 'bar'), ['bar', TextSubstitution(text='baz')]) output_rule = normalize_remap_rule(rule) assert isinstance(output_rule, tuple) assert len(output_rule) == 2 assert 'foobar' == perform_substitutions(lc, output_rule[0]) assert 'barbaz' == perform_substitutions(lc, output_rule[1])
def test_node_name_count(): lc = LaunchContext() add_node_name(lc, 'node_name_1') add_node_name(lc, 'node_name_1') add_node_name(lc, 'node_name_2') assert get_node_name_count(lc, 'node_name_1') == 2 assert get_node_name_count(lc, 'node_name_2') == 1 assert get_node_name_count(lc, 'node_name_3') == 0