diff --git a/launch/doc/source/architecture.rst b/launch/doc/source/architecture.rst
index c76aef835..4431d1ace 100644
--- a/launch/doc/source/architecture.rst
+++ b/launch/doc/source/architecture.rst
@@ -149,6 +149,17 @@ There are many possible variations of a substitution, but here are some of the c
- This substitution locates the full path to an executable on the PATH if it exists.
+- :class:`launch.substitutions.IsEmptySubstitution`
+
+ - This substitution checks whether a string is empty, returning 'true' or 'false'.
+ - For example, ``$(is-empty $(var arg))`` returns 'true' if the launch configuration is empty.
+
+- :class:`launch.substitutions.NotEmptySubstitution`
+
+ - This substitution checks whether a string is not empty, returning 'true' or 'false'.
+ - For example, ``$(not-empty $(var arg))`` returns 'true' if the launch configuration has content.
+ - This is the inverse of :class:`launch.substitutions.IsEmptySubstitution`.
+
The base substitution class provides some common introspection interfaces (which the specific derived substitutions may influence).
The Launch Service
diff --git a/launch/launch/substitutions/__init__.py b/launch/launch/substitutions/__init__.py
index 7fa7170e7..fbc84d5be 100644
--- a/launch/launch/substitutions/__init__.py
+++ b/launch/launch/substitutions/__init__.py
@@ -28,9 +28,11 @@
from .for_loop_var import ForEachVar
from .for_loop_var import ForLoopIndex
from .if_else_substitution import IfElseSubstitution
+from .is_empty_substitution import IsEmptySubstitution
from .launch_configuration import LaunchConfiguration
from .launch_log_dir import LaunchLogDir
from .local_substitution import LocalSubstitution
+from .not_empty_substitution import NotEmptySubstitution
from .not_equals_substitution import NotEqualsSubstitution
from .path_join_substitution import PathJoinSubstitution
from .path_join_substitution import PathSubstitution
@@ -54,9 +56,11 @@
'ForEachVar',
'ForLoopIndex',
'IfElseSubstitution',
+ 'IsEmptySubstitution',
'LaunchConfiguration',
'LaunchLogDir',
'LocalSubstitution',
+ 'NotEmptySubstitution',
'NotSubstitution',
'NotEqualsSubstitution',
'OrSubstitution',
diff --git a/launch/launch/substitutions/is_empty_substitution.py b/launch/launch/substitutions/is_empty_substitution.py
new file mode 100644
index 000000000..3b1176df7
--- /dev/null
+++ b/launch/launch/substitutions/is_empty_substitution.py
@@ -0,0 +1,91 @@
+# Copyright 2026 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.
+
+"""Module for the IsEmptySubstitution substitution."""
+
+from typing import Any
+from typing import Dict
+from typing import List
+from typing import Sequence
+from typing import Text
+from typing import Tuple
+from typing import Type
+
+from ..frontend.expose import expose_substitution
+from ..launch_context import LaunchContext
+from ..some_substitutions_type import SomeSubstitutionsType
+from ..substitution import Substitution
+from ..utilities import normalize_to_list_of_substitutions
+from ..utilities import perform_substitutions
+
+
+@expose_substitution('is-empty')
+class IsEmptySubstitution(Substitution):
+ """
+ Substitution that checks whether a string is empty.
+
+ Returns 'true' or 'false' strings depending on whether the input is empty.
+
+ For example, checking if a launch configuration is empty:
+
+ .. code-block:: python
+
+ is_empty = IsEmptySubstitution(LaunchConfiguration('arg'))
+
+ .. code-block:: xml
+
+
+
+ .. code-block:: yaml
+
+ - let:
+ name: empty_check
+ value: "$(is-empty $(var arg))"
+
+ This can be useful for conditional logic based on whether a string value
+ has content or not.
+ """
+
+ def __init__(self, value: SomeSubstitutionsType) -> None:
+ """
+ Create an IsEmptySubstitution.
+
+ :param value: string or substitutions whose emptiness is checked
+ """
+ super().__init__()
+ self.__value = normalize_to_list_of_substitutions(value)
+
+ @classmethod
+ def parse(
+ cls, data: Sequence[SomeSubstitutionsType]
+ ) -> Tuple[Type['IsEmptySubstitution'], Dict[str, Any]]:
+ """Parse `IsEmptySubstitution` substitution."""
+ if len(data) != 1:
+ raise TypeError('is-empty substitution expects 1 argument')
+ return cls, {'value': data[0]}
+
+ @property
+ def value(self) -> List[Substitution]:
+ """Getter for the value to check."""
+ return self.__value
+
+ def describe(self) -> Text:
+ """Return a description of this substitution as a string."""
+ return 'IsEmpty({})'.format(
+ ' + '.join([sub.describe() for sub in self.value]))
+
+ def perform(self, context: LaunchContext) -> Text:
+ """Perform substitutions and check if the result is empty."""
+ result = perform_substitutions(context, self.value)
+ return str(result == '').lower()
diff --git a/launch/launch/substitutions/not_empty_substitution.py b/launch/launch/substitutions/not_empty_substitution.py
new file mode 100644
index 000000000..a5711f0e2
--- /dev/null
+++ b/launch/launch/substitutions/not_empty_substitution.py
@@ -0,0 +1,91 @@
+# Copyright 2026 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.
+
+"""Module for the NotEmptySubstitution substitution."""
+
+from typing import Any
+from typing import Dict
+from typing import List
+from typing import Sequence
+from typing import Text
+from typing import Tuple
+from typing import Type
+
+from ..frontend.expose import expose_substitution
+from ..launch_context import LaunchContext
+from ..some_substitutions_type import SomeSubstitutionsType
+from ..substitution import Substitution
+from ..utilities import normalize_to_list_of_substitutions
+from ..utilities import perform_substitutions
+
+
+@expose_substitution('not-empty')
+class NotEmptySubstitution(Substitution):
+ """
+ Substitution that checks whether a string is not empty.
+
+ Returns 'true' or 'false' strings depending on whether the input has content.
+
+ For example, checking if launch configuration is not an empty string:
+
+ .. code-block:: python
+
+ not_empty = NotEmptySubstitution(LaunchConfiguration('arg'))
+
+ .. code-block:: xml
+
+
+
+ .. code-block:: yaml
+
+ - let:
+ name: not_empty_check
+ value: "$(not-empty $(var arg))"
+
+ This can be useful for conditional logic based on whether a string value
+ has content or not. It is the inverse of IsEmptySubstitution.
+ """
+
+ def __init__(self, value: SomeSubstitutionsType) -> None:
+ """
+ Create a NotEmptySubstitution.
+
+ :param value: string or substitutions whose non-emptiness is checked
+ """
+ super().__init__()
+ self.__value = normalize_to_list_of_substitutions(value)
+
+ @classmethod
+ def parse(
+ cls, data: Sequence[SomeSubstitutionsType]
+ ) -> Tuple[Type['NotEmptySubstitution'], Dict[str, Any]]:
+ """Parse `NotEmptySubstitution` substitution."""
+ if len(data) != 1:
+ raise TypeError('not-empty substitution expects 1 argument')
+ return cls, {'value': data[0]}
+
+ @property
+ def value(self) -> List[Substitution]:
+ """Getter for the value to check."""
+ return self.__value
+
+ def describe(self) -> Text:
+ """Return a description of this substitution as a string."""
+ return 'NotEmpty({})'.format(
+ ' + '.join([sub.describe() for sub in self.value]))
+
+ def perform(self, context: LaunchContext) -> Text:
+ """Perform substitutions and check if the result is not empty."""
+ result = perform_substitutions(context, self.value)
+ return str(result != '').lower()
diff --git a/launch/test/launch/substitutions/test_is_empty_substitution.py b/launch/test/launch/substitutions/test_is_empty_substitution.py
new file mode 100644
index 000000000..8bda7c91d
--- /dev/null
+++ b/launch/test/launch/substitutions/test_is_empty_substitution.py
@@ -0,0 +1,54 @@
+# Copyright 2026 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.
+
+"""Tests for the IsEmptySubstitution substitution class."""
+
+from launch import LaunchContext
+from launch.substitutions import IsEmptySubstitution
+from launch.substitutions import TextSubstitution
+
+import pytest
+
+
+@pytest.mark.parametrize('value, expected', [
+ ('', 'true'),
+ ('robot', 'false'),
+ (' ', 'false'),
+ ('\t\n\r', 'false'),
+])
+def test_is_empty(value, expected):
+ """Test checking if a string is empty."""
+ substitution = IsEmptySubstitution(value)
+ assert substitution.perform(LaunchContext()) == expected
+
+
+def test_is_empty_nested_substitutions():
+ """Test checking if a value assembled from multiple substitutions is empty."""
+ substitution = IsEmptySubstitution([
+ TextSubstitution(text='robot'),
+ TextSubstitution(text=' name'),
+ ])
+ assert substitution.perform(LaunchContext()) == 'false'
+
+
+def test_is_empty_parse():
+ """Test the frontend parser contract."""
+ substitution_type, kwargs = IsEmptySubstitution.parse(['some value'])
+ assert substitution_type is IsEmptySubstitution
+ assert kwargs == {'value': 'some value'}
+
+ with pytest.raises(TypeError, match='expects 1 argument'):
+ IsEmptySubstitution.parse([])
+ with pytest.raises(TypeError, match='expects 1 argument'):
+ IsEmptySubstitution.parse(['one', 'two'])
diff --git a/launch/test/launch/substitutions/test_not_empty_substitution.py b/launch/test/launch/substitutions/test_not_empty_substitution.py
new file mode 100644
index 000000000..54107430a
--- /dev/null
+++ b/launch/test/launch/substitutions/test_not_empty_substitution.py
@@ -0,0 +1,54 @@
+# Copyright 2026 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.
+
+"""Tests for the NotEmptySubstitution substitution class."""
+
+from launch import LaunchContext
+from launch.substitutions import NotEmptySubstitution
+from launch.substitutions import TextSubstitution
+
+import pytest
+
+
+@pytest.mark.parametrize('value, expected', [
+ ('', 'false'),
+ ('robot', 'true'),
+ (' ', 'true'),
+ ('\t\n\r', 'true'),
+])
+def test_not_empty(value, expected):
+ """Test checking if a string is not empty."""
+ substitution = NotEmptySubstitution(value)
+ assert substitution.perform(LaunchContext()) == expected
+
+
+def test_not_empty_nested_substitutions():
+ """Test checking if a value assembled from multiple substitutions is not empty."""
+ substitution = NotEmptySubstitution([
+ TextSubstitution(text='robot'),
+ TextSubstitution(text=' name'),
+ ])
+ assert substitution.perform(LaunchContext()) == 'true'
+
+
+def test_not_empty_parse():
+ """Test the frontend parser contract."""
+ substitution_type, kwargs = NotEmptySubstitution.parse(['some value'])
+ assert substitution_type is NotEmptySubstitution
+ assert kwargs == {'value': 'some value'}
+
+ with pytest.raises(TypeError, match='expects 1 argument'):
+ NotEmptySubstitution.parse([])
+ with pytest.raises(TypeError, match='expects 1 argument'):
+ NotEmptySubstitution.parse(['one', 'two'])
diff --git a/launch_xml/test/launch_xml/test_is_empty_substitution.py b/launch_xml/test/launch_xml/test_is_empty_substitution.py
new file mode 100644
index 000000000..8d782a5a1
--- /dev/null
+++ b/launch_xml/test/launch_xml/test_is_empty_substitution.py
@@ -0,0 +1,92 @@
+# Copyright 2026 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.
+
+"""Test parsing an IsEmptySubstitution in an XML launch file."""
+
+import io
+import textwrap
+
+from launch.actions import DeclareLaunchArgument
+from launch.actions import SetLaunchConfiguration
+from launch.frontend import Parser
+from launch.launch_context import LaunchContext
+from launch.substitutions import IsEmptySubstitution
+
+
+def test_nested():
+ xml_file = textwrap.dedent(
+ """
+
+
+
+
+ """
+ )
+ root_entity, parser = Parser.load(io.StringIO(xml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 2
+ assert isinstance(launch_description.entities[0], DeclareLaunchArgument)
+ assert isinstance(launch_description.entities[1], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ launch_description.entities[0].visit(context)
+
+ let = launch_description.entities[1]
+ assert isinstance(let.value[0], IsEmptySubstitution)
+ assert let.value[0].perform(context) == 'false'
+
+
+def test_nested_empty():
+ xml_file = textwrap.dedent(
+ """
+
+
+
+
+ """
+ )
+ root_entity, parser = Parser.load(io.StringIO(xml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 2
+ assert isinstance(launch_description.entities[0], DeclareLaunchArgument)
+ assert isinstance(launch_description.entities[1], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ launch_description.entities[0].visit(context)
+
+ let = launch_description.entities[1]
+ assert isinstance(let.value[0], IsEmptySubstitution)
+ assert let.value[0].perform(context) == 'true'
+
+
+def test_empty_string():
+ xml_file = textwrap.dedent(
+ """
+
+
+
+ """
+ )
+ root_entity, parser = Parser.load(io.StringIO(xml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 1
+ assert isinstance(launch_description.entities[0], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ let = launch_description.entities[0]
+ assert isinstance(let.value[0], IsEmptySubstitution)
+ assert let.value[0].perform(context) == 'true'
diff --git a/launch_xml/test/launch_xml/test_not_empty_substitution.py b/launch_xml/test/launch_xml/test_not_empty_substitution.py
new file mode 100644
index 000000000..c44769e44
--- /dev/null
+++ b/launch_xml/test/launch_xml/test_not_empty_substitution.py
@@ -0,0 +1,92 @@
+# Copyright 2026 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.
+
+"""Test parsing a NotEmptySubstitution in an XML launch file."""
+
+import io
+import textwrap
+
+from launch.actions import DeclareLaunchArgument
+from launch.actions import SetLaunchConfiguration
+from launch.frontend import Parser
+from launch.launch_context import LaunchContext
+from launch.substitutions import NotEmptySubstitution
+
+
+def test_nested():
+ xml_file = textwrap.dedent(
+ """
+
+
+
+
+ """
+ )
+ root_entity, parser = Parser.load(io.StringIO(xml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 2
+ assert isinstance(launch_description.entities[0], DeclareLaunchArgument)
+ assert isinstance(launch_description.entities[1], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ launch_description.entities[0].visit(context)
+
+ let = launch_description.entities[1]
+ assert isinstance(let.value[0], NotEmptySubstitution)
+ assert let.value[0].perform(context) == 'true'
+
+
+def test_nested_empty():
+ xml_file = textwrap.dedent(
+ """
+
+
+
+
+ """
+ )
+ root_entity, parser = Parser.load(io.StringIO(xml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 2
+ assert isinstance(launch_description.entities[0], DeclareLaunchArgument)
+ assert isinstance(launch_description.entities[1], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ launch_description.entities[0].visit(context)
+
+ let = launch_description.entities[1]
+ assert isinstance(let.value[0], NotEmptySubstitution)
+ assert let.value[0].perform(context) == 'false'
+
+
+def test_empty_string():
+ xml_file = textwrap.dedent(
+ """
+
+
+
+ """
+ )
+ root_entity, parser = Parser.load(io.StringIO(xml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 1
+ assert isinstance(launch_description.entities[0], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ let = launch_description.entities[0]
+ assert isinstance(let.value[0], NotEmptySubstitution)
+ assert let.value[0].perform(context) == 'false'
diff --git a/launch_yaml/test/launch_yaml/test_is_empty_substitution.py b/launch_yaml/test/launch_yaml/test_is_empty_substitution.py
new file mode 100644
index 000000000..807317ab5
--- /dev/null
+++ b/launch_yaml/test/launch_yaml/test_is_empty_substitution.py
@@ -0,0 +1,92 @@
+# Copyright 2026 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.
+
+"""Test parsing an IsEmptySubstitution in a YAML launch file."""
+
+import io
+
+from launch.actions import DeclareLaunchArgument
+from launch.actions import SetLaunchConfiguration
+from launch.frontend import Parser
+from launch.launch_context import LaunchContext
+from launch.substitutions import IsEmptySubstitution
+
+
+def test_nested():
+ yaml_file = """\
+launch:
+ - arg:
+ name: robot_name
+ default: rover
+ - let:
+ name: is_empty
+ value: $(is-empty $(var robot_name))
+"""
+ root_entity, parser = Parser.load(io.StringIO(yaml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 2
+ assert isinstance(launch_description.entities[0], DeclareLaunchArgument)
+ assert isinstance(launch_description.entities[1], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ launch_description.entities[0].visit(context)
+
+ let = launch_description.entities[1]
+ assert isinstance(let.value[0], IsEmptySubstitution)
+ assert let.value[0].perform(context) == 'false'
+
+
+def test_nested_empty():
+ yaml_file = """\
+launch:
+ - arg:
+ name: robot_name
+ default: ''
+ - let:
+ name: is_empty
+ value: $(is-empty $(var robot_name))
+"""
+ root_entity, parser = Parser.load(io.StringIO(yaml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 2
+ assert isinstance(launch_description.entities[0], DeclareLaunchArgument)
+ assert isinstance(launch_description.entities[1], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ launch_description.entities[0].visit(context)
+
+ let = launch_description.entities[1]
+ assert isinstance(let.value[0], IsEmptySubstitution)
+ assert let.value[0].perform(context) == 'true'
+
+
+def test_empty_string():
+ yaml_file = """\
+launch:
+ - let:
+ name: is_empty
+ value: $(is-empty '')
+"""
+ root_entity, parser = Parser.load(io.StringIO(yaml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 1
+ assert isinstance(launch_description.entities[0], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ let = launch_description.entities[0]
+ assert isinstance(let.value[0], IsEmptySubstitution)
+ assert let.value[0].perform(context) == 'true'
diff --git a/launch_yaml/test/launch_yaml/test_not_empty_substitution.py b/launch_yaml/test/launch_yaml/test_not_empty_substitution.py
new file mode 100644
index 000000000..39eedc015
--- /dev/null
+++ b/launch_yaml/test/launch_yaml/test_not_empty_substitution.py
@@ -0,0 +1,92 @@
+# Copyright 2026 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.
+
+"""Test parsing a NotEmptySubstitution in a YAML launch file."""
+
+import io
+
+from launch.actions import DeclareLaunchArgument
+from launch.actions import SetLaunchConfiguration
+from launch.frontend import Parser
+from launch.launch_context import LaunchContext
+from launch.substitutions import NotEmptySubstitution
+
+
+def test_nested():
+ yaml_file = """\
+launch:
+ - arg:
+ name: robot_name
+ default: rover
+ - let:
+ name: not_empty
+ value: $(not-empty $(var robot_name))
+"""
+ root_entity, parser = Parser.load(io.StringIO(yaml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 2
+ assert isinstance(launch_description.entities[0], DeclareLaunchArgument)
+ assert isinstance(launch_description.entities[1], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ launch_description.entities[0].visit(context)
+
+ let = launch_description.entities[1]
+ assert isinstance(let.value[0], NotEmptySubstitution)
+ assert let.value[0].perform(context) == 'true'
+
+
+def test_nested_empty():
+ yaml_file = """\
+launch:
+ - arg:
+ name: robot_name
+ default: ''
+ - let:
+ name: not_empty
+ value: $(not-empty $(var robot_name))
+"""
+ root_entity, parser = Parser.load(io.StringIO(yaml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 2
+ assert isinstance(launch_description.entities[0], DeclareLaunchArgument)
+ assert isinstance(launch_description.entities[1], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ launch_description.entities[0].visit(context)
+
+ let = launch_description.entities[1]
+ assert isinstance(let.value[0], NotEmptySubstitution)
+ assert let.value[0].perform(context) == 'false'
+
+
+def test_empty_string():
+ yaml_file = """\
+launch:
+ - let:
+ name: not_empty
+ value: $(not-empty '')
+"""
+ root_entity, parser = Parser.load(io.StringIO(yaml_file))
+ launch_description = parser.parse_description(root_entity)
+
+ assert len(launch_description.entities) == 1
+ assert isinstance(launch_description.entities[0], SetLaunchConfiguration)
+
+ context = LaunchContext()
+ let = launch_description.entities[0]
+ assert isinstance(let.value[0], NotEmptySubstitution)
+ assert let.value[0].perform(context) == 'false'