test/auto-install.sh boots ISO, drives gum installer via QEMU monitor sendkey with hardcoded test answers, waits for anaconda, reboots into installed system, SSHs in, runs validation checklist.
167 lines
6.9 KiB
Bash
Executable file
167 lines
6.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# auto-install-keymap.sh — sourced helper for QEMU-monitor-driven UI automation.
|
|
#
|
|
# Provides a minimal but complete US-layout keymap mapping every printable
|
|
# ASCII character to a QEMU `sendkey` chord, plus convenience wrappers for
|
|
# typing strings, sending special keys, taking screenshots, and waiting for
|
|
# the monitor socket to appear.
|
|
#
|
|
# Usage:
|
|
# source test/auto-install-keymap.sh
|
|
# MONITOR_SOCK=/path/to/sock
|
|
# km_wait_socket "$MONITOR_SOCK" 60
|
|
# km_send_str "$MONITOR_SOCK" "hello world"
|
|
# km_send_key "$MONITOR_SOCK" ret
|
|
# km_send_chord "$MONITOR_SOCK" ctrl alt f1
|
|
# km_screendump "$MONITOR_SOCK" /tmp/shot.ppm
|
|
#
|
|
# Why a separate file: other harnesses (regression suites, fuzzers) can
|
|
# source this without dragging in the full installer test driver.
|
|
|
|
# Guard against double-source.
|
|
[[ -n "${__VEILOR_KEYMAP_LOADED:-}" ]] && return 0
|
|
__VEILOR_KEYMAP_LOADED=1
|
|
|
|
# ── Tool requirements ──────────────────────────────────────────────────
|
|
# socat is the canonical way to talk to a unix-domain QEMU monitor.
|
|
# nc-openbsd would also work but socat is what run-vm.sh already uses.
|
|
km_require_tools() {
|
|
local missing=()
|
|
for t in socat qemu-img qemu-system-x86_64; do
|
|
command -v "$t" >/dev/null 2>&1 || missing+=("$t")
|
|
done
|
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
echo "[ERR] missing required tools: ${missing[*]}" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ── Low-level monitor I/O ──────────────────────────────────────────────
|
|
# Send a single line of monitor input. Newlines are critical — QEMU's HMP
|
|
# parses one command per line. Errors are swallowed: the most common cause
|
|
# is the VM having shut down between two send_* calls, which we tolerate.
|
|
km_monitor_send() {
|
|
local sock=$1; shift
|
|
printf '%s\n' "$*" | socat - "UNIX-CONNECT:$sock" 2>/dev/null || true
|
|
}
|
|
|
|
# Send a raw HMP command and capture any stdout response (e.g. for `info`
|
|
# queries). Trims the QEMU monitor banner + prompt noise.
|
|
km_monitor_query() {
|
|
local sock=$1; shift
|
|
printf '%s\n' "$*" | socat -t 1 - "UNIX-CONNECT:$sock" 2>/dev/null \
|
|
| sed -e 's/\r//g' -e '/^QEMU /d' -e '/^(qemu)/d' || true
|
|
}
|
|
|
|
# Wait until the monitor unix socket exists and accepts connections.
|
|
# $2 = max wait in seconds (default 60).
|
|
km_wait_socket() {
|
|
local sock=$1 max=${2:-60} waited=0
|
|
while (( waited < max )); do
|
|
if [[ -S $sock ]]; then
|
|
# Try a no-op query — confirms the QEMU side is actually serving.
|
|
if printf 'info status\n' | socat -t 1 - "UNIX-CONNECT:$sock" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
fi
|
|
sleep 1
|
|
waited=$((waited + 1))
|
|
done
|
|
echo "[ERR] monitor socket $sock never became ready (waited ${max}s)" >&2
|
|
return 1
|
|
}
|
|
|
|
# ── Screenshots ────────────────────────────────────────────────────────
|
|
# Ask QEMU to dump the current framebuffer. Output is PPM. Convert to PNG
|
|
# with ImageMagick if available; otherwise leave PPM and warn.
|
|
km_screendump() {
|
|
local sock=$1 out=$2
|
|
local ppm="${out%.png}.ppm"
|
|
km_monitor_send "$sock" "screendump $ppm"
|
|
sleep 1 # give QEMU a moment to flush
|
|
if [[ -f $ppm ]] && command -v convert >/dev/null 2>&1; then
|
|
convert "$ppm" "$out" 2>/dev/null && rm -f "$ppm"
|
|
fi
|
|
}
|
|
|
|
# ── Key tables ─────────────────────────────────────────────────────────
|
|
# QEMU `sendkey` reference: docs/system/keys.html.in. The HMP names are
|
|
# the X11 keysym lower-case, with a few exceptions for non-letter keys
|
|
# (spc, ret, minus, etc.). What follows is the full US-layout printable
|
|
# ASCII set. Everything outside this table is silently dropped — callers
|
|
# are responsible for not feeding it characters the installer can't accept
|
|
# anyway (passwords are validated to ASCII-printable in veilor-installer).
|
|
declare -gA __KM_PLAIN=(
|
|
[' ']=spc [a]=a [b]=b [c]=c [d]=d [e]=e [f]=f [g]=g [h]=h
|
|
[i]=i [j]=j [k]=k [l]=l [m]=m [n]=n [o]=o [p]=p [q]=q [r]=r
|
|
[s]=s [t]=t [u]=u [v]=v [w]=w [x]=x [y]=y [z]=z
|
|
[0]=0 [1]=1 [2]=2 [3]=3 [4]=4 [5]=5 [6]=6 [7]=7 [8]=8 [9]=9
|
|
['-']=minus ['=']=equal ['[']=bracket_left [']']=bracket_right
|
|
[';']=semicolon ["'"]=apostrophe [',']=comma ['.']=dot
|
|
['/']=slash ['\\']=backslash ['`']=grave_accent
|
|
)
|
|
|
|
# Shift-prefixed (US): all caps + shifted-symbol row.
|
|
declare -gA __KM_SHIFT=(
|
|
[A]=a [B]=b [C]=c [D]=d [E]=e [F]=f [G]=g [H]=h [I]=i [J]=j
|
|
[K]=k [L]=l [M]=m [N]=n [O]=o [P]=p [Q]=q [R]=r [S]=s [T]=t
|
|
[U]=u [V]=v [W]=w [X]=x [Y]=y [Z]=z
|
|
['!']=1 ['@']=2 ['#']=3 ['$']=4 ['%']=5
|
|
['^']=6 ['&']=7 ['*']=8 ['(']=9 [')']=0
|
|
['_']=minus ['+']=equal ['{']=bracket_left ['}']=bracket_right
|
|
[':']=semicolon ['"']=apostrophe ['<']=comma ['>']=dot
|
|
['?']=slash ['|']=backslash ['~']=grave_accent
|
|
)
|
|
|
|
# ── Public send wrappers ───────────────────────────────────────────────
|
|
# Send a single named key (e.g. ret, esc, up, tab, f1).
|
|
km_send_key() {
|
|
local sock=$1 key=$2
|
|
km_monitor_send "$sock" "sendkey $key"
|
|
}
|
|
|
|
# Send a chord — components are joined with `-` per QEMU HMP syntax.
|
|
km_send_chord() {
|
|
local sock=$1; shift
|
|
local IFS='-'
|
|
km_monitor_send "$sock" "sendkey $*"
|
|
}
|
|
|
|
# Type a string by encoding each character via the keymap. Unrecognised
|
|
# characters are skipped with a warning to stderr — caller is expected to
|
|
# stick to printable ASCII.
|
|
km_send_str() {
|
|
local sock=$1 s=$2 ch chord
|
|
local i=0
|
|
while (( i < ${#s} )); do
|
|
ch="${s:i:1}"
|
|
if [[ -n "${__KM_PLAIN[$ch]:-}" ]]; then
|
|
chord="${__KM_PLAIN[$ch]}"
|
|
km_monitor_send "$sock" "sendkey $chord"
|
|
elif [[ -n "${__KM_SHIFT[$ch]:-}" ]]; then
|
|
chord="${__KM_SHIFT[$ch]}"
|
|
km_monitor_send "$sock" "sendkey shift-$chord"
|
|
else
|
|
printf '[WARN] km_send_str: unencodable char %q skipped\n' "$ch" >&2
|
|
fi
|
|
i=$((i + 1))
|
|
# Tiny gap so QEMU doesn't drop fast keypresses on busy hosts.
|
|
# Empirically 5ms = the line between "100% reliable" and "loses ~1%".
|
|
sleep 0.02
|
|
done
|
|
}
|
|
|
|
# Convenience: type a string then press Enter.
|
|
km_send_line() {
|
|
local sock=$1 s=$2
|
|
km_send_str "$sock" "$s"
|
|
km_send_key "$sock" ret
|
|
}
|
|
|
|
# Visual indicator for log readability — prints a banner + a short pause so
|
|
# the next monitor command has time to land on a stable UI frame. Used by
|
|
# the harness between major steps; safe to skip in automated reuse.
|
|
km_step_banner() {
|
|
local label=$1
|
|
printf '\n──── %s @ %s ────\n' "$label" "$(date +'%H:%M:%S')"
|
|
}
|