# BST-TIMEOUT: 300 # every session case waits on a real pty round-trip # # screen 4.9.1 — the terminal multiplexer, driven entirely without a keyboard. # # -dmS starts a session detached, -X sends it a command as if typed, and # hardcopy writes the window's rendered screen to a file. That triple is what # makes screen scriptable, and it is also what makes this suite worth having: # every expectation below was taken from a native x86_64 build of the SAME # 4.9.1 tarball and dry-run against it before any target build. # # The interesting half is not the CLI. -dmS allocates a pty, forks a shell onto # its slave, and drives it through termios; hardcopy then reads back what # screen's own terminal emulator painted. A case that gets from `stuff` to a # marker in the hardcopy has exercised openpty/TIOCSCTTY/termios and the # emulator end to end, which is the part of this port most likely to meet a # platform gap rather than a packaging one. . "$BST_LIB" require screen screen/present SD=${TMPDIR:-/tmp}/bst-screen.$$ HC=$SD/hardcopy.txt LOGF=$SD/window.log SESS=bstscr$$ rm -rf "$SD" mkdir -p "$SD" 2>/dev/null chmod 700 "$SD" 2>/dev/null # screen refuses a socket directory it does not own or that is group/world # writable, and it takes the path from SCREENDIR. Point it somewhere writable # rather than inheriting /run/screen, which need not exist on this image. SCREENDIR=$SD; export SCREENDIR TERM=${TERM:-xterm}; export TERM # hardcopy the live window and wait for to appear in it, bounded. # Set _hc_win to address one window of a multi-window session. # # The file is cleared ONCE, before the loop, and never between issuing a hardcopy # and reading it. -X is fire-and-forget -- the client exits as soon as the message # is written and the session writes the file whenever it next runs -- so a rm # inside the loop deletes the very write it is waiting for, and the case then # fails while the marker is sitting in the file it just removed. Re-issuing # hardcopy each round is safe because screen truncates and rewrites. # # Polling beats a fixed sleep here: the guest is far slower than the host that # set these expectations, and a fixed delay would either flake or waste the run. _hc_wait() { _needle=$1; _tries=$2 rm -f "$HC" while [ "$_tries" -gt 0 ]; do screen -S "$SESS" ${_hc_win:+-p "$_hc_win"} -X hardcopy "$HC" >/dev/null 2>&1 sleep 1 if [ -f "$HC" ] && grep -q "$_needle" "$HC" 2>/dev/null; then return 0; fi _tries=$((_tries - 1)) done return 1 } # --- CLI surface, no session required --------------------------------------- check_out screen/version "Screen version 4.09.01" screen --version # -ls with nothing running is an ERROR exit by design, not a quiet success: it # is how a script asks "is anything up?" and gets a usable answer. out=$(screen -ls 2>&1); rc=$? if [ "$rc" -eq 1 ]; then bst_ok screen/ls-empty-rc else bst_notok screen/ls-empty-rc "want rc=1 got rc=$rc $(_bst_1line "$out")"; fi case "$out" in *"No Sockets found"*) bst_ok screen/ls-empty-text ;; *) bst_notok screen/ls-empty-text "$(_bst_1line "$out")" ;; esac # SCREENDIR is what the rest of this suite depends on being honoured; assert it # rather than assume it, or every later red would be ambiguous. case "$out" in *"$SD"*) bst_ok screen/screendir-honoured ;; *) bst_notok screen/screendir-honoured "socket dir not $SD -- $(_bst_1line "$out")" ;; esac out=$(screen -S nosuchsession -X stuff hi 2>&1); rc=$? if [ "$rc" -ne 0 ]; then bst_ok screen/absent-session-rc else bst_notok screen/absent-session-rc "want non-zero, got 0"; fi case "$out" in *"No screen session found"*) bst_ok screen/absent-session-text ;; *) bst_notok screen/absent-session-text "$(_bst_1line "$out")" ;; esac # --- session lifecycle: the pty half ---------------------------------------- check screen/dm-start screen -dmS "$SESS" /bin/sh # The session is created asynchronously; wait for it rather than racing it. tries=15 while [ "$tries" -gt 0 ]; do screen -ls 2>&1 | grep -q "$SESS" && break tries=$((tries - 1)); sleep 1 done out=$(screen -ls 2>&1) case "$out" in *"$SESS"*) bst_ok screen/ls-names-session ;; *) bst_notok screen/ls-names-session "$(_bst_1line "$out")" ;; esac case "$out" in *"(Detached)"*) bst_ok screen/ls-detached ;; *) bst_notok screen/ls-detached "$(_bst_1line "$out")" ;; esac # -Q asks the running session a question and prints the answer on the CALLER's # stdout -- the query channel, distinct from -X's fire-and-forget command. check_out screen/query-windows "sh" screen -S "$SESS" -Q windows # --- stuff -> hardcopy: the end-to-end pty round trip ------------------------ check screen/stuff-rc screen -S "$SESS" -X stuff 'echo BST-SCREEN-MARKER ' if _hc_wait BST-SCREEN-MARKER 30; then bst_ok screen/hardcopy-write bst_ok screen/stuff-executed else # Separate the two failures the one wait can mean: nothing written at all # (hardcopy itself broken) vs. written but never carrying the marker (the # pty round trip never completed). The re-issue gets its own bounded wait -- # reading it the instant the client returns would only re-measure -X's # asynchrony, and would call every slow hardcopy a missing one. rm -f "$HC" screen -S "$SESS" -X hardcopy "$HC" >/dev/null 2>&1 tries=10 while [ "$tries" -gt 0 ] && [ ! -s "$HC" ]; do tries=$((tries - 1)); sleep 1; done if [ -s "$HC" ]; then bst_ok screen/hardcopy-write bst_notok screen/stuff-executed "hardcopy written but no marker after 30s -- $(_bst_1line "$(cat "$HC" 2>&1)")" else bst_notok screen/hardcopy-write "no hardcopy produced at $HC" bst_notok screen/stuff-executed "no hardcopy to read the marker from" fi fi # The marker must appear TWICE: once as the echoed input line and once as the # shell's output. One occurrence means the emulator painted the typing but the # shell never ran it. n=$(grep -c BST-SCREEN-MARKER "$HC" 2>/dev/null || echo 0) if [ "${n:-0}" -ge 2 ]; then bst_ok screen/shell-ran-command else bst_notok screen/shell-ran-command "marker appears $n time(s), want >=2"; fi # --- window management ------------------------------------------------------ check screen/title-set screen -S "$SESS" -X title bstwin check_out screen/title-query "bstwin" screen -S "$SESS" -Q title check screen/new-window screen -S "$SESS" -X screen /bin/sh tries=15 while [ "$tries" -gt 0 ]; do screen -S "$SESS" -Q windows 2>&1 | grep -q "1 " && break tries=$((tries - 1)); sleep 1 done out=$(screen -S "$SESS" -Q windows 2>&1) case "$out" in *bstwin*) bst_ok screen/windows-keeps-title ;; *) bst_notok screen/windows-keeps-title "$(_bst_1line "$out")" ;; esac case "$out" in *"1 "*) bst_ok screen/windows-two ;; *) bst_notok screen/windows-two "second window not listed -- $(_bst_1line "$out")" ;; esac # -X screen also SELECTS what it created, so the session's current window is now # the new one. check_out screen/number-after-new "1 (" screen -S "$SESS" -Q number # With no display attached there is nothing for `select` to switch -- it returns # 0 and leaves -Q number where it was. The scripted way to address one window of # many is -p, and that it resolves each window to its own title is what makes a # multi-window session drivable with no keyboard anywhere. check_out screen/window0-by-p "bstwin" screen -S "$SESS" -p 0 -Q title check_out screen/window1-by-p "sh" screen -S "$SESS" -p 1 -Q title # -p addresses the PTY, not just the bookkeeping: type into window 0 while # window 1 is the current one, and read it back out of window 0's own hardcopy. screen -S "$SESS" -p 0 -X stuff 'echo BST-SCREEN-W0 ' >/dev/null 2>&1 _hc_win=0 if _hc_wait BST-SCREEN-W0 25; then bst_ok screen/per-window-stuff else bst_notok screen/per-window-stuff "no BST-SCREEN-W0 in window 0 hardcopy after 25s" fi _hc_win= # --- per-window logging: screen writing the pty back out to a file ---------- check screen/logfile-set screen -S "$SESS" -X logfile "$LOGF" check screen/log-on screen -S "$SESS" -X log on screen -S "$SESS" -X stuff 'echo BST-SCREEN-LOGGED ' >/dev/null 2>&1 tries=20 while [ "$tries" -gt 0 ]; do [ -f "$LOGF" ] && grep -q BST-SCREEN-LOGGED "$LOGF" 2>/dev/null && break tries=$((tries - 1)); sleep 1 done if [ -f "$LOGF" ] && grep -q BST-SCREEN-LOGGED "$LOGF" 2>/dev/null; then bst_ok screen/log-captures-output else bst_notok screen/log-captures-output "no BST-SCREEN-LOGGED in $LOGF after 20s" fi # --- teardown --------------------------------------------------------------- check screen/quit screen -S "$SESS" -X quit tries=15 while [ "$tries" -gt 0 ]; do screen -ls 2>&1 | grep -q "$SESS" || break tries=$((tries - 1)); sleep 1 done out=$(screen -ls 2>&1) case "$out" in *"$SESS"*) bst_notok screen/quit-removes-session "$(_bst_1line "$out")" ;; *) bst_ok screen/quit-removes-session ;; esac screen -S "$SESS" -X quit >/dev/null 2>&1 rm -rf "$SD"