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 _launch(launch_description): loop = osrf_pycommon.process_utils.get_loop() ls = LaunchService() ls.include_launch_description(launch_description) launch_task = loop.create_task(ls.run_async()) loop.run_until_complete(asyncio.sleep(5, loop=loop)) if not launch_task.done(): loop.create_task(ls.shutdown()) loop.run_until_complete(launch_task) return ls.context
def _assert_launch_no_errors(actions, *, timeout_sec=5): ld = LaunchDescription(actions) ls = LaunchService(debug=True) ls.include_launch_description(ld) loop = osrf_pycommon.process_utils.get_loop() launch_task = loop.create_task(ls.run_async()) loop.run_until_complete(asyncio.sleep(timeout_sec)) if not launch_task.done(): loop.create_task(ls.shutdown()) loop.run_until_complete(launch_task) assert 0 == launch_task.result() return ls.context
class ComponentsLauncher: def __init__(self): self._components_list = list() self._launch_task = None self._loop = None self._launch_service = None def add_component(self, component): assert (isinstance(component, Component)) self._components_list.append(component) def launch(self): components_launch_description_list = map( lambda c: c.launch_description, self._components_list) launch_description = LaunchDescription( components_launch_description_list) self._launch_service = LaunchService(argv=argv) self._launch_service.include_launch_description(launch_description) self._loop = osrf_pycommon.process_utils.get_loop() self._launch_task = self._loop.create_task( self._launch_service.run_async()) self._loop.run_until_complete(self._launch_task) def shutdown(self): if self._launch_task is None: print_error( "ComponentsLauncher.shutdown: components launcher has not been launched" ) return if not self._launch_task.done(): asyncio.ensure_future(self._launch_service.shutdown(), loop=self._loop) self._loop.run_until_complete(self._launch_task)
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))
class _RunnerWorker(): def __init__(self, test_run, launch_file_arguments=[], debug=False): self._test_run = test_run 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 # # It would be simpler if we could run the pre-shutdown test and the post-shutdown tests on # one thread, and run the launch on another thead. # # Instead, we'll run the pre-shutdown tests on a background thread concurrent with the # launch on the main thread. Once the launch is stopped, we'll run the post-shutdown # tests on the main thread self._test_tr = threading.Thread(target=self._run_test, name='test_runner_thread', daemon=True) 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 = self._test_run.normalized_test_description( ready_fn=lambda: self._processes_launched.set()) # Data that needs to be bound to the tests: proc_info = ActiveProcInfoHandler() proc_output = ActiveIoHandler() full_context = dict(test_context, **self._test_run.param_args) # TODO pete: this can be simplified as a call to the dict ctor: parsed_launch_arguments = parse_launch_arguments( self._launch_file_arguments) test_args = {} for k, v in parsed_launch_arguments: test_args[k] = v self._test_run.bind( self._test_run.pre_shutdown_tests, injected_attributes={ 'proc_info': proc_info, 'proc_output': proc_output, 'test_args': test_args, }, injected_args=dict( full_context, # Add a few more things to the args dictionary: **{ 'proc_info': proc_info, 'proc_output': proc_output, 'test_args': test_args })) self._test_run.bind( self._test_run.post_shutdown_tests, injected_attributes={ 'proc_info': proc_info._proc_info_handler, 'proc_output': proc_output._io_handler, 'test_args': test_args, }, injected_args=dict( full_context, # Add a few more things to the args dictionary: **{ 'proc_info': proc_info._proc_info_handler, 'proc_output': proc_output._io_handler, 'test_args': test_args })) # 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: proc_info.append(info))), RegisterEventHandler( OnProcessIO( on_stdout=proc_output.append, on_stderr=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') # Give some extra help debugging why processes died early self._print_process_output_summary(proc_info, proc_output) # We treat this as a test failure and return some test results indicating such raise _LaunchDiedException() inactive_results = unittest.TextTestRunner( verbosity=2, resultclass=TestResult).run(self._test_run.post_shutdown_tests) self._results.append(inactive_results) return self._results 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: # Run the tests self._results = unittest.TextTestRunner( verbosity=2, resultclass=TestResult).run(self._test_run.pre_shutdown_tests) finally: self._tests_completed.set() self._launch_service.shutdown() def _print_process_output_summary(self, proc_info, proc_output): failed_procs = [proc for proc in 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 proc_output[process.action]: print('{}'.format(io.text.decode('ascii'))) except KeyError: pass # Process generated no output print('#' * (len(process.process_name) + 21))
def check_launch_component_container(file): root_entity, parser = Parser.load(file) ld = parser.parse_description(root_entity) ls = LaunchService() ls.include_launch_description(ld) loop = osrf_pycommon.process_utils.get_loop() launch_task = loop.create_task(ls.run_async()) node_container, load_composable_node = ld.describe_sub_entities() talker = node_container._ComposableNodeContainer__composable_node_descriptions[ 0] listener = load_composable_node._LoadComposableNodes__composable_node_descriptions[ 0] def perform(substitution): return perform_substitutions(ls.context, substitution) # Check container params assert perform(node_container._Node__package) == 'rclcpp_components' assert perform( node_container._Node__node_executable) == 'component_container' assert perform(node_container._Node__node_name) == 'my_container' assert perform(node_container._Node__node_namespace) == '' assert perform(node_container._Node__arguments[0]) == 'test_args' assert perform(load_composable_node._LoadComposableNodes__target_container ) == 'my_container' # Check node parameters talker_remappings = list(talker._ComposableNode__remappings) listener_remappings = list(listener._ComposableNode__remappings) talker_params = evaluate_parameters(ls.context, talker._ComposableNode__parameters) listener_params = evaluate_parameters(ls.context, listener._ComposableNode__parameters) talker_extra_args = evaluate_parameters( ls.context, talker._ComposableNode__extra_arguments) listener_extra_args = evaluate_parameters( ls.context, listener._ComposableNode__extra_arguments) assert perform(talker._ComposableNode__package) == 'composition' assert perform( talker._ComposableNode__node_plugin) == 'composition::Talker' assert perform(talker._ComposableNode__node_name) == 'talker' assert perform(talker._ComposableNode__node_namespace) == 'test_namespace' assert (perform(talker_remappings[0][0]), perform(talker_remappings[0][1])) == ('chatter', '/remap/chatter') assert talker_params[0]['use_sim_time'] is True assert perform(listener._ComposableNode__package) == 'composition' assert perform( listener._ComposableNode__node_plugin) == 'composition::Listener' assert perform(listener._ComposableNode__node_name) == 'listener' assert perform( listener._ComposableNode__node_namespace) == 'test_namespace' assert (perform(listener_remappings[0][0]), perform(listener_remappings[0][1])) == ('chatter', '/remap/chatter') assert listener_params[0]['use_sim_time'] is True # Check extra arguments assert talker_extra_args[0]['use_intra_process_comms'] is True assert listener_extra_args[0]['use_intra_process_comms'] is True timeout_sec = 5 loop.run_until_complete(asyncio.sleep(timeout_sec)) if not launch_task.done(): loop.create_task(ls.shutdown()) loop.run_until_complete(launch_task) assert 0 == launch_task.result()
class ApexRunner(object): def __init__(self, gen_launch_description_fn, test_module, launch_file_arguments=[]): """ Create an ApexRunner object. :param callable gen_launch_description_fn: A function that returns a ros2 LaunchDesription for launching the nodes under test. This function should take a callable as a parameter which will be called when the nodes 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() self._nodes_launched = threading.Event( ) # To signal when all nodes 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 self._gen_launch_description_fn(lambda: None) def run(self): """ Launch the nodes under test and run the tests. :return: A tuple of two unittest.Results - one for tests that ran while nodes were active, and another set for tests that ran after nodes were shutdown """ test_ld = self._gen_launch_description_fn( lambda: self._nodes_launched.set()) # Data to squirrel away for post-shutdown tests self.proc_info = ActiveProcInfoHandler() self.proc_output = ActiveIoHandler() 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 # We should treat this as a test failure and return some test results indicating such print("Nodes under test stopped before tests completed") # TODO: This will make the program exit with an exit code, but it's not apparent # why. Consider having apex_rostest_main print some summary return _fail_result(), _fail_result() # Now, run the post-shutdown tests inactive_suite = PostShutdownTestLoader().loadTestsFromModule( self._test_module) self._give_attribute_to_tests(self.proc_info, "proc_info", inactive_suite) self._give_attribute_to_tests(self.proc_output._io_handler, "proc_output", inactive_suite) self._give_attribute_to_tests(self.test_args, "test_args", inactive_suite) 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 nodes to start (signaled by the _nodes_launched # event) and then runs the tests if not self._nodes_launched.wait(timeout=15): # Timed out waiting for the nodes to start print("Timed out waiting for nodes to start up") self._launch_service.shutdown() return node = None try: # Load the tests active_suite = PreShutdownTestLoader().loadTestsFromModule( self._test_module) # Start up a ROS2 node which will be made available to the tests rclpy.init() node = rclpy.create_node( "test_node") # TODO: Give this a better (unique?) name self._give_attribute_to_tests(node, "node", active_suite) self._give_attribute_to_tests(self.proc_output, "proc_output", active_suite) self._give_attribute_to_tests(self.proc_info, "proc_info", active_suite) self._give_attribute_to_tests(self.test_args, "test_args", active_suite) # Run the tests self._results = unittest.TextTestRunner( verbosity=2, resultclass=TestResult).run(active_suite) finally: self._tests_completed.set() if node: node.destroy_node() self._launch_service.shutdown() def _give_attribute_to_tests(self, data, attr_name, test_suite): # Test suites can contain other test suites which will eventually contain # the actual test classes to run. This function will recursively drill down until # we find the actual tests and give the tests a reference to the node # The effect of this is that every test will have self.node available to it so that # it can interact with ROS2 or the process exit coes, or whatever data we want try: iter(test_suite) except TypeError: # Base case - test_suite is not iterable, so it must be an individual test method setattr(test_suite, attr_name, data) else: # Otherwise, it's a test_suite, or a list of individual test methods. recurse for test in test_suite: self._give_attribute_to_tests(data, attr_name, test)