Skip to content

Commit 6eb159c

Browse files
Add test for scoping of current launch file/dir context locals
Signed-off-by: Christophe Bedard <[email protected]>
1 parent 86939f1 commit 6eb159c

File tree

3 files changed

+123
-0
lines changed

3 files changed

+123
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright 2025 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from launch import LaunchContext
16+
from launch import LaunchDescription
17+
from launch.actions import OpaqueFunction
18+
from launch.actions import SetEnvironmentVariable
19+
from launch.substitutions import ThisLaunchFile
20+
from launch.substitutions import ThisLaunchFileDir
21+
22+
23+
def set_local(context: LaunchContext) -> None:
24+
context.extend_locals({'included_local': 'context_local_value'})
25+
26+
27+
def generate_launch_description():
28+
"""Fixture for tests."""
29+
return LaunchDescription([
30+
SetEnvironmentVariable('Included_ThisLaunchFile', ThisLaunchFile()),
31+
OpaqueFunction(function=set_local),
32+
SetEnvironmentVariable('Included_ThisLaunchFileDir', ThisLaunchFileDir()),
33+
])
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2025 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
17+
from launch import LaunchDescription
18+
from launch.actions import IncludeLaunchDescription
19+
from launch.actions import SetEnvironmentVariable
20+
from launch.substitutions import ThisLaunchFile
21+
from launch.substitutions import ThisLaunchFileDir
22+
23+
24+
def generate_launch_description():
25+
"""Fixture for tests."""
26+
return LaunchDescription([
27+
SetEnvironmentVariable('Before_ThisLaunchFile', ThisLaunchFile()),
28+
SetEnvironmentVariable('Before_ThisLaunchFileDir', ThisLaunchFileDir()),
29+
IncludeLaunchDescription(
30+
os.path.join(
31+
os.path.dirname(os.path.abspath(__file__)),
32+
'included',
33+
'launch_file_dir.launch.py',
34+
),
35+
),
36+
SetEnvironmentVariable('After_ThisLaunchFile', ThisLaunchFile()),
37+
SetEnvironmentVariable('After_ThisLaunchFileDir', ThisLaunchFileDir()),
38+
])

launch/test/launch/actions/test_include_launch_description.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
"""Tests for the IncludeLaunchDescription action class."""
1616

17+
import os
1718
from pathlib import Path
1819

1920
from launch import LaunchContext
@@ -22,9 +23,13 @@
2223
from launch import LaunchService
2324
from launch.actions import DeclareLaunchArgument
2425
from launch.actions import IncludeLaunchDescription
26+
from launch.actions import OpaqueFunction
2527
from launch.actions import ResetLaunchConfigurations
28+
from launch.actions import SetEnvironmentVariable
2629
from launch.actions import SetLaunchConfiguration
2730
from launch.launch_description_sources import PythonLaunchDescriptionSource
31+
from launch.substitutions import ThisLaunchFile
32+
from launch.substitutions import ThisLaunchFileDir
2833
from launch.utilities import perform_substitutions
2934

3035
import pytest
@@ -87,6 +92,53 @@ def test_include_launch_description_launch_file_location():
8792
assert action2.get_asyncio_future() is None
8893

8994

95+
def test_include_launch_description_launch_file_dir_location_scoped():
96+
"""Test that the launch file name & dir locals are scoped to the included launch file."""
97+
# Rely on the test launch files to set environment variables with
98+
# ThisLaunchFile()/ThisLaunchFileDir() to make testing easier
99+
parent_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'launch')
100+
parent_launch_file = os.path.join(parent_dir, 'parent_launch_file_dir.launch.py')
101+
included_dir = os.path.join(parent_dir, 'included')
102+
included_launch_file = os.path.join(included_dir, 'launch_file_dir.launch.py')
103+
104+
# The current launch file/dir context locals should be scoped to the included launch file
105+
ld = LaunchDescription([IncludeLaunchDescription(parent_launch_file)])
106+
ls = LaunchService()
107+
ls.include_launch_description(ld)
108+
assert 0 == ls.run()
109+
lc = ls.context
110+
assert lc.environment.get('Before_ThisLaunchFile') == parent_launch_file
111+
assert lc.environment.get('Before_ThisLaunchFileDir') == parent_dir
112+
assert lc.environment.get('Included_ThisLaunchFile') == included_launch_file
113+
assert lc.environment.get('Included_ThisLaunchFileDir') == included_dir
114+
assert lc.environment.get('After_ThisLaunchFile') == parent_launch_file
115+
assert lc.environment.get('After_ThisLaunchFileDir') == parent_dir
116+
117+
# The launch file/dir context locals should be completely removed after the first included
118+
# (parent) launch file, because at that point we're in a launch script and not a launch file,
119+
# and therefore these substitutions should raise an error
120+
ld2 = LaunchDescription([
121+
IncludeLaunchDescription(parent_launch_file),
122+
SetEnvironmentVariable('Outside_ThisLaunchFile', ThisLaunchFile()),
123+
SetEnvironmentVariable('Outside_ThisLaunchFileDir', ThisLaunchFileDir()),
124+
])
125+
ls2 = LaunchService()
126+
ls2.include_launch_description(ld2)
127+
assert 1 == ls2.run()
128+
129+
# The non-launch file/dir context locals should not be scoped to the included launch file
130+
def assert_unscoped_context_local(context: LaunchContext):
131+
assert context.locals.included_local == 'context_local_value'
132+
133+
ld3 = LaunchDescription([
134+
IncludeLaunchDescription(parent_launch_file),
135+
OpaqueFunction(function=assert_unscoped_context_local),
136+
])
137+
ls3 = LaunchService()
138+
ls3.include_launch_description(ld3)
139+
assert 0 == ls3.run()
140+
141+
90142
def test_include_launch_description_launch_arguments():
91143
"""Test the interactions between declared launch arguments and IncludeLaunchDescription."""
92144
# test that arguments are set when given, even if they are not declared

0 commit comments

Comments
 (0)