|
| 1 | +#!/bin/bash |
| 2 | +# Quit-path launcher for UI smoke tests. |
| 3 | +# Usage: quit-launch.sh <sim-config-ini> <gui-process-match> |
| 4 | +# |
| 5 | +# Boots linuxcnc + GUI under xvfb-run exactly like launch.sh, waits for |
| 6 | +# the NML task to come up (via drive.py), then sends SIGTERM to the GUI |
| 7 | +# process *alone* and asserts the GUI exits on its own within a short |
| 8 | +# grace. This is the regression guard for the SIGTERM clean-shutdown |
| 9 | +# handlers: a GUI that absorbs SIGTERM and has to be SIGKILLed fails. |
| 10 | +# |
| 11 | +# <gui-process-match> is a pgrep -f pattern identifying the GUI process |
| 12 | +# (e.g. "bin/touchy", "bin/gmoccapy"). It must not match the linuxcnc |
| 13 | +# launcher or task/motion helpers. |
| 14 | +# |
| 15 | +# Markers (consumed by checkresult-quit.sh): |
| 16 | +# UI_SMOKE_QUIT_OK GUI exited on SIGTERM within QUIT_GRACE |
| 17 | +# UI_SMOKE_QUIT_FAIL GUI never started, was not found, or ignored TERM |
| 18 | + |
| 19 | +set -u |
| 20 | + |
| 21 | +CONFIG_INI="$1" |
| 22 | +GUI_MATCH="$2" |
| 23 | +TEST_DIR="${TEST_DIR:-$(cd "$(dirname "$0")" && pwd)}" |
| 24 | +LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 25 | + |
| 26 | +cd "$TEST_DIR" || exit 1 |
| 27 | +rm -f ui-smoke.out ui-smoke.err linuxcnc.pid |
| 28 | + |
| 29 | +bash "$LIB_DIR/cleanup-runtime.sh" |
| 30 | + |
| 31 | +LINUXCNC_TIMEOUT=240 |
| 32 | +DRIVER_TIMEOUT=90 |
| 33 | +# Seconds to wait for the GUI to exit after SIGTERM before declaring it |
| 34 | +# stuck. A GUI honouring SIGTERM exits in well under a second; the |
| 35 | +# margin covers Cleanup of task/motion on slow CI. |
| 36 | +QUIT_GRACE=15 |
| 37 | + |
| 38 | +# Shared headless environment (software GL + audio silencing), kept in |
| 39 | +# launch-env.sh so launch.sh and quit-launch.sh cannot drift apart. |
| 40 | +. "$LIB_DIR/launch-env.sh" |
| 41 | + |
| 42 | +export CONFIG_INI LIB_DIR DRIVER_TIMEOUT GUI_MATCH QUIT_GRACE |
| 43 | + |
| 44 | +# shellcheck disable=SC2016 |
| 45 | +xvfb-run -a --server-args="-screen 0 1024x768x24" \ |
| 46 | + timeout "$LINUXCNC_TIMEOUT" \ |
| 47 | + bash -c ' |
| 48 | + setsid linuxcnc -r "$CONFIG_INI" >linuxcnc.out 2>linuxcnc.err & |
| 49 | + LINUXCNC_PID=$! |
| 50 | + echo "$LINUXCNC_PID" >linuxcnc.pid |
| 51 | +
|
| 52 | + # Wait until the task is reachable (GUI has constructed and the |
| 53 | + # NML round-trip works). Reuse the phase-1 driver for readiness. |
| 54 | + timeout "$DRIVER_TIMEOUT" python3 "$LIB_DIR/drive.py" >ui-smoke.out 2>ui-smoke.err |
| 55 | + if ! grep -q "^UI_SMOKE_OK$" ui-smoke.out; then |
| 56 | + echo "UI_SMOKE_QUIT_FAIL: GUI did not come up; cannot test quit" |
| 57 | + kill -KILL -- -"$LINUXCNC_PID" 2>/dev/null || true |
| 58 | + bash "$LIB_DIR/cleanup-runtime.sh" |
| 59 | + exit 1 |
| 60 | + fi |
| 61 | +
|
| 62 | + # Identify the GUI process. pgrep -f matches against the whole |
| 63 | + # command line, so wrapper processes (the linuxcnc launcher, the |
| 64 | + # xvfb-run shell, this bash -c) also match because the GUI name |
| 65 | + # appears in the config path or the embedded script text. Every |
| 66 | + # such wrapper has a shell or xvfb-run as argv[0]; the real GUI |
| 67 | + # is a python interpreter. Pick the first match whose argv[0] |
| 68 | + # basename is a python binary. |
| 69 | + GUI_PID="" |
| 70 | + for p in $(pgrep -f "$GUI_MATCH"); do |
| 71 | + arg0=$(tr "\0" "\n" <"/proc/$p/cmdline" 2>/dev/null | head -1) |
| 72 | + case "$(basename "$arg0" 2>/dev/null)" in |
| 73 | + python*) GUI_PID="$p"; break ;; |
| 74 | + esac |
| 75 | + done |
| 76 | + if [ -z "$GUI_PID" ]; then |
| 77 | + echo "UI_SMOKE_QUIT_FAIL: GUI process matching \"$GUI_MATCH\" not found" |
| 78 | + kill -KILL -- -"$LINUXCNC_PID" 2>/dev/null || true |
| 79 | + bash "$LIB_DIR/cleanup-runtime.sh" |
| 80 | + exit 1 |
| 81 | + fi |
| 82 | +
|
| 83 | + # Send SIGTERM to the GUI alone and time how long it takes to go. |
| 84 | + kill -TERM "$GUI_PID" 2>/dev/null || true |
| 85 | + waited=0 |
| 86 | + while [ "$waited" -lt "$QUIT_GRACE" ]; do |
| 87 | + kill -0 "$GUI_PID" 2>/dev/null || break |
| 88 | + sleep 1 |
| 89 | + waited=$((waited + 1)) |
| 90 | + done |
| 91 | +
|
| 92 | + if kill -0 "$GUI_PID" 2>/dev/null; then |
| 93 | + echo "UI_SMOKE_QUIT_FAIL: GUI (pid $GUI_PID) still alive ${QUIT_GRACE}s after SIGTERM" |
| 94 | + RC=1 |
| 95 | + else |
| 96 | + echo "UI_SMOKE_QUIT_OK: GUI exited ${waited}s after SIGTERM" |
| 97 | + RC=0 |
| 98 | + fi |
| 99 | +
|
| 100 | + # Tear down whatever is left (task/motion, or the GUI on failure). |
| 101 | + kill -TERM -- -"$LINUXCNC_PID" 2>/dev/null || true |
| 102 | + for _ in $(seq 30); do |
| 103 | + kill -0 "$LINUXCNC_PID" 2>/dev/null || break |
| 104 | + sleep 1 |
| 105 | + done |
| 106 | + if kill -0 "$LINUXCNC_PID" 2>/dev/null; then |
| 107 | + kill -KILL -- -"$LINUXCNC_PID" 2>/dev/null || true |
| 108 | + sleep 2 |
| 109 | + bash "$LIB_DIR/cleanup-runtime.sh" |
| 110 | + fi |
| 111 | + exit "$RC" |
| 112 | + ' |
| 113 | +RC=$? |
| 114 | + |
| 115 | +echo "=== linuxcnc.err ===" |
| 116 | +[ -f linuxcnc.err ] && cat linuxcnc.err |
| 117 | +echo "=== ui-smoke.out ===" |
| 118 | +[ -f ui-smoke.out ] && cat ui-smoke.out |
| 119 | + |
| 120 | +exit "$RC" |
0 commit comments