Files
httrack/tests/ttyrun.py
Xavier Roche 507f3565e8 A crawl on a terminal with stdin at end of file toggles its display mode in a loop (#1078)
* Tell an stdin at end of file from a keypress

select() reporting stdin readable does not mean there is anything to read: at
end of file it stays ready forever, and check_stdin() sold that to the crawl
loop as an ENTER. The display then toggled on every pass, so a terminal run
with stdin closed alternated between the spinner and the full panel.

Peek a byte before answering yes, and drop the fflush(stdin) that would have
discarded it (undefined on an input stream anyway). The Windows branch reads
the console queue rather than stdin, so it has no end of file to confuse.

Closes #1072

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Model the ticket's terminal at EOF, not just a redirection

The EOF leg fed httrack /dev/null, so a fix that simply gave up off a
terminal would have passed it while leaving the reported bug alive. Add a
third crawl that types ^D into the pty, which is the case #1072 describes.

Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 21:18:11 +02:00

48 lines
1.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Run a command with a pty for stdout/stderr and capture what it paints.
Usage: ttyrun.py <capture-file> [--stdin tty|eof] [--send enter|eot] -- <cmd> [args...]
--stdin tty (default) hands the child the pty, --stdin eof gives it /dev/null.
--send types one ENTER, or one ^D to put the terminal at end of file, as soon
as the child paints.
"""
import os
import subprocess
import sys
import termios
capture = sys.argv[1]
opts = sys.argv[2 : sys.argv.index("--")]
argv = sys.argv[sys.argv.index("--") + 1 :]
stdin_mode = opts[opts.index("--stdin") + 1] if "--stdin" in opts else "tty"
keys = {"enter": b"\n", "eot": b"\004"}
send = keys[opts[opts.index("--send") + 1]] if "--send" in opts else None
master, slave = os.openpty()
attrs = termios.tcgetattr(slave)
attrs[1] &= ~termios.ONLCR # or the pty manufactures the CRs a spinner check counts
termios.tcsetattr(slave, termios.TCSANOW, attrs)
devnull = os.open(os.devnull, os.O_RDONLY) if stdin_mode == "eof" else None
p = subprocess.Popen(
argv, stdin=slave if devnull is None else devnull, stdout=slave, stderr=slave
)
os.close(slave)
if devnull is not None:
os.close(devnull)
with open(capture, "wb") as f:
while True:
try:
data = os.read(master, 4096)
except OSError: # the slave closed: EIO on Linux, empty read on BSD
break
if not data:
break
f.write(data)
if send:
os.write(master, send)
send = None
os.close(master)
sys.exit(p.wait())