Add in-guest acceptance test suite
Runs inside a live Omarchy session (typically a VM installed by omarchy-iso-test) and verifies the desktop actually works: session health, application launches by window class, and shell surfaces via IPC — including typing into the launcher and launching the top hit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c70ae3a405
commit
5aa9e4173d
Executable
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# In-guest acceptance suite. Runs inside a live Omarchy session (typically a
|
||||||
|
# VM installed by omarchy-iso-test, reached over SSH) and verifies that the
|
||||||
|
# desktop actually works: session health, application launches, and shell
|
||||||
|
# surfaces (launcher, emoji picker, menu).
|
||||||
|
#
|
||||||
|
# Artifacts (screenshots, logs) land in $OMARCHY_ACCEPTANCE_DIR.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
TEST_DIR="$ROOT/test/acceptance.d"
|
||||||
|
|
||||||
|
export OMARCHY_ACCEPTANCE_DIR="${OMARCHY_ACCEPTANCE_DIR:-/tmp/omarchy-acceptance}"
|
||||||
|
mkdir -p "$OMARCHY_ACCEPTANCE_DIR"
|
||||||
|
|
||||||
|
# Discover the graphical session so the suite works over SSH, where none of
|
||||||
|
# the session environment is inherited.
|
||||||
|
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
||||||
|
export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=$XDG_RUNTIME_DIR/bus}"
|
||||||
|
export OMARCHY_PATH="${OMARCHY_PATH:-$ROOT}"
|
||||||
|
export PATH="$OMARCHY_PATH/bin:$PATH"
|
||||||
|
|
||||||
|
# First boot may still be settling; wait for Hyprland to come up.
|
||||||
|
boot_deadline=$((SECONDS + ${OMARCHY_ACCEPTANCE_BOOT_TIMEOUT:-300}))
|
||||||
|
|
||||||
|
wait_for_session() {
|
||||||
|
local signature
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
signature=$(ls -t "$XDG_RUNTIME_DIR/hypr" 2>/dev/null | head -1)
|
||||||
|
|
||||||
|
if [[ -n $signature ]]; then
|
||||||
|
export HYPRLAND_INSTANCE_SIGNATURE="$signature"
|
||||||
|
hyprctl -j monitors >/dev/null 2>&1 && return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((SECONDS >= boot_deadline)); then
|
||||||
|
echo "not ok - Hyprland session never came up" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_session
|
||||||
|
printf 'ok - Hyprland session is up (signature %s)\n' "$HYPRLAND_INSTANCE_SIGNATURE"
|
||||||
|
|
||||||
|
if [[ -z ${WAYLAND_DISPLAY:-} ]]; then
|
||||||
|
display=$(find "$XDG_RUNTIME_DIR" -maxdepth 1 -name "wayland-*" ! -name "*.lock" -printf '%f\n' 2>/dev/null | head -1)
|
||||||
|
export WAYLAND_DISPLAY="${display:-wayland-1}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
tests=()
|
||||||
|
for test in "$TEST_DIR"/*-test.sh; do
|
||||||
|
[[ $(basename "$test") == "base-test.sh" ]] && continue
|
||||||
|
tests+=("$test")
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if (( ${#tests[@]} == 0 )); then
|
||||||
|
echo "No acceptance tests found in $TEST_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for test in "${tests[@]}"; do
|
||||||
|
printf '==> %s\n' "${test#$ROOT/}"
|
||||||
|
bash "$test"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf 'ok - acceptance suite passed (artifacts in %s)\n' "$OMARCHY_ACCEPTANCE_DIR"
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
|
launch_and_verify() {
|
||||||
|
local name="$1" command="$2" class="$3"
|
||||||
|
|
||||||
|
# A pre-existing window makes the test ambiguous (and closing it would be
|
||||||
|
# hostile on a dev machine) — acceptance runs expect a fresh session.
|
||||||
|
if window_present "$class" >/dev/null 2>&1; then
|
||||||
|
fail "$name starts with no pre-existing window" "a window matching $class is already open"
|
||||||
|
fi
|
||||||
|
|
||||||
|
launch_app "$command"
|
||||||
|
wait_until "$name opens a window" 90 window_present "$class"
|
||||||
|
sleep 1
|
||||||
|
screenshot "app-$name"
|
||||||
|
|
||||||
|
local deadline=$((SECONDS + 30))
|
||||||
|
while window_present "$class" >/dev/null 2>&1; do
|
||||||
|
close_windows "$class"
|
||||||
|
|
||||||
|
if ((SECONDS >= deadline)); then
|
||||||
|
fail "$name window closes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
pass "$name window closes"
|
||||||
|
}
|
||||||
|
|
||||||
|
# name|command|window class regex
|
||||||
|
apps='terminal|foot|^foot$
|
||||||
|
browser|chromium --new-window|(?i)chromium
|
||||||
|
files|nautilus --new-window|org.gnome.Nautilus
|
||||||
|
calculator|gnome-calculator|org.gnome.Calculator
|
||||||
|
notes|obsidian|(?i)obsidian
|
||||||
|
office|libreoffice|(?i)(soffice|libreoffice)
|
||||||
|
pdf-viewer|evince|(?i)evince'
|
||||||
|
|
||||||
|
while IFS='|' read -r name command class; do
|
||||||
|
launch_and_verify "$name" "$command" "$class"
|
||||||
|
done <<<"$apps"
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
|
||||||
|
echo "source test/acceptance.d/base-test.sh from an acceptance test; do not run it directly" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||||
|
ARTIFACTS="${OMARCHY_ACCEPTANCE_DIR:-/tmp/omarchy-acceptance}"
|
||||||
|
|
||||||
|
mkdir -p "$ARTIFACTS"
|
||||||
|
|
||||||
|
pass() {
|
||||||
|
printf 'ok - %s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
local description="$1"
|
||||||
|
local detail="${2:-}"
|
||||||
|
|
||||||
|
[[ -n $detail ]] && printf '%s\n' "$detail" >&2
|
||||||
|
screenshot "failure-$(date +%s)"
|
||||||
|
printf 'not ok - %s\n' "$description" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
screenshot() {
|
||||||
|
grim "$ARTIFACTS/$1.png" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Poll a command until it succeeds; screenshot and fail on timeout.
|
||||||
|
wait_until() {
|
||||||
|
local description="$1" timeout="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
local deadline=$((SECONDS + timeout))
|
||||||
|
|
||||||
|
until "$@" >/dev/null 2>&1; do
|
||||||
|
if ((SECONDS >= deadline)); then
|
||||||
|
fail "$description" "timed out after ${timeout}s waiting for: $*"
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
pass "$description"
|
||||||
|
}
|
||||||
|
|
||||||
|
window_present() {
|
||||||
|
hyprctl -j clients | jq -e --arg class "$1" '[.[] | select(.class | test($class))] | length > 0'
|
||||||
|
}
|
||||||
|
|
||||||
|
window_absent() {
|
||||||
|
! window_present "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
layer_present() {
|
||||||
|
hyprctl -j layers | jq -e --arg ns "$1" '[.. | objects | select(.namespace? == $ns)] | length > 0'
|
||||||
|
}
|
||||||
|
|
||||||
|
layer_absent() {
|
||||||
|
! layer_present "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Close every window matching a class regex, by address so multi-window apps
|
||||||
|
# are fully closed. Tries the quattro Lua dispatcher first, then classic.
|
||||||
|
close_windows() {
|
||||||
|
local class="$1"
|
||||||
|
local addr
|
||||||
|
|
||||||
|
while read -r addr; do
|
||||||
|
hyprctl dispatch "hl.dsp.window.close({ window = \"address:$addr\" })" >/dev/null 2>&1 ||
|
||||||
|
hyprctl dispatch closewindow "address:$addr" >/dev/null 2>&1 || true
|
||||||
|
done < <(hyprctl -j clients | jq -r --arg class "$class" '.[] | select(.class | test($class)) | .address')
|
||||||
|
}
|
||||||
|
|
||||||
|
launch_app() {
|
||||||
|
setsid -f bash -c "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
|
# The compositor reports at least one monitor
|
||||||
|
monitors=$(hyprctl -j monitors | jq 'length')
|
||||||
|
(( monitors >= 1 )) || fail "compositor reports a monitor"
|
||||||
|
pass "compositor reports a monitor"
|
||||||
|
|
||||||
|
# The Omarchy shell is running and responsive
|
||||||
|
wait_until "omarchy-shell responds to ping" 60 omarchy-shell shell ping
|
||||||
|
|
||||||
|
# Core shell plugins are loaded
|
||||||
|
plugins=$(omarchy-shell shell listPlugins)
|
||||||
|
for plugin in omarchy.bar omarchy.launcher omarchy.emojis omarchy.menu omarchy.notifications; do
|
||||||
|
[[ $plugins == *"$plugin"* ]] || fail "shell plugin is loaded: $plugin" "loaded plugins: $plugins"
|
||||||
|
pass "shell plugin is loaded: $plugin"
|
||||||
|
done
|
||||||
|
|
||||||
|
# The bar and background are actually on screen
|
||||||
|
wait_until "bar layer is on screen" 30 layer_present "omarchy-bar"
|
||||||
|
wait_until "background layer is on screen" 30 layer_present "omarchy-background"
|
||||||
|
|
||||||
|
# Audio stack is up
|
||||||
|
wait_until "pipewire is running" 30 wpctl status
|
||||||
|
|
||||||
|
# Root filesystem is btrfs as installed
|
||||||
|
[[ $(findmnt -no FSTYPE /) == "btrfs" ]] || fail "root filesystem is btrfs"
|
||||||
|
pass "root filesystem is btrfs"
|
||||||
|
|
||||||
|
# Omarchy reports its version
|
||||||
|
omarchy-version >/dev/null || fail "omarchy-version works"
|
||||||
|
pass "omarchy-version works"
|
||||||
|
|
||||||
|
# No failed units, system or user. OMARCHY_ACCEPTANCE_IGNORE_UNITS can hold a
|
||||||
|
# regex of units to overlook (useful on dev machines; a fresh VM should be clean).
|
||||||
|
failed_units() {
|
||||||
|
systemctl "$@" --failed --no-legend --plain | awk '{print $1}' |
|
||||||
|
grep -Ev "${OMARCHY_ACCEPTANCE_IGNORE_UNITS:-^$}" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
failed_system=$(failed_units --system)
|
||||||
|
if [[ -n $failed_system ]]; then
|
||||||
|
fail "no failed system units" "failed units: $failed_system"
|
||||||
|
fi
|
||||||
|
pass "no failed system units"
|
||||||
|
|
||||||
|
failed_user=$(failed_units --user)
|
||||||
|
if [[ -n $failed_user ]]; then
|
||||||
|
fail "no failed user units" "failed units: $failed_user"
|
||||||
|
fi
|
||||||
|
pass "no failed user units"
|
||||||
|
|
||||||
|
screenshot "desktop"
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
|
open_and_close() {
|
||||||
|
local name="$1" plugin="$2" namespace="$3" payload="${4:-}"
|
||||||
|
|
||||||
|
if [[ -n $payload ]]; then
|
||||||
|
omarchy-shell shell summon "$plugin" "$payload" >/dev/null
|
||||||
|
else
|
||||||
|
omarchy-shell shell summon "$plugin" >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait_until "$name opens" 15 layer_present "$namespace"
|
||||||
|
sleep 1
|
||||||
|
screenshot "$name-open"
|
||||||
|
|
||||||
|
omarchy-shell shell hide "$plugin" >/dev/null
|
||||||
|
wait_until "$name closes" 15 layer_absent "$namespace"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Overlays open and close on IPC command
|
||||||
|
open_and_close "emoji-picker" omarchy.emojis omarchy-emojis
|
||||||
|
open_and_close "menu" omarchy.menu omarchy-menu '{"menu":"root"}'
|
||||||
|
open_and_close "clipboard" omarchy.clipboard omarchy-clipboard
|
||||||
|
|
||||||
|
# The launcher does the full loop: open, search by typing, launch the top hit
|
||||||
|
if window_present "org.gnome.Calculator" >/dev/null 2>&1; then
|
||||||
|
fail "launcher test starts with no calculator window" "a calculator window is already open"
|
||||||
|
fi
|
||||||
|
|
||||||
|
omarchy-shell shell summon omarchy.launcher >/dev/null
|
||||||
|
wait_until "launcher opens" 15 layer_present "omarchy-launcher"
|
||||||
|
sleep 1
|
||||||
|
screenshot "launcher-open"
|
||||||
|
|
||||||
|
wtype "calculator"
|
||||||
|
sleep 1
|
||||||
|
screenshot "launcher-search"
|
||||||
|
wtype -k Return
|
||||||
|
|
||||||
|
wait_until "launcher launches the top search hit" 60 window_present "org.gnome.Calculator"
|
||||||
|
wait_until "launcher closes after launching" 15 layer_absent "omarchy-launcher"
|
||||||
|
|
||||||
|
close_windows "org.gnome.Calculator"
|
||||||
|
wait_until "calculator window closes" 30 window_absent "org.gnome.Calculator"
|
||||||
Reference in New Issue
Block a user