test: cover the key markers and the keys line

Pins the markers that used to be unreachable (s-tab, home, bs, pgup, f5, a
repeat, a literal `<`), the two shapes of a wrong marker (unknown name, bad
repeat) coming back as an error from the tool instead of being typed, and the
pretty line naming the keys it sent.
This commit is contained in:
2026-10-24 02:03:40 +08:00
parent a2d8159267
commit dbb68e7273
2 changed files with 48 additions and 9 deletions
+3 -3
View File
@@ -806,13 +806,13 @@ async def test_pretty_pty_write_and_keys(capsys: pytest.CaptureFixture[str]) ->
await render_events( await render_events(
_aiter( _aiter(
[ [
_pretty_call("write_pty_keys", '{"session_id": 3, "data": "ctrl+c"}'), _pretty_call("write_pty_keys", '{"session_id": 3, "data": "<c-c><enter>"}'),
_result("session_id=3\nalive=true\nexit_code=\nwrote=1"), _result("session_id=3\nalive=true\nexit_code=\nwrote=2"),
] ]
) )
) )
out = capsys.readouterr().out out = capsys.readouterr().out
assert "* PTY Keys 3 (1 char)" in out assert "* PTY Keys 3 '<c-c><enter>' (2 chars)" in out
async def test_pretty_ask_into_pty_never_shows_the_answer(capsys: pytest.CaptureFixture[str]) -> None: async def test_pretty_ask_into_pty_never_shows_the_answer(capsys: pytest.CaptureFixture[str]) -> None:
+45 -6
View File
@@ -4,6 +4,8 @@ import asyncio
import sys import sys
from pathlib import Path from pathlib import Path
import pytest
from plyngent.tools.process import ( from plyngent.tools.process import (
ask_into_pty, ask_into_pty,
close_pty, close_pty,
@@ -514,13 +516,37 @@ def test_decode_write_data_escapes() -> None:
from plyngent.tools.process.pty_terminal import decode_write_data from plyngent.tools.process.pty_terminal import decode_write_data
assert decode_write_data(r"\x0f") == "\x0f" assert decode_write_data(r"\x0f") == "\x0f"
assert decode_write_data("ctrl+x") == "\x18"
assert decode_write_data("CTRL+O") == "\x0f"
assert decode_write_data(r"\e") == "\x1b" assert decode_write_data(r"\e") == "\x1b"
assert decode_write_data("key=esc") == "\x1b"
assert decode_write_data("key=enter") == "\r"
assert decode_write_data(r"a\nb") == "a\nb" assert decode_write_data(r"a\nb") == "a\nb"
assert decode_write_data("plain") == "plain" assert decode_write_data("plain") == "plain"
# Markers: names, modifiers, repeats, and a literal `<`.
assert decode_write_data("<esc>") == "\x1b"
assert decode_write_data("<ESC>") == "\x1b"
assert decode_write_data("<enter>") == "\r"
assert decode_write_data("<s-tab>") == "\x1b[Z"
assert decode_write_data("<c-x>") == "\x18"
assert decode_write_data("<C-O>") == "\x0f"
assert decode_write_data("<c-[>") == "\x1b"
assert decode_write_data("<m-b>") == "\x1bb"
assert decode_write_data("<m-up>") == "\x1b\x1b[A"
assert decode_write_data("<up*3>") == "\x1b[A" * 3
assert decode_write_data("<<value>") == "<value>"
assert decode_write_data("<home>") == "\x1b[H"
assert decode_write_data("<bs>") == "\x7f"
assert decode_write_data("<pgup>") == "\x1b[5~"
assert decode_write_data("<f5>") == "\x1b[15~"
# A `<` that does not open a marker stays literal, spaces included.
assert decode_write_data("sort < file") == "sort < file"
assert decode_write_data("a < b > c") == "a < b > c"
def test_decode_write_data_refuses_an_unknown_key() -> None:
"""A typo must be an error, not text typed into the terminal."""
from plyngent.tools.process.pty_terminal import KeySyntaxError, decode_write_data
for data in ("<hme>", "<s-up>", "<c-1>", "<up*x>", "<up*0>", "<c->"):
with pytest.raises(KeySyntaxError):
_ = decode_write_data(data)
def test_sanitize_pty_output_escapes_csi() -> None: def test_sanitize_pty_output_escapes_csi() -> None:
@@ -567,7 +593,7 @@ async def test_write_pty_keys_ctrl_escape(workspace: object) -> None:
), ),
) )
session_id = _session_id(opened) session_id = _session_id(opened)
written = await call_async(write_pty_keys, session_id, "ctrl+c") written = await call_async(write_pty_keys, session_id, "<c-c>")
assert "wrote=1" in written assert "wrote=1" in written
text = await call_async(read_pty, session_id, timeout=2.0) text = await call_async(read_pty, session_id, timeout=2.0)
assert "error" not in text.lower() or "alive=" in text assert "error" not in text.lower() or "alive=" in text
@@ -576,13 +602,26 @@ async def test_write_pty_keys_ctrl_escape(workspace: object) -> None:
PtyManager.close_all() PtyManager.close_all()
async def test_write_pty_keys_refuses_an_unknown_marker(workspace: object) -> None:
"""The typo comes back as an error and never reaches the session."""
del workspace
try:
opened = await call_async(open_pty, _py("print('up')"))
session_id = _session_id(opened)
result = await call_async(write_pty_keys, session_id, "<hme>")
assert result.startswith("error: unknown key <hme>")
_ = await call_async(close_pty, session_id)
finally:
PtyManager.close_all()
def test_write_pty_is_literal() -> None: def test_write_pty_is_literal() -> None:
"""Plain write_pty must not call the keys decoder.""" """Plain write_pty must not call the keys decoder."""
import inspect import inspect
from plyngent.tools.process.pty_terminal import decode_write_data from plyngent.tools.process.pty_terminal import decode_write_data
assert decode_write_data("press ctrl+c to cancel") == "press " + chr(3) + " to cancel" assert decode_write_data("press <c-c> to cancel") == "press " + chr(3) + " to cancel"
src = inspect.getsource(write_pty.handler) src = inspect.getsource(write_pty.handler)
assert "decode_write_data" not in src assert "decode_write_data" not in src
doc = write_pty.description or write_pty.handler.__doc__ or "" doc = write_pty.description or write_pty.handler.__doc__ or ""