|
69 | 69 |
|
70 | 70 |
|
71 | 71 | 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 | + """ |
72 | 78 | 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") |
74 | 82 | return str(path) |
75 | 83 |
|
76 | 84 |
|
@@ -236,51 +244,56 @@ def backend(): |
236 | 244 |
|
237 | 245 | # The probe reports what a script sees, one short line at a time: the pseudoconsole wraps at |
238 | 246 | # 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. |
239 | 251 | 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 |
269 | 255 |
|
270 | 256 |
|
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 |
281 | 269 |
|
| 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: |
282 | 292 | for line in lines: |
283 | | - print(line) |
| 293 | + print(line, file=handle) |
| 294 | +
|
| 295 | +for line in lines: |
| 296 | + print(line) |
284 | 297 | """ |
285 | 298 |
|
286 | 299 |
|
@@ -389,7 +402,11 @@ def test_a_script_runs_on_a_real_console_inside_the_job(backend, tmp_path): |
389 | 402 | backend.close() |
390 | 403 | output = backend.normalized_output() |
391 | 404 | 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 | + ) |
393 | 410 |
|
394 | 411 | assert exit_code == 0, evidence |
395 | 412 | assert "ISATTY=True" in output, evidence |
|
0 commit comments