Skip to content

Commit 22e29f0

Browse files
committed
Embed the terminal probe so it cannot arrive malformed
The probe reached the runner with an indent no longer shared by every line, so dedent removed nothing and the target died on line 2 before it could report anything. The source is provably correct in the blob, so rather than explain the transformation the program now sits at column zero and carries no escape sequence at all: neither dedent nor an escape resolved too early has anything left to act on. write_program compiles what it is about to write, so a malformed program fails the test at the write with its own text and line, and the probe case reports the first lines actually on disk beside the transcript and the literal.
1 parent 774ac63 commit 22e29f0

1 file changed

Lines changed: 59 additions & 42 deletions

File tree

tests/test_conpty.py

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,16 @@
6969

7070

7171
def write_program(tmp_path: Path, name: str, source: str) -> str:
72+
"""Writes a probe program, and refuses to write one that will not parse.
73+
74+
A target that dies on a parse error reports a bare non-zero exit code and whatever the
75+
terminal happened to catch, which is the least useful evidence available. Compiling here,
76+
while the text is still in hand, fails the test at the write with the source and the line.
77+
"""
7278
path = tmp_path / f"{name}.py"
73-
path.write_text(textwrap.dedent(source), encoding="utf-8")
79+
program = textwrap.dedent(source)
80+
compile(program, str(path), "exec")
81+
path.write_text(program, encoding="utf-8")
7482
return str(path)
7583

7684

@@ -236,51 +244,56 @@ def backend():
236244

237245
# The probe reports what a script sees, one short line at a time: the pseudoconsole wraps at
238246
# the configured width, so a single long line would come back folded.
247+
# Written at column zero and without a single escape sequence: the target parses this file
248+
# on its own, and the two ways a program embedded in a test can arrive malformed — an indent
249+
# no longer shared by every line, and an escape the test source resolves too early — are both
250+
# absent by construction rather than by review.
239251
TERMINAL_PROBE = """
240-
import ctypes
241-
import os
242-
import traceback
243-
244-
245-
def report():
246-
# Declared: an undeclared call returns c_int, which truncates a handle and reports a
247-
# false negative for both questions below.
248-
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
249-
kernel32.GetStdHandle.argtypes = [ctypes.c_uint]
250-
kernel32.GetStdHandle.restype = ctypes.c_void_p
251-
kernel32.GetCurrentProcess.argtypes = []
252-
kernel32.GetCurrentProcess.restype = ctypes.c_void_p
253-
kernel32.GetConsoleMode.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint)]
254-
kernel32.GetConsoleMode.restype = ctypes.c_int
255-
kernel32.IsProcessInJob.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_int)]
256-
kernel32.IsProcessInJob.restype = ctypes.c_int
257-
258-
mode = ctypes.c_uint(0)
259-
console = kernel32.GetConsoleMode(kernel32.GetStdHandle(0xFFFFFFF5), ctypes.byref(mode))
260-
in_job = ctypes.c_int(0)
261-
kernel32.IsProcessInJob(kernel32.GetCurrentProcess(), None, ctypes.byref(in_job))
262-
return [
263-
"ISATTY=%s" % (os.isatty(0) and os.isatty(1) and os.isatty(2)),
264-
"CONSOLE=%s" % bool(console),
265-
"INJOB=%s" % bool(in_job.value),
266-
"TERM=%s" % os.environ.get("TERM"),
267-
"DONE",
268-
]
252+
import ctypes
253+
import os
254+
import traceback
269255
270256
271-
try:
272-
lines = report()
273-
except BaseException:
274-
lines = ["PROBE-FAILED", traceback.format_exc()]
275-
276-
# Written beside the probe as well as printed: the file survives a target whose standard
277-
# handles are not the terminal's, and it needs no argument that could itself go wrong.
278-
beside_the_probe = os.path.join(os.path.dirname(os.path.abspath(__file__)), "probe.txt")
279-
with open(beside_the_probe, "w", encoding="utf-8") as handle:
280-
handle.write("\n".join(lines))
257+
def report():
258+
# Declared: an undeclared call returns c_int, which truncates a handle and reports a
259+
# false negative for both questions below.
260+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
261+
kernel32.GetStdHandle.argtypes = [ctypes.c_uint]
262+
kernel32.GetStdHandle.restype = ctypes.c_void_p
263+
kernel32.GetCurrentProcess.argtypes = []
264+
kernel32.GetCurrentProcess.restype = ctypes.c_void_p
265+
kernel32.GetConsoleMode.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint)]
266+
kernel32.GetConsoleMode.restype = ctypes.c_int
267+
kernel32.IsProcessInJob.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_int)]
268+
kernel32.IsProcessInJob.restype = ctypes.c_int
281269
270+
mode = ctypes.c_uint(0)
271+
console = kernel32.GetConsoleMode(kernel32.GetStdHandle(0xFFFFFFF5), ctypes.byref(mode))
272+
in_job = ctypes.c_int(0)
273+
kernel32.IsProcessInJob(kernel32.GetCurrentProcess(), None, ctypes.byref(in_job))
274+
return [
275+
"ISATTY=%s" % (os.isatty(0) and os.isatty(1) and os.isatty(2)),
276+
"CONSOLE=%s" % bool(console),
277+
"INJOB=%s" % bool(in_job.value),
278+
"TERM=%s" % os.environ.get("TERM"),
279+
"DONE",
280+
]
281+
282+
283+
try:
284+
lines = report()
285+
except BaseException:
286+
lines = ["PROBE-FAILED"] + traceback.format_exc().splitlines()
287+
288+
# Written beside the probe as well as printed: the file survives a target whose standard
289+
# handles are not the terminal's, and it needs no argument that could itself go wrong.
290+
beside_the_probe = os.path.join(os.path.dirname(os.path.abspath(__file__)), "probe.txt")
291+
with open(beside_the_probe, "w", encoding="utf-8") as handle:
282292
for line in lines:
283-
print(line)
293+
print(line, file=handle)
294+
295+
for line in lines:
296+
print(line)
284297
"""
285298

286299

@@ -389,7 +402,11 @@ def test_a_script_runs_on_a_real_console_inside_the_job(backend, tmp_path):
389402
backend.close()
390403
output = backend.normalized_output()
391404
report = report_path.read_text(encoding="utf-8") if report_path.exists() else "(no report was written)"
392-
evidence = f"transcript={output!r}; the target reported:\n{report}"
405+
written = "".join(Path(script).read_text(encoding="utf-8").splitlines(keepends=True)[:5])
406+
evidence = (
407+
f"transcript={output!r}\nthe target reported:\n{report}\n"
408+
f"first lines written={written!r}\nliteral={TERMINAL_PROBE[:80]!r}"
409+
)
393410

394411
assert exit_code == 0, evidence
395412
assert "ISATTY=True" in output, evidence

0 commit comments

Comments
 (0)