Skip to content

Commit c52fb97

Browse files
committed
fix: strip end-of-line whitespace from wait-for needles (codeplain-tty)
The transcript is rendered per line with trailing whitespace stripped, so a needle quoting a prompt verbatim — 'Master password: ' — could never match as written; every such wait burned its full timeout and stacked past the script budget. Deterministically reproduced from the cli-password-manager failure in the pty-with-codeplain-tty benchmark run (17 of the render's conformance failures were 120-second timeouts): the generated tests drove the CLI through codeplain-tty exactly as instructed, and hung only on the trailing space in 'wait-for "Master password: "'. The broker now strips end-of-line whitespace from wait-for and wait-until-absent needles before matching; a needle that is empty after the normalization is a usage error.
1 parent 7be2136 commit c52fb97

2 files changed

Lines changed: 34 additions & 2 deletions

File tree

render_machine/tty_broker.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,12 @@ def _text_arg(args: dict, key: str = "text") -> str:
239239
return value
240240

241241
def _wait(self, args: dict, present: bool) -> dict:
242-
text = self._text_arg(args)
242+
# The transcript is rendered per line with trailing whitespace stripped, so a
243+
# needle quoting a prompt verbatim — "Master password: " — could never match as
244+
# written. End-of-line whitespace is stripped from the needle to compensate.
245+
text = "\n".join(part.rstrip() for part in self._text_arg(args).split("\n"))
243246
if not text:
244-
raise ValueError("the text to wait for must not be empty")
247+
raise ValueError("the text to wait for must not be empty or whitespace-only")
245248
timeout = args.get("timeout", DEFAULT_WAIT_SECONDS)
246249
if not isinstance(timeout, (int, float)) or isinstance(timeout, bool) or timeout <= 0:
247250
raise ValueError("'timeout' must be a positive number of seconds")

tests/test_tty_broker.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,35 @@ def appear_later():
128128
assert response["ok"] is True
129129

130130

131+
def test_wait_for_matches_a_prompt_despite_trailing_whitespace(broker):
132+
"""The transcript is rendered per line with trailing whitespace stripped, so a
133+
needle quoting a prompt verbatim — 'Master password: ' — could never match as
134+
written. The broker strips end-of-line whitespace from the needle to compensate."""
135+
instance, process = broker
136+
process.transcript = "Master password:"
137+
response = command(instance, "wait-for", {"text": "Master password: ", "timeout": 2})
138+
assert response["ok"] is True
139+
140+
141+
def test_wait_until_absent_applies_the_same_needle_normalization(broker):
142+
instance, process = broker
143+
process.transcript = "spinner"
144+
145+
def clear_later():
146+
time.sleep(0.2)
147+
process.transcript = "done"
148+
149+
threading.Thread(target=clear_later, daemon=True).start()
150+
response = command(instance, "wait-until-absent", {"text": "spinner ", "timeout": 5})
151+
assert response["ok"] is True
152+
153+
154+
def test_a_whitespace_only_wait_needle_is_a_usage_error(broker):
155+
instance, _ = broker
156+
response = command(instance, "wait-for", {"text": " ", "timeout": 2})
157+
assert response["error"] == tty_protocol.ERROR_INVALID_REQUEST
158+
159+
131160
def test_wait_for_times_out_with_the_timeout_error(broker):
132161
instance, _ = broker
133162
response = command(instance, "wait-for", {"text": "never", "timeout": 0.2})

0 commit comments

Comments
 (0)