diff --git a/.taskid b/.taskid index 0d88bd5..f362f7c 100644 --- a/.taskid +++ b/.taskid @@ -1 +1 @@ -DEVX-14 +DEVX-15 diff --git a/src/devx/molecule/distribute_molecule.py b/src/devx/molecule/distribute_molecule.py index 4207f44..ee7cb76 100644 --- a/src/devx/molecule/distribute_molecule.py +++ b/src/devx/molecule/distribute_molecule.py @@ -41,7 +41,8 @@ class TestPair: def encode(self) -> str: """Serialize to a pipe-delimited string for CI consumption.""" - return f"{self.scenario}|{self.platform['name']}|{self.platform['image']}|{self.platform['command']}" + cmd = self.platform["command"].replace(" ", "__SPACE__") + return f"{self.scenario}|{self.platform['name']}|{self.platform['image']}|{cmd}" @staticmethod def decode(encoded: str) -> TestPair: @@ -49,7 +50,7 @@ class TestPair: parts = encoded.split("|") return TestPair( scenario=parts[0], - platform={"name": parts[1], "image": parts[2], "command": parts[3]}, + platform={"name": parts[1], "image": parts[2], "command": parts[3].replace("__SPACE__", " ")}, ) @@ -63,9 +64,8 @@ class MultiRoleTestPair: def encode(self) -> str: """Serialize to a pipe-delimited string: ``role|scenario|platform_name|image|command``.""" - return ( - f"{self.role}|{self.scenario}|{self.platform['name']}|{self.platform['image']}|{self.platform['command']}" - ) + cmd = self.platform["command"].replace(" ", "__SPACE__") + return f"{self.role}|{self.scenario}|{self.platform['name']}|{self.platform['image']}|{cmd}" @staticmethod def decode(encoded: str) -> MultiRoleTestPair: @@ -74,7 +74,7 @@ class MultiRoleTestPair: return MultiRoleTestPair( role=parts[0], scenario=parts[1], - platform={"name": parts[2], "image": parts[3], "command": parts[4]}, + platform={"name": parts[2], "image": parts[3], "command": parts[4].replace("__SPACE__", " ")}, ) diff --git a/src/devx/molecule/molecule_ci_guard.py b/src/devx/molecule/molecule_ci_guard.py index 5585e75..31b3b44 100644 --- a/src/devx/molecule/molecule_ci_guard.py +++ b/src/devx/molecule/molecule_ci_guard.py @@ -114,12 +114,14 @@ def parse_pair(pair: str) -> tuple[str, str, str, str, str]: Supports both 4-part (single-role) and 5-part (multi-role) formats. For 4-part pairs, role is empty (caller uses default role dir). + Spaces in the command field are encoded as ``__SPACE__`` to survive + shell word-splitting when ``$TEST_PAIRS`` is expanded unquoted. """ parts = pair.split("|") if len(parts) == 4: - return "", parts[0], parts[1], parts[2], parts[3] + return "", parts[0], parts[1], parts[2], parts[3].replace("__SPACE__", " ") if len(parts) == 5: - return parts[0], parts[1], parts[2], parts[3], parts[4] + return parts[0], parts[1], parts[2], parts[3], parts[4].replace("__SPACE__", " ") raise click.ClickException(f"Invalid pair format: {pair!r} (expected 4 or 5 pipe-delimited parts)")