def launch(shutdown_pipe, package_name, launch_file_name, launch_arguments, debug): """Launch a ROS system.""" event_loop = asyncio.new_event_loop() asyncio.set_event_loop(event_loop) # based on ros2launch/command/launch.py:main if package_name is None: single_file = True else: single_file = False get_package_prefix(package_name) path = None launch_arguments = [] if single_file: if os.path.exists(package_name): path = package_name else: raise ValueError(package_name) if launch_file_name is not None: launch_arguments.append(launch_file_name) else: try: path = get_share_file_path_from_package(package_name=package_name, file_name=launch_file_name) except PackageNotFoundError as exc: raise RuntimeError("Package '{}' not found: {}".format( package_name, exc)) except (FileNotFoundError, MultipleLaunchFilesError) as exc: raise RuntimeError(str(exc)) launch_arguments.extend(launch_arguments) launch_service = LaunchService(argv=launch_arguments, debug=debug) launch_description = LaunchDescription([ IncludeLaunchDescription( AnyLaunchDescriptionSource(path), launch_arguments={}, ), ]) launch_service.include_launch_description(launch_description) finished = False def shutdown(): while not finished and not shutdown_pipe.poll(1): pass launch_service.shutdown() t = threading.Thread(target=shutdown) t.start() launch_service.run(shutdown_when_idle=True) finished = True t.join() event_loop.close()
def main(argv=sys.argv[1:]): """Main.""" ld = LaunchDescription([ launch_ros.actions.Node(package='demo_nodes_cpp', node_executable='talker', output='screen', remappings=[('chatter', 'my_chatter')]), launch_ros.actions.Node(package='demo_nodes_cpp', node_executable='listener', output='screen', remappings=[('chatter', 'my_chatter')]), ]) print('Starting introspection of launch description...') print('') print(LaunchIntrospector().format_launch_description(ld)) print('') print('Starting launch of launch description...') print('') # ls = LaunchService(debug=True) ls = LaunchService() ls.include_launch_description( get_default_launch_description(prefix_output_with_name=False)) ls.include_launch_description(ld) return ls.run()
def test_executable(): """Parse executable yaml example.""" yaml_file = \ """\ launch: - executable: cmd: ls -l -a -s cwd: '/' name: my_ls shell: true output: log launch_prefix: $(env LAUNCH_PREFIX) env: - name: var value: '1' """ yaml_file = textwrap.dedent(yaml_file) root_entity, parser = Parser.load(io.StringIO(yaml_file)) ld = parser.parse_description(root_entity) executable = ld.entities[0] cmd = [i[0].perform(None) for i in executable.cmd] assert( cmd == ['ls', '-l', '-a', '-s']) assert(executable.cwd[0].perform(None) == '/') assert(executable.name[0].perform(None) == 'my_ls') assert(executable.shell is True) assert(executable.output == 'log') key, value = executable.additional_env[0] key = key[0].perform(None) value = value[0].perform(None) assert(key == 'var') assert(value == '1') ls = LaunchService() ls.include_launch_description(ld) assert(0 == ls.run())
def test_execute_process_with_respawn(): """Test launching a process with a respawn and respawn_delay attribute.""" def on_exit_callback(event, context): on_exit_callback.called_count = on_exit_callback.called_count + 1 on_exit_callback.called_count = 0 respawn_delay = 2.0 shutdown_time = 3.0 # to shutdown the launch service, so that the process only respawn once expected_called_count = 2 # normal exit and respawn exit def generate_launch_description(): return LaunchDescription([ ExecuteProcess(cmd=[sys.executable, '-c', "print('action')"], respawn=True, respawn_delay=respawn_delay, on_exit=on_exit_callback), TimerAction(period=shutdown_time, actions=[Shutdown(reason='Timer expired')]) ]) ls = LaunchService() ls.include_launch_description(generate_launch_description()) assert 0 == ls.run() assert expected_called_count == on_exit_callback.called_count
def test_execute_process_with_on_exit_behavior(): """Test a process' on_exit callback and actions are processed.""" def on_exit_callback(event, context): on_exit_callback.called = True on_exit_callback.called = False executable_with_on_exit_callback = ExecuteProcess( cmd=[sys.executable, '-c', "print('callback')"], output='screen', on_exit=on_exit_callback) assert len(executable_with_on_exit_callback.get_sub_entities()) == 0 def on_exit_function(context): on_exit_function.called = True on_exit_function.called = False on_exit_action = OpaqueFunction(function=on_exit_function) executable_with_on_exit_action = ExecuteProcess( cmd=[sys.executable, '-c', "print('action')"], output='screen', on_exit=[on_exit_action]) assert executable_with_on_exit_action.get_sub_entities() == [ on_exit_action ] ld = LaunchDescription( [executable_with_on_exit_callback, executable_with_on_exit_action]) ls = LaunchService() ls.include_launch_description(ld) assert 0 == ls.run() assert on_exit_callback.called assert on_exit_function.called
def _assert_launch_frontend_no_errors(self, file) -> Trace: root_entity, parser = Parser.load(file) ld = parser.parse_description(root_entity) ls = LaunchService() ls.include_launch_description(ld) assert 0 == ls.run() trace_action = ld.describe_sub_entities()[0] return trace_action
def check_launch_namespace(file): root_entity, parser = Parser.load(file) ld = parser.parse_description(root_entity) ls = LaunchService() ls.include_launch_description(ld) assert 0 == ls.run() assert 'ros_namespace' in ls.context.launch_configurations assert '/asd' == ls.context.launch_configurations['ros_namespace']
def check_launch_node(file): root_entity, parser = Parser.load(file) ld = parser.parse_description(root_entity) ls = LaunchService() ls.include_launch_description(ld) assert(0 == ls.run()) evaluated_parameters = evaluate_parameters( ls.context, ld.describe_sub_entities()[3]._Node__parameters ) assert len(evaluated_parameters) == 3 assert isinstance(evaluated_parameters[0], dict) assert isinstance(evaluated_parameters[1], dict) assert isinstance(evaluated_parameters[2], pathlib.Path) assert 'param1' in evaluated_parameters[0] assert evaluated_parameters[0]['param1'] == 'ads' param_dict = evaluated_parameters[1] assert 'param_group1.param_group2.param2' in param_dict assert 'param_group1.param3' in param_dict assert 'param_group1.param4' in param_dict assert 'param_group1.param5' in param_dict assert 'param_group1.param6' in param_dict assert 'param_group1.param7' in param_dict assert 'param_group1.param8' in param_dict assert 'param_group1.param9' in param_dict assert 'param_group1.param10' in param_dict assert 'param_group1.param11' in param_dict assert 'param_group1.param12' in param_dict assert 'param_group1.param13' in param_dict assert 'param_group1.param14' in param_dict assert 'param_group1.param15' in param_dict assert param_dict['param_group1.param_group2.param2'] == 2 assert param_dict['param_group1.param3'] == [2, 5, 8] assert param_dict['param_group1.param4'] == [2, 5, 8] assert param_dict['param_group1.param5'] == '[2, 5, 8]' assert param_dict['param_group1.param6'] == [2., 5., 8.] assert param_dict['param_group1.param7'] == ['2', '5', '8'] assert param_dict['param_group1.param8'] == ["'2'", "'5'", "'8'"] assert param_dict['param_group1.param9'] == ["'2'", "'5'", "'8'"] assert param_dict['param_group1.param10'] == ["'asd'", "'bsd'", "'csd'"] assert param_dict['param_group1.param11'] == ['asd', 'bsd', 'csd'] assert param_dict['param_group1.param12'] == '' assert param_dict['param_group1.param13'] == '100' assert param_dict['param_group1.param14'] == ["'2'", "'5'", "'8'"] assert param_dict['param_group1.param15'] == ['2', '5', '8'] # Check remappings exist remappings = ld.describe_sub_entities()[3]._Node__remappings assert remappings is not None assert len(remappings) == 2 listener_node_action = ld.describe_sub_entities()[4] listener_node_cmd = listener_node_action.process_details['cmd'] assert [ sys.executable, '-c', 'import sys; print(sys.argv[1:])' ] == listener_node_cmd[:3]
def test_node_frontend(file): """Parse node xml example.""" root_entity, parser = Parser.load(io.StringIO(file)) ld = parser.parse_description(root_entity) ls = LaunchService() ls.include_launch_description(ld) assert 0 == ls.run() assert 'ros_namespace' in ls.context.launch_configurations assert '/asd' == ls.context.launch_configurations['ros_namespace']
def test_launch_service_emit_event(): """ Test the emitting of events in the LaunchService class. Also covers basic tests for include_launch_description(), run(), and shutdown(). """ ls = LaunchService(debug=True) assert ls._LaunchService__context._event_queue.qsize() == 0 from launch.actions import OpaqueFunction from launch.actions import RegisterEventHandler from launch.event_handler import EventHandler handled_events = queue.Queue() ld = LaunchDescription([ RegisterEventHandler(EventHandler( matcher=lambda event: True, entities=OpaqueFunction( function=lambda context: handled_events.put(context.locals.event), ), )) ]) ls.include_launch_description(ld) assert ls._LaunchService__context._event_queue.qsize() == 1 class MockEvent: name = 'Event' ls.emit_event(MockEvent()) assert ls._LaunchService__context._event_queue.qsize() == 2 assert handled_events.qsize() == 0 t = threading.Thread(target=ls.run, kwargs={'shutdown_when_idle': False}) t.start() # First event (after including description of event handler). handled_events.get(block=True, timeout=5.0) # Emit and then check for a second event. ls.emit_event(MockEvent()) handled_events.get(block=True, timeout=5.0) # Shutdown (generates a third event) and join the thread. ls.shutdown() t.join() # Check that the shutdown event was handled. handled_events.get(block=False) assert handled_events.qsize() == 0 ls.emit_event(MockEvent()) assert handled_events.qsize() == 0 assert ls.run(shutdown_when_idle=True) == 0 handled_events.get(block=False)
def test_wait_for_ready(self): data = [] self.launch_description.add_entity( RegisterEventHandler( StdoutReadyListener( node_name="terminating_node", ready_txt="Ready", actions=[ OpaqueFunction( function=lambda context: data.append('ok')) ]))) launch_service = LaunchService() launch_service.include_launch_description(self.launch_description) launch_service.run() # If the StdoutReadyListener worked, we should see 'ok' in the data self.assertIn('ok', data)
def test_wait_for_wrong_message(self): data = [] self.launch_description.add_entity( RegisterEventHandler( StdoutReadyListener( node_name="different_node", ready_txt="not_ready", actions=[ OpaqueFunction( function=lambda context: data.append('ok')) ]))) launch_service = LaunchService() launch_service.include_launch_description(self.launch_description) launch_service.run() # We should not get confused by output that doesn't match the ready_txt self.assertNotIn('ok', data)
def launch_gtest(test_path): """Launch a gtest.""" ld = LaunchDescription([ GTest(path=str(test_path), timeout=5.0, on_exit=[EmitEvent(event=Shutdown())]) ]) ls = LaunchService() ls.include_launch_description(ld) assert 0 == ls.run()
def setUpClass(cls): # It's easier to actually capture some IO from the launch system than it is to fake it # but it takes a few seconds. We'll do it once and run tests on the same captured # IO node_env = os.environ.copy() node_env["PYTHONUNBUFFERED"] = "1" cls.proc_output = ActiveIoHandler() cls.node_1 = Node(package='apex_rostest', node_executable='terminating_node', env=node_env) # This node should be distinguishable by its cmd line args cls.node_2 = Node(package='apex_rostest', node_executable='terminating_node', arguments=['--extra'], env=node_env) # This node should be distinguishable by its diffetent node name cls.node_3 = Node(package='apex_rostest', node_executable='terminating_node', node_name='different_name', env=node_env) launch_description = LaunchDescription([ cls.node_1, cls.node_2, cls.node_3, # This plumbs all the output to our IoHandler just like the ApexRunner does RegisterEventHandler( OnProcessIO( on_stdout=cls.proc_output.append, on_stderr=cls.proc_output.append, )) ]) launch_service = LaunchService() launch_service.include_launch_description(launch_description) launch_service.run()
def test_execute_process_with_env(): """Test launching a process with an environment variable.""" ld = LaunchDescription([ ExecuteProcess( cmd=[sys.executable, 'TEST_PROCESS_WITH_ENV'], output='screen', env={'TEST_PROCESS_WITH_ENV': 'Hello World'}, ) ]) ls = LaunchService() ls.include_launch_description(ld) assert 0 == ls.run()
def run_and_trace( base_path: str, session_name_prefix: str, ros_events: List[str], kernel_events: List[str], package_name: str, node_names: List[str], additional_actions: Union[List[Action], Action] = [], ) -> Tuple[int, str]: """ Run a node while tracing. :param base_path: the base path where to put the trace directory :param session_name_prefix: the session name prefix for the trace directory :param ros_events: the list of ROS UST events to enable :param kernel_events: the list of kernel events to enable :param package_name: the name of the package to use :param node_names: the names of the nodes to execute :param additional_actions: the list of additional actions to prepend :return: exit code, full generated path """ session_name = append_timestamp(session_name_prefix) full_path = os.path.join(base_path, session_name) if not isinstance(additional_actions, list): additional_actions = [additional_actions] launch_actions = additional_actions # Add trace action launch_actions.append( Trace( session_name=session_name, append_timestamp=False, base_path=base_path, events_ust=ros_events, events_kernel=kernel_events, )) # Add nodes for node_name in node_names: n = Node( package=package_name, executable=node_name, output='screen', ) launch_actions.append(n) ld = LaunchDescription(launch_actions) ls = LaunchService() ls.include_launch_description(ld) exit_code = ls.run() return exit_code, full_path
def test_execute_process_shutdown(): """Test shutting down a process in (non)interactive settings.""" def on_exit(event, ctx): on_exit.returncode = event.returncode def generate_launch_description(): process_action = ExecuteProcess( cmd=[sys.executable, '-c', 'import signal; signal.pause()'], sigterm_timeout='1', # shorten timeouts on_exit=on_exit) # Launch process and emit shutdown event as if # launch had received a SIGINT return LaunchDescription([ process_action, RegisterEventHandler(event_handler=OnProcessStart( target_action=process_action, on_start=[ EmitEvent( event=ShutdownEvent(reason='none', due_to_sigint=True)) ])) ]) ls = LaunchService(noninteractive=True) ls.include_launch_description(generate_launch_description()) assert 0 == ls.run() if platform.system() != 'Windows': assert on_exit.returncode == -signal.SIGINT # Got SIGINT else: assert on_exit.returncode != 0 # Process terminated ls = LaunchService() # interactive ls.include_launch_description(generate_launch_description()) assert 0 == ls.run() if platform.system() != 'Windows': # Assume interactive Ctrl+C (i.e. SIGINT to process group) assert on_exit.returncode == -signal.SIGTERM # Got SIGTERM else: assert on_exit.returncode != 0 # Process terminated
def main(argv): ld = generate_launch_description() print('Starting introspection of launch description...') print('') print(LaunchIntrospector().format_launch_description(ld)) print('') print('Starting launch of launch description...') print('') ls = LaunchService() ls.include_launch_description(ld) return ls.run()
def main(argv=sys.argv[1:]): """Main.""" ld = generate_launch_description() print('Starting introspection of launch description...') print('') print(LaunchIntrospector().format_launch_description(ld)) print('') print('Starting launch of launch description...') print('') ls = LaunchService() ls.include_launch_description(get_default_launch_description()) ls.include_launch_description(ld) return ls.run()
def test_node_frontend(file): """Parse node xml example.""" root_entity, parser = Parser.load(io.StringIO(file)) ld = parser.parse_description(root_entity) ls = LaunchService() ls.include_launch_description(ld) assert (0 == ls.run()) evaluated_parameters = evaluate_parameters( ls.context, ld.describe_sub_entities()[2]._Node__parameters) assert isinstance(evaluated_parameters[0], dict) assert isinstance(evaluated_parameters[1], dict) assert isinstance(evaluated_parameters[2], pathlib.Path) assert 'param1' in evaluated_parameters[0] assert evaluated_parameters[0]['param1'] == 'ads' param_dict = evaluated_parameters[1] assert 'param_group1.param_group2.param2' in param_dict assert 'param_group1.param3' in param_dict assert 'param_group1.param4' in param_dict assert 'param_group1.param5' in param_dict assert 'param_group1.param6' in param_dict assert 'param_group1.param7' in param_dict assert 'param_group1.param8' in param_dict assert 'param_group1.param9' in param_dict assert 'param_group1.param10' in param_dict assert 'param_group1.param11' in param_dict assert param_dict['param_group1.param_group2.param2'] == 2 assert param_dict['param_group1.param3'] == (2, 5, 8) assert param_dict['param_group1.param4'] == (2, 5, 8) assert param_dict['param_group1.param5'] == '[2, 5, 8]' assert param_dict['param_group1.param6'] == (2., 5., 8.) assert param_dict['param_group1.param7'] == ('2', '5', '8') assert param_dict['param_group1.param8'] == ("'2'", "'5'", "'8'") assert param_dict['param_group1.param9'] == ("'2'", "'5'", "'8'") assert param_dict['param_group1.param10'] == ("'asd'", "'bsd'", "'csd'") assert param_dict['param_group1.param11'] == ('asd', 'bsd', 'csd') assert param_dict['param_group1.param12'] == '' listener_node_action = ld.describe_sub_entities()[3] listener_node_cmd = listener_node_action.process_details['cmd'] assert [sys.executable, '-c', 'import sys; print(sys.argv[1:])'] == listener_node_cmd[:3]
def test_include(): """Parse node xml example.""" # Always use posix style paths in launch XML files. path = (Path(__file__).parent / 'executable.xml').as_posix() xml_file = \ """\ <launch> <include file="{}"/> </launch> """.format(path) # noqa: E501 xml_file = textwrap.dedent(xml_file) root_entity, parser = Parser.load(io.StringIO(xml_file)) ld = parser.parse_description(root_entity) include = ld.entities[0] assert isinstance(include, IncludeLaunchDescription) assert isinstance(include.launch_description_source, AnyLaunchDescriptionSource) ls = LaunchService(debug=True) ls.include_launch_description(ld) assert 0 == ls.run()
class TestCPUMonitor(unittest.TestCase): @classmethod def test_test_ekf_localization_node_interfaces(self): print("test_ekf_localization_node_interfaces Testcases Execution Start...") os.system("ros2 launch robot_localization test_ekf_localization_node_interfaces.launch.py") @classmethod def test_test_ukf_localization_node_interfaces(self): print("test_ukf_localization_node_interfaces Testcases Execution Start...") os.system("ros2 launch robot_localization test_ukf_localization_node_interfaces.launch.py") @classmethod def test_filter_base(self): print("filter_base Testcases Execution Start...") os.system("ros2 run robot_localization filter_base-test") @classmethod def test_filter_base_diagnostics_timestamps(self): print("test_filter_base_diagnostics_timestamps Testcases Execution Start...") os.system("ros2 launch robot_localization test_filter_base_diagnostics_timestamps.launch.py") @classmethod def setUpClass(cls): rclpy.init() cls.node = rclpy.create_node(TEST_NODE, namespace=TEST_NAMESPACE) @classmethod def tearDownClass(cls): cls.node.destroy_node() rclpy.shutdown() def _assert_launch_errors(self, actions): ld = LaunchDescription(actions) self.ls = LaunchService() self.ls.include_launch_description(ld) assert 0 != self.ls.run() def _assert_launch_no_errors(self, actions): ld = LaunchDescription(actions) self.ls = LaunchService() self.ls.include_launch_description(ld) t = threading.Thread(target=self.ls.run, kwargs={'shutdown_when_idle': False}) t.start()
def test_execute_process_with_env(test_input, expected): """Test launching a process with an environment variable.""" os.environ['TEST_CHANGE_CURRENT_ENV'] = '1' additional_env = {'TEST_PROCESS_WITH_ENV': 'Hello World'} executable = ExecuteProcess(cmd=[sys.executable, 'TEST_PROCESS_WITH_ENV'], output='screen', env=test_input, additional_env=additional_env) ld = LaunchDescription([executable]) ls = LaunchService() ls.include_launch_description(ld) assert 0 == ls.run() env = executable.process_details['env'] assert env['TEST_PROCESS_WITH_ENV'] == 'Hello World' assert ('TEST_CHANGE_CURRENT_ENV' in env) is expected[0] if expected[0]: assert env['TEST_CHANGE_CURRENT_ENV'] == '1' assert ('TEST_NEW_ENV' in env) is expected[1] if expected[1]: assert env['TEST_NEW_ENV'] == '2'
def test_executable(): """Parse node xml example.""" xml_file = str(Path(__file__).parent / 'executable.xml') root_entity, parser = Parser.load(xml_file) ld = parser.parse_description(root_entity) executable = ld.entities[0] cmd = [i[0].perform(None) for i in executable.cmd] assert cmd == ['ls', '-l', '-a', '-s'] assert executable.cwd[0].perform(None) == '/' assert executable.name[0].perform(None) == 'my_ls' assert executable.shell is True assert executable.output == 'log' key, value = executable.additional_env[0] key = key[0].perform(None) value = value[0].perform(None) assert key == 'var' assert value == '1' ls = LaunchService() ls.include_launch_description(ld) assert 0 == ls.run()
def test_node_frontend(file): """Parse node xml example.""" root_entity, parser = Parser.load(io.StringIO(file)) ld = parser.parse_description(root_entity) ls = LaunchService() ls.include_launch_description(ld) assert(0 == ls.run()) evaluated_parameters = evaluate_parameters( ls.context, ld.describe_sub_entities()[2]._Node__parameters ) assert isinstance(evaluated_parameters[0], pathlib.Path) assert isinstance(evaluated_parameters[1], dict) param_dict = evaluated_parameters[1] assert 'param1' in param_dict assert param_dict['param1'] == 'ads' assert 'param_group1.param_group2.param2' in param_dict assert 'param_group1.param3' in param_dict assert 'param_group1.param4' in param_dict assert 'param_group1.param5' in param_dict assert 'param_group1.param6' in param_dict assert 'param_group1.param7' in param_dict assert 'param_group1.param8' in param_dict assert 'param_group1.param9' in param_dict assert 'param_group1.param10' in param_dict assert 'param_group1.param11' in param_dict assert param_dict['param_group1.param_group2.param2'] == 2 assert param_dict['param_group1.param3'] == (2, 5, 8) assert param_dict['param_group1.param4'] == (2, 5, 8) assert param_dict['param_group1.param5'] == '[2, 5, 8]' assert param_dict['param_group1.param6'] == (2., 5., 8.) assert param_dict['param_group1.param7'] == ('2', '5', '8') assert param_dict['param_group1.param8'] == ("'2'", "'5'", "'8'") assert param_dict['param_group1.param9'] == ("'2'", "'5'", "'8'") assert param_dict['param_group1.param10'] == ("'asd'", "'bsd'", "'csd'") assert param_dict['param_group1.param11'] == ('asd', 'bsd', 'csd') assert param_dict['param_group1.param12'] == ''
def _assert_launch_no_errors(self, actions): ld = LaunchDescription(actions) ls = LaunchService(debug=True) ls.include_launch_description(ld) assert 0 == ls.run()
def main(argv=sys.argv[1:]): """Main.""" # Configure rotating logs. launch.logging.launch_config.log_handler_factory = \ lambda path, encoding=None: launch.logging.handlers.RotatingFileHandler( path, maxBytes=1024, backupCount=3, encoding=encoding) # Any number of actions can optionally be given to the constructor of LaunchDescription. # Or actions/entities can be added after creating the LaunchDescription. user_env_var = 'USERNAME' if platform.system() == 'Windows' else 'USER' ld = LaunchDescription([ launch.actions.LogInfo(msg='Hello World!'), launch.actions.LogInfo(msg=( 'Is that you, ', launch.substitutions.EnvironmentVariable(name=user_env_var), '?' )), ]) # Setup a custom event handler for all stdout/stderr from processes. # Later, this will be a configurable, but always present, extension to the LaunchService. def on_output(event: launch.Event) -> None: for line in event.text.decode().splitlines(): print('[{}] {}'.format( cast(launch.events.process.ProcessIO, event).process_name, line)) ld.add_action(launch.actions.RegisterEventHandler(launch.event_handlers.OnProcessIO( # this is the action ^ and this, the event handler ^ on_stdout=on_output, on_stderr=on_output, ))) # Run whoami, and use its output to log the name of the user. # Prefix just the whoami process with `time`. ld.add_action(launch.actions.SetLaunchConfiguration('launch-prefix', 'time')) # Run whoami, but keep handle to action to make a targeted event handler. if platform.system() == 'Windows': whoami_cmd = ['echo', '%USERNAME%'] else: whoami_cmd = [launch.substitutions.FindExecutable(name='whoami')] whoami_action = launch.actions.ExecuteProcess( cmd=whoami_cmd, shell=True ) ld.add_action(whoami_action) # Make event handler that uses the output. ld.add_action(launch.actions.RegisterEventHandler(launch.event_handlers.OnProcessIO( target_action=whoami_action, # The output of `time` will be skipped since `time`'s output always goes to stderr. on_stdout=lambda event: launch.actions.LogInfo( msg="whoami says you are '{}'.".format(event.text.decode().strip()) ), ))) # Unset launch prefix to prevent other process from getting this setting. ld.add_action(launch.actions.SetLaunchConfiguration('launch-prefix', '')) # Run the counting program, with default options. counter_action = launch.actions.ExecuteProcess(cmd=[sys.executable, '-u', './counter.py']) ld.add_action(counter_action) # Setup an event handler for just this process which will exit when `Counter: 4` is seen. def counter_output_handler(event): target_str = 'Counter: 4' if target_str in event.text.decode(): return launch.actions.EmitEvent(event=launch.events.Shutdown( reason="saw '{}' from '{}'".format(target_str, event.process_name) )) ld.add_action(launch.actions.RegisterEventHandler(launch.event_handlers.OnProcessIO( target_action=counter_action, on_stdout=counter_output_handler, on_stderr=counter_output_handler, ))) # Run the counter a few more times, with various options. ld.add_action(launch.actions.ExecuteProcess( cmd=[sys.executable, '-u', './counter.py', '--ignore-sigint'] )) ld.add_action(launch.actions.ExecuteProcess( cmd=[sys.executable, '-u', './counter.py', '--ignore-sigint', '--ignore-sigterm'] )) # Add our own message for when shutdown is requested. ld.add_action(launch.actions.RegisterEventHandler(launch.event_handlers.OnShutdown( on_shutdown=[launch.actions.LogInfo(msg=[ 'Launch was asked to shutdown: ', launch.substitutions.LocalSubstitution('event.reason'), ])], ))) print('Starting introspection of launch description...') print('') print(LaunchIntrospector().format_launch_description(ld)) print('') print('Starting launch of launch description...') print('') # ls = LaunchService(argv=argv, debug=True) # Use this instead to get more debug messages. ls = LaunchService(argv=argv) ls.include_launch_description(ld) return ls.run()
def test_include_launch_description_launch_arguments(): """Test the interactions between declared launch arguments and IncludeLaunchDescription.""" # test that arguments are set when given, even if they are not declared ld1 = LaunchDescription([]) action1 = IncludeLaunchDescription( LaunchDescriptionSource(ld1), launch_arguments={'foo': 'FOO'}.items(), ) assert len(action1.launch_arguments) == 1 lc1 = LaunchContext() result1 = action1.visit(lc1) assert len(result1) == 2 assert isinstance(result1[0], SetLaunchConfiguration) assert perform_substitutions(lc1, result1[0].name) == 'foo' assert perform_substitutions(lc1, result1[0].value) == 'FOO' assert result1[1] == ld1 # test that a declared argument that is not provided raises an error ld2 = LaunchDescription([DeclareLaunchArgument('foo')]) action2 = IncludeLaunchDescription(LaunchDescriptionSource(ld2)) lc2 = LaunchContext() with pytest.raises(RuntimeError) as excinfo2: action2.visit(lc2) assert 'Included launch description missing required argument' in str( excinfo2.value) # test that a declared argument that is not provided raises an error, but with other args set ld2 = LaunchDescription([DeclareLaunchArgument('foo')]) action2 = IncludeLaunchDescription( LaunchDescriptionSource(ld2), launch_arguments={'not_foo': 'NOT_FOO'}.items(), ) lc2 = LaunchContext() with pytest.raises(RuntimeError) as excinfo2: action2.visit(lc2) assert 'Included launch description missing required argument' in str( excinfo2.value) assert 'not_foo' in str(excinfo2.value) # test that a declared argument with a default value that is not provided does not raise ld2 = LaunchDescription( [DeclareLaunchArgument('foo', default_value='FOO')]) action2 = IncludeLaunchDescription(LaunchDescriptionSource(ld2)) lc2 = LaunchContext() action2.visit(lc2) # Test that default arguments in nested IncludeLaunchDescription actions do not raise ld1 = LaunchDescription( [DeclareLaunchArgument('foo', default_value='FOO')]) action1 = IncludeLaunchDescription(LaunchDescriptionSource(ld1), ) ld2 = LaunchDescription([action1, DeclareLaunchArgument('foo2')]) action2 = IncludeLaunchDescription( LaunchDescriptionSource(ld2), launch_arguments={'foo2': 'FOO2'}.items(), ) lc2 = LaunchContext() action2.visit(lc2) # Test that provided launch arguments of nested IncludeLaunchDescription actions do not raise ld1 = LaunchDescription([DeclareLaunchArgument('foo')]) action1 = IncludeLaunchDescription( LaunchDescriptionSource(ld1), launch_arguments={'foo': 'FOO'}.items(), ) ld2 = LaunchDescription([action1, DeclareLaunchArgument('foo2')]) action2 = IncludeLaunchDescription( LaunchDescriptionSource(ld2), launch_arguments={'foo2': 'FOO2'}.items(), ) lc2 = LaunchContext() action2.visit(lc2) # Test that arguments can not be passed from the parent launch description ld1 = LaunchDescription([DeclareLaunchArgument('foo')]) action1 = IncludeLaunchDescription(LaunchDescriptionSource(ld1)) ld2 = LaunchDescription([action1, DeclareLaunchArgument('foo2')]) action2 = IncludeLaunchDescription( LaunchDescriptionSource(ld2), launch_arguments={ 'foo': 'FOO', 'foo2': 'FOO2' }.items(), ) ld3 = LaunchDescription([action2]) ls = LaunchService() ls.include_launch_description(ld3) assert 1 == ls.run() # Test that arguments can be redeclared in the parent launch description ld1 = LaunchDescription([DeclareLaunchArgument('foo')]) action1 = IncludeLaunchDescription(LaunchDescriptionSource(ld1)) ld2 = LaunchDescription( [action1, DeclareLaunchArgument('foo'), DeclareLaunchArgument('foo2')]) action2 = IncludeLaunchDescription( LaunchDescriptionSource(ld2), launch_arguments={ 'foo': 'FOO', 'foo2': 'FOO2' }.items(), ) lc2 = LaunchContext() action2.visit(lc2) # Test that arguments after a ResetLaunchConfigurations action are not checked ld1 = LaunchDescription([DeclareLaunchArgument('foo')]) action1 = IncludeLaunchDescription(LaunchDescriptionSource(ld1)) ld2 = LaunchDescription([ DeclareLaunchArgument('foo2'), ResetLaunchConfigurations(), SetLaunchConfiguration('foo', 'asd'), action1 ]) action2 = IncludeLaunchDescription( LaunchDescriptionSource(ld2), launch_arguments={'foo2': 'FOO2'}.items(), ) lc2 = LaunchContext() action2.visit(lc2)
#!/usr/bin/env python3 import os from ament_index_python.packages import get_package_share_directory from launch.frontend import Parser from launch import LaunchService XML_FILE_NAME = 'crs.launch.xml' """ @summary: entry point for ros2 launch [pkg] [launch] @requires: ros-eloquent-launch-xml, install by running apt install ros-eloquent-launch-xml """ def generate_launch_description(): xml_file_path = str( os.path.join(get_package_share_directory('crs_application'), 'launch', XML_FILE_NAME)) print('Opening ROS2 launch file: %s' % (xml_file_path)) root_entity, parser = Parser.load(xml_file_path) ld = parser.parse_description(root_entity) return ld # main is not needed but it can be used to run as a standalone python program if __name__ == '__main__': ld = generate_launch_description() ls = LaunchService() ls.include_launch_description(ld) ls.run()
class ApexRunner(object): def __init__(self, gen_launch_description_fn, test_module, launch_file_arguments=[], debug=False): """ Create an ApexRunner object. :param callable gen_launch_description_fn: A function that returns a ros2 LaunchDesription for launching the processes under test. This function should take a callable as a parameter which will be called when the processes under test are ready for the test to start """ self._gen_launch_description_fn = gen_launch_description_fn self._test_module = test_module self._launch_service = LaunchService(debug=debug) self._processes_launched = threading.Event( ) # To signal when all processes started self._tests_completed = threading.Event( ) # To signal when all the tests have finished self._launch_file_arguments = launch_file_arguments # Can't run LaunchService.run on another thread :-( # See https://github.com/ros2/launch/issues/126 # Instead, we'll let the tests run on another thread self._test_tr = threading.Thread(target=self._run_test, name="test_runner_thread", daemon=True) def get_launch_description(self): return _normalize_ld(self._gen_launch_description_fn)(lambda: None)[0] def run(self): """ Launch the processes under test and run the tests. :return: A tuple of two unittest.Results - one for tests that ran while processes were active, and another set for tests that ran after processes were shutdown """ test_ld, test_context = _normalize_ld(self._gen_launch_description_fn)( lambda: self._processes_launched.set()) # Data to squirrel away for post-shutdown tests self.proc_info = ActiveProcInfoHandler() self.proc_output = ActiveIoHandler() self.test_context = test_context parsed_launch_arguments = parse_launch_arguments( self._launch_file_arguments) self.test_args = {} for k, v in parsed_launch_arguments: self.test_args[k] = v # Wrap the test_ld in another launch description so we can bind command line arguments to # the test and add our own event handlers for process IO and process exit: launch_description = LaunchDescription([ launch.actions.IncludeLaunchDescription( launch.LaunchDescriptionSource(launch_description=test_ld), launch_arguments=parsed_launch_arguments), RegisterEventHandler( OnProcessExit( on_exit=lambda info, unused: self.proc_info.append(info))), RegisterEventHandler( OnProcessIO( on_stdout=self.proc_output.append, on_stderr=self.proc_output.append, )), ]) self._launch_service.include_launch_description(launch_description) self._test_tr.start() # Run the tests on another thread self._launch_service.run( ) # This will block until the test thread stops it if not self._tests_completed.wait(timeout=0): # LaunchService.run returned before the tests completed. This can be because the user # did ctrl+c, or because all of the launched nodes died before the tests completed print("Processes under test stopped before tests completed") self._print_process_output_summary( ) # <-- Helpful to debug why processes died early # We treat this as a test failure and return some test results indicating such return FailResult(), FailResult() # Now, run the post-shutdown tests inactive_suite = PostShutdownTestLoader( injected_attributes={ "proc_info": self.proc_info, "proc_output": self.proc_output._io_handler, "test_args": self.test_args, }, injected_args=dict( self.test_context, # Add a few more things to the args dictionary: **{ "proc_info": self.proc_info, "proc_output": self.proc_output._io_handler, "test_args": self.test_args })).loadTestsFromModule(self._test_module) inactive_results = unittest.TextTestRunner( verbosity=2, resultclass=TestResult).run(inactive_suite) return self._results, inactive_results def validate(self): """Inspect the test configuration for configuration errors.""" # Make sure the function signature of the launch configuration # generator is correct inspect.getcallargs(self._gen_launch_description_fn, lambda: None) def _run_test(self): # Waits for the DUT processes to start (signaled by the _processes_launched # event) and then runs the tests if not self._processes_launched.wait(timeout=15): # Timed out waiting for the processes to start print("Timed out waiting for processes to start up") self._launch_service.shutdown() return try: # Load the tests active_suite = PreShutdownTestLoader( injected_attributes={ "proc_info": self.proc_info, "proc_output": self.proc_output, "test_args": self.test_args, }, injected_args=dict( self.test_context, # Add a few more things to the args dictionary: **{ "proc_info": self.proc_info, "proc_output": self.proc_output, "test_args": self.test_args })).loadTestsFromModule(self._test_module) # Run the tests self._results = unittest.TextTestRunner( verbosity=2, resultclass=TestResult).run(active_suite) finally: self._tests_completed.set() self._launch_service.shutdown() def _print_process_output_summary(self): failed_procs = [ proc for proc in self.proc_info if proc.returncode != 0 ] for process in failed_procs: print("Process '{}' exited with {}".format(process.process_name, process.returncode)) print("##### '{}' output #####".format(process.process_name)) try: for io in self.proc_output[process.action]: print("{}".format(io.text.decode('ascii'))) except KeyError: pass # Process generated no output print("#" * (len(process.process_name) + 21))