From b3b4356bc69d012890a8a4ce58aef28cca805177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A0=E8=B6=85=E8=83=9C?= Date: Sun, 13 Sep 2026 10:33:26 +0800 Subject: [PATCH 1/2] Reject invalid executable boolean attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 渠超胜 --- launch/launch/actions/execute_process.py | 5 +++-- launch_xml/test/launch_xml/test_executable.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/launch/launch/actions/execute_process.py b/launch/launch/actions/execute_process.py index 588b66462..4003edd7a 100644 --- a/launch/launch/actions/execute_process.py +++ b/launch/launch/actions/execute_process.py @@ -411,12 +411,13 @@ def parse( kwargs['sigterm_timeout'] = str(sigterm_timeout) if 'shell' not in ignore: - shell = entity.get_attr('shell', data_type=bool, optional=True) + shell = entity.get_attr('shell', data_type=bool, optional=True, can_be_str=False) if shell is not None: kwargs['shell'] = shell if 'emulate_tty' not in ignore: - emulate_tty = entity.get_attr('emulate_tty', data_type=bool, optional=True) + emulate_tty = entity.get_attr( + 'emulate_tty', data_type=bool, optional=True, can_be_str=False) if emulate_tty is not None: kwargs['emulate_tty'] = emulate_tty diff --git a/launch_xml/test/launch_xml/test_executable.py b/launch_xml/test/launch_xml/test_executable.py index 647512842..bf90d6bf9 100644 --- a/launch_xml/test/launch_xml/test_executable.py +++ b/launch_xml/test/launch_xml/test_executable.py @@ -69,6 +69,19 @@ def test_executable_wrong_subtag(): assert 'whats_this' in str(excinfo.value) +@pytest.mark.parametrize('attribute', ['shell', 'emulate_tty']) +def test_executable_rejects_arbitrary_boolean_strings(attribute): + """Reject arbitrary strings for boolean executable attributes.""" + xml_file = textwrap.dedent(f""" + + + + """) + root_entity, parser = load_no_extensions(io.StringIO(xml_file)) + with pytest.raises(TypeError, match=attribute): + parser.parse_description(root_entity) + + def test_executable_on_exit(): xml_file = \ """\ From 272946e45b0afa258a0d22d6e01c0162300f724b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A0=E8=B6=85=E8=83=9C?= Date: Sun, 13 Sep 2026 20:00:37 +0800 Subject: [PATCH 2/2] Expand XML executable boolean coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 渠超胜 --- launch/launch/actions/execute_process.py | 5 +++- launch_xml/test/launch_xml/test_executable.py | 26 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/launch/launch/actions/execute_process.py b/launch/launch/actions/execute_process.py index 4003edd7a..f95687c44 100644 --- a/launch/launch/actions/execute_process.py +++ b/launch/launch/actions/execute_process.py @@ -198,7 +198,8 @@ def __init__( :param: additional_env dictionary of environment variables to be added. If 'env' was None, they are added to the current environment. If not, 'env' is updated with additional_env. - :param: shell if True, a shell is used to execute the cmd + :param: shell if True, a shell is used to execute the cmd. This must be + a boolean value in XML launch files; substitutions are not supported. :param: sigterm_timeout time until shutdown should escalate to SIGTERM, as a string or a list of strings and Substitutions to be resolved at runtime, defaults to the LaunchConfiguration called @@ -211,6 +212,8 @@ def __init__( be overridden with the LaunchConfiguration called 'emulate_tty', the value of which is evaluated as true or false according to :py:func:`evaluate_condition_expression`. + In XML launch files, this attribute must be a boolean value; + substitutions are not supported. Throws :py:exc:`InvalidConditionExpressionError` if the 'emulate_tty' configuration does not represent a boolean. :param: prefix a set of commands/arguments to precede the cmd, used for diff --git a/launch_xml/test/launch_xml/test_executable.py b/launch_xml/test/launch_xml/test_executable.py index bf90d6bf9..16bae1d04 100644 --- a/launch_xml/test/launch_xml/test_executable.py +++ b/launch_xml/test/launch_xml/test_executable.py @@ -69,17 +69,33 @@ def test_executable_wrong_subtag(): assert 'whats_this' in str(excinfo.value) +@pytest.mark.parametrize( + 'value', ['true', 'false', 'True', 'False', 'yes', 'no', 'on', 'off', '1', '0']) @pytest.mark.parametrize('attribute', ['shell', 'emulate_tty']) -def test_executable_rejects_arbitrary_boolean_strings(attribute): - """Reject arbitrary strings for boolean executable attributes.""" +def test_executable_accepts_boolean_values(attribute, value): + """Accept the boolean spellings supported by the XML type converter.""" xml_file = textwrap.dedent(f""" - + """) root_entity, parser = load_no_extensions(io.StringIO(xml_file)) - with pytest.raises(TypeError, match=attribute): - parser.parse_description(root_entity) + executable = parser.parse_description(root_entity).entities[0] + assert getattr(executable, attribute) is (value.lower() in ('true', 'yes', 'on', '1')) + + +@pytest.mark.parametrize('attribute', ['shell', 'emulate_tty']) +def test_executable_rejects_arbitrary_boolean_strings(attribute): + """Reject arbitrary strings for boolean executable attributes.""" + for value in ('', ' ', 'not-a-boolean', '$(var flag)'): + xml_file = textwrap.dedent(f""" + + + + """) + root_entity, parser = load_no_extensions(io.StringIO(xml_file)) + with pytest.raises(TypeError, match=attribute): + parser.parse_description(root_entity) def test_executable_on_exit():