ROS 2 Launch Framework
repository·rolling·Indexed 41 days ago
https://github.com/ros2/launchA system for orchestrating the execution of multiple processes in ROS 2. It includes tools for managing complex systems and integration testing via launch_pytest and launch_testing, as well as support for defining launch descriptions using XML (launch_xml) and YAML (launch_yaml).
What's inside ROS 2 Launch
- The ROS 2 Launch Framework provides tools for launching multiple processes simultaneously and for writing tests that involve multiple processes. It is designed to manage the lifecycle and execution of complex systems by orchestrating various nodes and components.
How active tests and post-shutdown tests work
rollingThe
launch_testingframework distinguishes between two types of tests:Active Tests: Any class inheriting from
unittest.TestCasethat is not decorated with@launch_testing.post_shutdown_test. These run concurrently with the launched processes and are used to interact with running nodes (e.g., checking stdout in real-time).Post-Shutdown Tests: Any class inheriting from
unittest.TestCasedecorated with@launch_testing.post_shutdown_test. These run after the launched processes have been shut down. They are used to verify exit codes, final stdout/stderr, or side-effects created by the processes.
Access process output and exit codes in tests
rollingThe framework automatically injects two objects into your test cases to allow access to process information:
self.proc_info: AProcInfoHandlerobject used to access exit codes and manage process shutdown.self.proc_output: AnIoHandlerobject used to access stdout and stderr.
Both objects provide dictionary-like access to process information and methods to wait for specific events.
Extend the launch framework via extension points
rollingThe
launchframework provides extension points that allow external Python packages to add new functionality to the core categories. To extend the framework, your custom classes must follow these inheritance requirements:- New Actions: Must inherit from
launch.Action. - New Events: Must inherit from
launch.Event. - New Substitutions: Must inherit from
launch.Substitution. - New Launch Entities: Must inherit from
launch.LaunchDescriptionEntity.
- New Actions: Must inherit from
Implement custom Event Handlers
rollingEvent handlers allow you to react to specific lifecycle events within a launch session. To create a custom event handler, you must inherit from the
launch.EventHandlerbase class and implement two required methods:matches(event): Receives the event as input. It must returnTrueif the handler should react to this specific event, orFalseotherwise.handle(event, launch_context): Receives the event and the currentlaunch_context. This method can perform side effects and optionally returns a list oflaunch.LaunchDescriptionEntityobjects. If returned, these entities are visited by the launch service depth-first.
Note that event handlers are not
LaunchDescriptionEntityobjects themselves, but they are 'visited' by the launch service during event processing.How Launch Entities and Launch Descriptions work
rollingThe core of the
launchframework is built aroundLaunchDescriptionEntityobjects. These entities capture the user's intent for how a system should be launched and how the system should react to asynchronous events.Key components include:
launch.LaunchDescriptionEntity: The base class for all entities that are "launched". They implement avisitmethod (called during execution) anddescribemethods (called during introspection).launch.LaunchDescription: The root entity that encapsulates a list oflaunch.Actionobjects. It serves as the starting point for the launch process.launch.Action: A type of entity that performs side effects (like running a process) or yields other actions. Actions can be introspected without being executed.launch.Substitution: Values embedded within descriptions that are only evaluated at runtime (e.g., fetching an environment variable or a launch configuration).
How to use @pytest.mark.launch for different test types
rollingThe behavior of your test depends on whether it is a standard function, a coroutine, a generator, or an async generator. Use the
shutdownargument to control when the test runs relative to the launch service lifecycle.Standard Functions and Coroutines
- Normal behavior: Runs concurrently with the launch service.
shutdown=True: Runs only after the launch service has shut down.- Note on Coroutines: Coroutines run in the same event loop as the launch description, while normal functions run in a separate thread.
Generators and Async Generators
Generators allow you to split a test into two phases: a concurrent phase and a post-shutdown phase.
- Phase 1: The code before the
yieldruns concurrently with the launch service. - Phase 2: The code after the
yieldruns after the launch service has shut down. - Constraint: You cannot pass the
shutdownargument to the decorator when using generators. - Note on Async Generators: Async generators run in the same event loop as the launch description.
# Standard function (concurrent) @pytest.mark.launch(fixture=my_ld_fixture) def normal_test_case(): pass # Standard function (after shutdown) @pytest.mark.launch(fixture=my_ld_fixture, shutdown=True) def shutdown_test_case(): pass # Generator (concurrent phase -> yield -> post-shutdown phase) @pytest.mark.launch(fixture=my_ld_fixture) def generator_test_case(): assert True # Runs while service is active yield assert True # Runs after service shuts downAccess nested Entities and children
rollingAttributes in the YAML tree can themselves be entities or lists of entities.
- Nested Entities: If an attribute is an entity, you can access its properties using
get_attrwithdata_type=List[Entity]or by accessing the attribute name directly as a property. - Children: You can access all child elements of an entity using the
.childrenproperty. This returns a list oflaunch_xml.Entityobjects wrapping each child.
# Accessing a list of entities (e.g., an 'env' list) env = e.get_attr('env', data_type=List[Entity]) len(env) == 2 env[0].get_attr('name') == 'a' # Accessing children directly # Returns a list of Entity objects wrapping the children child_entities = e.children- Nested Entities: If an attribute is an entity, you can access its properties using
Define launch_pytest fixtures
rollingA
@launch_pytest.fixturemust return alaunch.LaunchDescriptionobject or a sequence where the first item is alaunch.LaunchDescription.Controlling Test Start Timing
You can include
launch_pytest.actions.ReadyToTest()in yourLaunchDescriptionto signal exactly when the launch service is ready and the active tests should begin. If you do not add this action, one will be automatically appended to the end of your description.Fixture Scopes
launch_pytestfixtures supportmodule,class, orfunctionscopes (default isfunction).functionscope: A new launch service is started and shut down for every single test case.modulescope: The launch service is started once for the entire module, and all test cases run concurrently with that same service.
Best Practices:
- Do not mix
modulescope fixtures withclassorfunctionscope fixtures in the same file. - Avoid using scopes larger than
module. - A single test should not depend on more than one
launch_pytestfixture.
@launch_pytest.fixture def launch_description(hello_world_proc): """Launch a simple process to print 'hello_world'.""" return launch.LaunchDescription([ hello_world_proc, # Tell launch when to start the test # If no ReadyToTest action is added, one will be appended automatically. launch_pytest.actions.ReadyToTest() ]) @pytest.mark.launch(fixture=launch_description) def test_case(): passConfigure launch tests in CMake
rollingTo integrate launch tests into a ROS 2 package using CMake:
- Add
launch_testing_ament_cmaketo yourpackage.xmldependencies. - In
CMakeLists.txt, usefind_packageandadd_launch_test.
Basic usage:
find_package(launch_testing_ament_cmake) add_launch_test(test/name_of_test.test.py)Passing arguments via CMake:
add_launch_test( test/test_with_args.test.py ARGS "arg1:=foo" )- Add
How to use the Launch Service
rollingThe
launch.LaunchServiceis a long-running activity that manages the event loop, dispatches events to handlers, and executes actions.To use a
LaunchServicein a typical application, follow these steps:- Create a
LaunchDescription(programmatically or via a markup file). - Instantiate a
LaunchService. - Include the
LaunchDescriptionin the service. - (Recommended) Add a signal handler (e.g., for
SIGINT) that callsshutdown()on the service. - Run the service's event loop.
Note: You can include launch descriptions asynchronously from other threads (e.g., from a REST or ROS service) while the launch service is already running.
- Create a
Quick start with launch_pytest
rollingTo use
launch_pytestfor integration testing, create a pytest file that defines a@launch_pytest.fixturereturning alaunch.LaunchDescription. Mark your test cases with@pytest.mark.launch(fixture=<fixture_name>)to associate them with that launch description.Run your tests using the standard pytest module command:
python3 -m pytest <path_to_test_file>python3 -m pytest test/launch_pytest/examples/pytest_hello_world.py