From 44bfd8360a4397b9cf45deed8cfd2b9efb974f49 Mon Sep 17 00:00:00 2001 From: jsekar Date: Mon, 31 Aug 2026 14:10:40 +0530 Subject: [PATCH] feature/LCHIB-777: remove stop command and escape content from :: --- launchable/commands/gate.py | 10 +++++----- tests/commands/test_gate.py | 29 +++++++++-------------------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/launchable/commands/gate.py b/launchable/commands/gate.py index d17aa6dbe..8fea7e925 100644 --- a/launchable/commands/gate.py +++ b/launchable/commands/gate.py @@ -1,7 +1,6 @@ import json import os import sys -import uuid from http import HTTPStatus import click @@ -87,6 +86,10 @@ def _escape_github_actions_command_value(value: str) -> str: return value.replace('\r', '%0D').replace('\n', '%0A') +def _escape_github_actions_log_line(line: str) -> str: + return line.replace('::', '%3A%3A', 1) if line.startswith('::') else line + + def display_as_json(res: Response): res_json = res.json() click.echo(json.dumps(res_json, indent=2)) @@ -115,13 +118,10 @@ def display_as_table(res: Response): stderr = (test.get("stderr") or "").strip() if is_github_actions: safe_test_path = _escape_github_actions_command_value(test_path) - token = uuid.uuid4().hex click.echo("::group::{}. {}".format(i, safe_test_path)) - click.echo("::stop-commands::{}".format(token)) if stderr: for line in stderr.splitlines(): - click.echo(line) - click.echo("::{}::".format(token)) + click.echo(_escape_github_actions_log_line(line)) click.echo("::endgroup::") else: click.echo("{}. {}".format(i, test_path)) diff --git a/tests/commands/test_gate.py b/tests/commands/test_gate.py index 33371f651..9c783ffc7 100644 --- a/tests/commands/test_gate.py +++ b/tests/commands/test_gate.py @@ -160,7 +160,6 @@ def test_gate_failed_github_actions_format(self): result = self.cli('gate', '--session', self.session) self.assert_exit_code(result, 1) self.assertIn('::group::1. file=src/FooTest.java#testcase=testBar', result.output) - self.assertIn('::stop-commands::', result.output) self.assertIn('AssertionError: expected true but was false', result.output) self.assertIn('::endgroup::', result.output) @@ -199,25 +198,15 @@ def test_gate_github_actions_stderr_with_command_syntax(self): result = self.cli('gate', '--session', self.session) self.assert_exit_code(result, 1) - # verify all dangerous commands are sandwiched between stop-commands and resume token - stop_idx = result.output.index('::stop-commands::') - # resume token is the line between stop-commands and ::endgroup:: - endgroup_idx = result.output.index('::endgroup::') - - error_idx = result.output.index('::error::some error') - warning_idx = result.output.index('::warning::spoofed') - mask_idx = result.output.index('::add-mask::secret-value') - assertion_idx = result.output.index('java.lang.AssertionError') - - # all stderr content must be after ::stop-commands:: and before ::endgroup:: - self.assertLess(stop_idx, error_idx) - self.assertLess(stop_idx, warning_idx) - self.assertLess(stop_idx, mask_idx) - self.assertLess(stop_idx, assertion_idx) - self.assertLess(error_idx, endgroup_idx) - self.assertLess(warning_idx, endgroup_idx) - self.assertLess(mask_idx, endgroup_idx) - self.assertLess(assertion_idx, endgroup_idx) + # dangerous :: lines are escaped so GHA won't interpret them as commands + self.assertIn('%3A%3Aerror::some error', result.output) + self.assertIn('%3A%3Awarning::spoofed', result.output) + self.assertIn('%3A%3Aadd-mask::secret-value', result.output) + self.assertIn('%3A%3Aset-output name=x::y', result.output) + + # normal lines are untouched + self.assertIn('java.lang.AssertionError', result.output) + self.assertIn('::endgroup::', result.output) @responses.activate @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token})