Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions launch_ros/test/test_cli_namespace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3

# Copyright 2025 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest
import subprocess

class TestNamespaceArgument(unittest.TestCase):
"""Test the --namespace command line argument for ros2 launch."""

def test_namespace_argument(self):
"""Test that the --namespace argument correctly namespaces topics."""
try:
# Start the talker_listener launch file with namespace argument
launch_proc_remapped = subprocess.Popen(
['ros2', 'launch', 'demo_nodes_cpp', 'talker_listener_launch.py',
'--namespace', 'test_namespace'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)

# Run ros2 topic list to get the remapped topics
result = subprocess.run(['ros2', 'topic', 'list'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=True)
remapped_topics = result.stdout.strip().split('\n')

# Verify /chatter is NOT in the list
self.assertNotIn('/chatter', remapped_topics,
f"Did not expect to find /chatter after remapping. Topics: {remapped_topics}")

# Verify /test_namespace/chatter IS in the list
self.assertIn('/test_namespace/chatter', remapped_topics,
f"Expected to find /test_namespace/chatter after remapping. Topics: {remapped_topics}")

finally:
# Clean up
if 'launch_proc_remapped' in locals():
launch_proc_remapped.terminate()
try:
launch_proc_remapped.wait(timeout=5)
except subprocess.TimeoutExpired:
launch_proc_remapped.kill()
launch_proc_remapped.wait()


if __name__ == '__main__':
unittest.main()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing last line feed detected by github.

13 changes: 9 additions & 4 deletions ros2launch/ros2launch/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import launch
from launch.frontend import Parser
from launch.launch_description_sources import get_launch_description_from_any_launch_file
from launch_ros.actions import PushROSNamespace


class MultipleLaunchFilesError(Exception):
Expand Down Expand Up @@ -145,7 +146,8 @@ def launch_a_launch_file(
noninteractive=False,
args=None,
option_extensions={},
debug=False
debug=False,
namespace=None
):
"""Launch a given launch file (by path) and pass it the given launch file arguments."""
for name in sorted(option_extensions.keys()):
Expand All @@ -167,14 +169,17 @@ def launch_a_launch_file(
parsed_launch_arguments = parse_launch_arguments(launch_file_arguments)
# Include the user provided launch file using IncludeLaunchDescription so that the
# location of the current launch file is set.
launch_description = launch.LaunchDescription([
launch_description = launch.LaunchDescription()
if namespace is not None:
launch_description.add_action(PushROSNamespace(namespace))
launch_description.add_action(
launch.actions.IncludeLaunchDescription(
launch.launch_description_sources.AnyLaunchDescriptionSource(
launch_file_path
),
launch_arguments=parsed_launch_arguments,
),
])
)
)
for name in sorted(option_extensions.keys()):
result = option_extensions[name].prelaunch(
launch_description,
Expand Down
7 changes: 6 additions & 1 deletion ros2launch/ros2launch/command/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ def add_arguments(self, parser, cli_name):
help=('Regex pattern for filtering which executables the --launch-prefix is applied '
'to by matching the executable name.')
)
parser.add_argument(
'--namespace',
help=('A namespace to push to the actions/nodes started by the launch file.')
)
arg = parser.add_argument(
'package_name',
help='Name of the ROS package which contains the launch file')
Expand Down Expand Up @@ -175,5 +179,6 @@ def main(self, *, parser, args):
noninteractive=args.noninteractive,
args=args,
option_extensions=self._option_extensions,
debug=args.debug
debug=args.debug,
namespace=args.namespace
)