#!/bin/bash # run_scenarios.sh -- scenario acceptance runner (check_detail0821 §14 / R3). # # Reads tests/scenarios/*/scenario.yaml (a minimal key: value subset) and runs # each scenario whose envs match the current environment. A scenario is a # pass when its check succeeds (a marker file or a command), a fail when it # does not, and skip/unsupported when the env or needs are not met. Results # are written as JSON (status.json) and SUPPORT_MATRIX.md is regenerated. # # Usage: run_scenarios.sh [ahk_binary] [--env x11|wayland] (or [ahk_binary] [x11|wayland]) set -u DIR="$(cd "$(dirname "$0")" && pwd)" AHK="${1:-}" ENV_OVERRIDE="" case "${2:-}" in --env) ENV_OVERRIDE="${3:-}" ;; x11|wayland) ENV_OVERRIDE="${2}" ;; esac if [ -z "$AHK" ]; then AHK="${AHK_CORE:-}" fi if [ -z "$AHK" ] || [ ! -x "$AHK" ]; then echo "usage: run_scenarios.sh [--env x11|wayland]" >&2 echo "diag: AHK='$AHK' cwd=$(pwd) exists=$([ -e "$AHK" ] && echo yes || echo no)" >&2 exit 2 fi # The scenario scripts run from their own directory, so the binary must be an # absolute path (a relative build path would break after the per-scenario cd). case "$AHK" in /*) : ;; *) AHK="$(cd "$(dirname "$AHK")" && pwd)/$(basename "$AHK")" ;; esac export AHK # Determine the environment. ENV="x11" if [ -n "$ENV_OVERRIDE" ]; then ENV="$ENV_OVERRIDE" elif [ -n "${WAYLAND_DISPLAY:-}" ]; then ENV="wayland" elif [ -n "${DISPLAY:-}" ]; then ENV="x11" fi echo "env=$ENV binary=$AHK" RESULTS="{}" FIRST=1 GATE=0 for yaml in "$DIR"/*/scenario.yaml; do [ -f "$yaml" ] || continue SC="$(dirname "$yaml")" id="$(grep -m1 '^id:' "$yaml" | sed 's/^id:[[:space:]]*//')" title="$(grep -m1 '^title:' "$yaml" | sed 's/^title:[[:space:]]*//')" sc_parse() { grep -m1 "^$1:" "$yaml" | sed "s/^$1:[[:space:]]*//; s/^\"//; s/\"\$//"; } script="$(sc_parse script)" stype="$(sc_parse script_type)" check="$(sc_parse check)" envs="$(grep -m1 '^envs:' "$yaml" | sed 's/^envs:[[:space:]]*//')" expect="$(grep -m1 '^expect:' "$yaml" | sed 's/^expect:[[:space:]]*//')" needs="$(grep -m1 '^needs:' "$yaml" | sed 's/^needs:[[:space:]]*//')" [ -n "$id" ] || continue # needs gate: capabilities the environment may lack (uinput writes, KDE, # Flatpak, inputd) -- skip + record instead of failing the GATE. needs_missing="" if [ -n "$needs" ]; then case ",$needs," in *,uinput,*) [ -w /dev/uinput ] || needs_missing="uinput not writable" ;; *,kde,*) needs_missing="no KDE host" ;; *,flatpak,*) needs_missing="no Flatpak host" ;; *,inputd,*) needs_missing="no inputd daemon" ;; *,gnome,*) [ -n "${WAYLAND_DISPLAY:-}" ] && [ -S "/run/user/$(id -u)/at-spi/bus_0" ] || needs_missing="no GNOME session with a11y" ;; *,ibus,*) pgrep -f ibus-daemon >/dev/null 2>&1 || needs_missing="no ibus-daemon" ;; esac fi # Env gate: scenario declares which envs it can run in. case ",$envs," in *,any,*) can_run=1 ;; *,"$ENV",*) can_run=1 ;; *) can_run=0 ;; esac if [ -n "$needs_missing" ]; then status="skip" detail="needs missing: $needs_missing" elif [ "$can_run" != "1" ]; then status="skip" detail="env $ENV not in envs=$envs" elif [ -z "$script" ]; then # env-agnostic scenarios that document unsupported capabilities (no script # here) are skipped with their 'needs' recorded. status="skip" detail="no script (needs: $needs)" else rm -f "$SC"/out.txt "$SC"/marker if [ "$stype" = "sh" ]; then ( cd "$SC" && bash "$script" > out.txt 2>&1 ) & else ( cd "$SC" && "$AHK" "$script" > out.txt 2>&1 ) & fi spid=$! # Bound the run with a SIGKILL watchdog: a hung core can ignore SIGTERM # (observed with a script-error dialog under Xvfb), so plain `timeout` # never returns; kill -9 always wins. ( sleep 30; kill -9 "$spid" 2>/dev/null ) & wd=$! wait "$spid" 2>/dev/null run_rc=$? kill "$wd" 2>/dev/null # The check: "marker:" or a command. status="fail" detail="rc=$run_rc" case "$check" in marker:*) mp="${check#marker:}" [ -f "$mp" ] && status="pass" detail="$detail marker=$mp" ;; script:*) cmd="${check#script:}" if ( cd "$SC" && bash -c "$cmd" ) >/dev/null 2>&1; then status="pass" fi detail="$detail check=$cmd" ;; *) [ -n "$check" ] && { ( cd "$SC" && bash -c "$check" ) >/dev/null 2>&1 && status="pass"; } ;; esac if [ -n "$needs" ]; then # needs are documented capabilities; the runner records them but a run # here already proves the env, so it stays a pass unless the check failed. : fi fi # Accumulate JSON (a tiny hand-rolled builder; values are quoted). json_id="$(printf '%s' "$id" | sed 's/"/\\"/g')" json_title="$(printf '%s' "$title" | sed 's/"/\\"/g')" json_status="$status" json_detail="$(printf '%s' "$detail" | sed 's/"/\\"/g')" entry="{\"id\":\"$json_id\",\"title\":\"$json_title\",\"env\":\"$ENV\",\"status\":\"$json_status\",\"detail\":\"$json_detail\",\"expect\":\"$expect\"}" if [ "$FIRST" = "1" ]; then RESULTS="[$entry" FIRST=0 else RESULTS="$RESULTS,$entry" fi printf '%-28s %-8s %s\n' "$id" "$status" "$detail" # Gate: an expected-pass scenario that failed breaks the pipeline (the CI # runner step); known-fail scenarios (recorded gaps) do not gate. if [ "$expect" = "pass" ] && [ "$status" = "fail" ]; then GATE=1 fi done RESULTS="$RESULTS]" printf '%s\n' "$RESULTS" > "$DIR/status.json" # Regenerate SUPPORT_MATRIX.md (scenario x env x level table). { echo "# Support Matrix (scenario x env) — generated by run_scenarios.sh" echo echo "| scenario | env | status | note |" echo "|---|---|---|---|" for yaml in "$DIR"/*/scenario.yaml; do [ -f "$yaml" ] || continue id="$(grep -m1 '^id:' "$yaml" | sed 's/^id:[[:space:]]*//')" title="$(grep -m1 '^title:' "$yaml" | sed 's/^title:[[:space:]]*//')" st=$(printf '%s' "$RESULTS" | sed -n "s/.*\"id\":\"$id\",\"title\":\"[^\"]*\",\"env\":\"[^\"]*\",\"status\":\"\([^\"]*\)\".*/\1/p" | head -1) echo "| $id | $ENV | ${st:-not-run} | $title |" done } > "$DIR/SUPPORT_MATRIX.md" echo "wrote $DIR/status.json + $DIR/SUPPORT_MATRIX.md" if [ "$GATE" = "1" ]; then echo "GATE: an expected-pass scenario failed (see status.json)" >&2 exit 1 fi exit 0