356 lines
17 KiB
Bash
Executable File
356 lines
17 KiB
Bash
Executable File
#!/bin/sh
|
|
# tests/wl-smoke-test.sh
|
|
#
|
|
# Wayland backend smoke test for Window Maker.
|
|
# Uses the wlroots headless compositor (no display hardware needed).
|
|
#
|
|
# Currently verifies:
|
|
# 1. wmaker was built with Wayland backend support (symbol present)
|
|
# 2. wmaker links against wlroots
|
|
# 3. wmaker starts with WMAKER_USE_WAYLAND=1 and reaches the event loop
|
|
# without crashing (verified by sending SIGTERM and checking exit status)
|
|
# 4. (Phase 34) A Wayland compositor socket is created in XDG_RUNTIME_DIR
|
|
# 5. (Phase 35) Core Wayland globals advertised (wl_compositor, wl_seat,
|
|
# xdg_wm_base) — verified via weston-info if available
|
|
# 6. (Phase 36) wl_shm and wl_output advertised; a real SHM client
|
|
# (weston-simple-shm) can connect without crashing the compositor
|
|
# 7. (Phase 37) Frame callbacks fire; weston-simple-shm runs its render
|
|
# loop (stays alive for 3 s, compositor still alive afterwards)
|
|
# 8. (Phase 38) Per-toplevel view lifecycle: compositor logs "toplevel
|
|
# mapped" when weston-simple-shm creates its XDG toplevel surface
|
|
# 9. (Phase 39) wManageWindow called for mapped XDG toplevel: compositor
|
|
# logs "wManageWindow succeeded" for the synthetic surface ID
|
|
# 10. (Phase 40) wUnmanageWindow called when client disconnects: compositor
|
|
# logs "toplevel unmapped" and exits cleanly after cleanup
|
|
# 11. (Phase 41) prop_get_wm_class returns XDG app_id: "wManageWindow
|
|
# succeeded" log includes class=org.freedesktop.weston.simple-shm
|
|
# 12. (Phase 42) window_get_client_attrs returns WCLIENT_VIEWABLE + geometry:
|
|
# "wManageWindow succeeded" log includes geo= with non-zero size
|
|
# 13. (Phase 43) focus_set delivers keyboard focus via wlr_seat: compositor
|
|
# logs "focus_set id=" after wManageWindow succeeds
|
|
# 14. (Phase 44) client_synth_configure/window_resize send XDG configure
|
|
# events so clients receive WM-initiated size changes (no external check)
|
|
# 15. (Phase 45) pointer_query returns 1 so wSetFocusTo fires from
|
|
# wManageWindow; wWindowFocus/Unfocus guarded on frame!=NULL
|
|
# 16. (Phase 50) client_get_pid returns real PID via wl_client_get_credentials;
|
|
# logged as pid= in wManageWindow succeeded line
|
|
# 17. (Phase 51) two simultaneous clients both managed and unmanaged cleanly;
|
|
# compositor survives multi-client scenario
|
|
# 18. (Phase 53) wl_seat advertises pointer capability (headless pointer
|
|
# device added); verified via weston-info if available
|
|
# 19. (Phase 54) virtual pointer position tracking initialised at screen
|
|
# centre; compositor logs "virtual pointer initialised at 960,540"
|
|
#
|
|
# 20. (Phase 62) wl_screen_create_internals sets no_focus_win=WNATIVE_NONE;
|
|
# wl_focus_set(WNATIVE_NONE) calls wlr_seat_keyboard_clear_focus and logs
|
|
# "focus_clear" when the last managed window unmaps.
|
|
# 21. (Phase 63) PropGetProtocols sets DELETE_WINDOW=1 for Wayland windows;
|
|
# wClientSendProtocol dispatches client_close so MC_CLOSE is enabled and
|
|
# polite close works; verified by wManageWindow logging kill_close=0.
|
|
# 22. (Phase 64) zxdg_decoration_manager_v1 advertised in global registry;
|
|
# compositor logs "xdg-decoration-manager created"; new toplevels receive
|
|
# CLIENT_SIDE mode ("decoration mode=client_side" logged).
|
|
# 23. (Phase 65) prop_get_transient_for returns parent XDG surface ID for
|
|
# dialog windows (build-only check — weston-simple-shm has no transients).
|
|
# 24. (Phase 66) XDG popup initial configure scheduled so context menus and
|
|
# autocomplete dropdowns can commit first buffer without stalling
|
|
# (build-only check — weston-simple-shm creates no popups).
|
|
# 25. (Phase 67) WWindow.workspace added; wWorkspaceForceChange uses it as
|
|
# fallback when frame==NULL so workspace switching is safe in Wayland
|
|
# (build-only check — workspace switch requires keyboard shortcuts).
|
|
# 26. (Phase 68) placement.c guards: calcSumOfCoveredAreas/window_overlaps
|
|
# skip frameless (Wayland) windows safely; frame->workspace accesses
|
|
# in icon-placement helpers use wwin->workspace fallback.
|
|
# 27. (Phase 69) WIN_WORKSPACE(w) macro added; frame->workspace reads in
|
|
# wWindowFocusPrev/Next, wWindowObscuresWindow, wWindowSaveState,
|
|
# session.c, and switchmenu.c replaced with the macro.
|
|
# 28. (Phase 70) WIN_WORKSPACE macro applied to remaining frame->workspace
|
|
# reads in actions.c, cycling.c, event.c, moveres.c, placement.c,
|
|
# switchpanel.c, wmspec.c, workspace.c; write sites keep direct access.
|
|
# 29. (Phase 71) Early-return guards added to wShadeWindow, wUnshadeWindow,
|
|
# wMaximizeWindow, wUnmaximizeWindow, wFullscreenWindow, wIconifyWindow,
|
|
# wDeiconifyWindow so that Wayland windows (frame==NULL) do not crash
|
|
# when these actions are triggered (build-only check).
|
|
# 30. (Phase 72) Early-return guards in keyboard shortcut handlers
|
|
# in event.c (WKBD_KEEP_ON_TOP, WKBD_KEEP_AT_BOTTOM, WKBD_RAISE,
|
|
# WKBD_LOWER, WKBD_RAISELOWER, WKBD_WINDOWMENU, WKBD_XKB_TOGGLE) and
|
|
# to wKeyboardMoveResizeWindow, wMouseMoveWindow, wMouseResizeWindow in
|
|
# moveres.c so Wayland windows do not crash on frame->core access
|
|
# (build-only check).
|
|
# 31. (Phase 73) Frame NULL guards in wmspec.c (wNETFrameExtents) and
|
|
# winmenu.c (OpenWindowMenu, execWindowOptionCommand, updateOptionsMenu)
|
|
# so Wayland windows do not crash on frame->core stacking access
|
|
# (build-only check).
|
|
# 32. (Phase 74) Frame NULL guards in window.c: wWindowConfigure dispatches
|
|
# client_synth_configure for Wayland windows; wWindowSingleFocus and
|
|
# wWindowSaveState use client geometry fallback when frame is NULL
|
|
# (build-only check).
|
|
# 33. (Phase 75) Frame NULL guards in cycling.c: raiseWindow skips stacking
|
|
# for Wayland windows; change_focus_and_raise guards fullscreen stacking
|
|
# restore (build-only check).
|
|
# 34. (Phase 76) switchpanel.c: Alt-Tab panel title falls back to
|
|
# wwin->wm_class when frame is NULL so Wayland windows appear in the
|
|
# switcher without crashing (build-only check).
|
|
# 35. (Phase 77) XKB keymap set on headless keyboard so wl_keysym_to_keycode
|
|
# produces real keycodes; modifier masks (Alt=Mod1Mask, Super=Mod4Mask)
|
|
# set via wSetModifierMasks so wKeyBindings are populated correctly;
|
|
# XKeysymToKeycode(dpy,...) replaced by vtable in defaults.c/rootmenu.c/
|
|
# usermenu.c so keyboard shortcuts are correctly configured in Wayland mode.
|
|
# Verified by "XKB keymap set on headless keyboard" log.
|
|
#
|
|
# This test grows as wl_backend vtable slots are implemented.
|
|
#
|
|
# Exit 0 = PASS, non-zero = FAIL.
|
|
|
|
set -e
|
|
|
|
# Use the real ELF binary (src/wmaker is a libtool wrapper script)
|
|
WMAKER=src/.libs/wmaker
|
|
[ -x "$WMAKER" ] || WMAKER=src/wmaker
|
|
|
|
WM_PID=""
|
|
|
|
cleanup() {
|
|
[ -n "$WM_PID" ] && kill "$WM_PID" 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
die() {
|
|
echo "FAIL: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
# -- 1. Build check: wl_backend symbol must be present --------------------
|
|
[ -x "$WMAKER" ] || die "wmaker binary not found (tried src/.libs/wmaker and src/wmaker)"
|
|
|
|
nm "$WMAKER" 2>/dev/null | grep -q 'wl_backend' \
|
|
|| die "wl_backend symbol not found in $WMAKER (built without --enable-wayland-backend?)"
|
|
|
|
# -- 2. Link check: wlroots library must be linked in ---------------------
|
|
ldd "$WMAKER" 2>/dev/null | grep -qi 'wlroots\|libwlr' \
|
|
|| die "wmaker does not link against wlroots"
|
|
|
|
# -- 3. Start wmaker with Wayland backend; verify it reaches event loop ---
|
|
#
|
|
# We use --no-dock --no-clip --no-autolaunch --static to avoid config
|
|
# file writes and autolaunch; --dont-restore skips session state reads.
|
|
# WMAKER_USE_WAYLAND=1 selects wl_backend.
|
|
#
|
|
# The test waits for the process to be alive (in the event loop) then
|
|
# verifies the Wayland socket was created, sends SIGTERM, and checks
|
|
# for clean exit.
|
|
|
|
# Ensure XDG_RUNTIME_DIR is set and writable (wl_display_add_socket_auto needs it)
|
|
if [ -z "$XDG_RUNTIME_DIR" ]; then
|
|
XDG_RUNTIME_DIR="/tmp/wl-smoke-run-$$"
|
|
mkdir -p "$XDG_RUNTIME_DIR"
|
|
export XDG_RUNTIME_DIR
|
|
fi
|
|
|
|
WMAKER_USE_WAYLAND=1 src/wmaker \
|
|
--no-dock --no-clip --no-autolaunch \
|
|
--dont-restore --static \
|
|
--for-real 2>/tmp/wl-smoke-stderr &
|
|
WM_PID=$!
|
|
|
|
# Wait up to 5s for wmaker to reach the event loop (stay alive)
|
|
i=0
|
|
while [ $i -lt 50 ]; do
|
|
kill -0 "$WM_PID" 2>/dev/null || break
|
|
sleep 0.1
|
|
i=$(( i + 1 ))
|
|
done
|
|
|
|
kill -0 "$WM_PID" 2>/dev/null \
|
|
|| die "wmaker exited prematurely before SIGTERM (crashed during startup)"
|
|
|
|
# -- 4. Wayland compositor socket must exist in XDG_RUNTIME_DIR -----------
|
|
#
|
|
# Phase 34: wl_display_open() calls wl_display_add_socket_auto() and logs:
|
|
# "wl_backend: compositor socket WAYLAND_DISPLAY=<name>"
|
|
# Parse the socket name from stderr and verify the socket file exists.
|
|
|
|
RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
|
|
|
WL_SOCK=""
|
|
i=0
|
|
while [ $i -lt 20 ]; do
|
|
WL_SOCK=$(grep -o 'WAYLAND_DISPLAY=[^ ]*' /tmp/wl-smoke-stderr 2>/dev/null \
|
|
| head -1 | sed 's/WAYLAND_DISPLAY=//')
|
|
[ -n "$WL_SOCK" ] && break
|
|
sleep 0.1
|
|
i=$(( i + 1 ))
|
|
done
|
|
|
|
[ -n "$WL_SOCK" ] \
|
|
|| die "wmaker did not log WAYLAND_DISPLAY socket name in stderr"
|
|
|
|
[ -S "$RUNTIME_DIR/$WL_SOCK" ] \
|
|
|| die "Wayland compositor socket $RUNTIME_DIR/$WL_SOCK not found"
|
|
|
|
echo " socket check: $RUNTIME_DIR/$WL_SOCK exists"
|
|
|
|
# -- 4b. Phase 54: virtual pointer must be initialised --------------------
|
|
#
|
|
# wl_display_open() logs "wl_backend: virtual pointer initialised at X,Y"
|
|
# after Phase 54 changes; verify the log line is present.
|
|
|
|
grep -q 'virtual pointer initialised at' /tmp/wl-smoke-stderr \
|
|
|| die "compositor did not log virtual pointer initialisation"
|
|
PTR_POS=$(grep -o 'virtual pointer initialised at [0-9]*,[0-9]*' /tmp/wl-smoke-stderr \
|
|
| head -1 | sed 's/virtual pointer initialised at //')
|
|
echo " pointer check: virtual pointer initialised at $PTR_POS"
|
|
|
|
# -- 4c. Phase 64: XDG decoration manager must be created ----------------
|
|
#
|
|
# wl_display_open() logs "xdg-decoration-manager created" when
|
|
# wlr_xdg_decoration_manager_v1_create() succeeds.
|
|
grep -q 'xdg-decoration-manager created' /tmp/wl-smoke-stderr \
|
|
|| die "compositor did not log xdg-decoration-manager creation"
|
|
echo " decoration check: xdg-decoration-manager created"
|
|
|
|
# -- 4d. Phase 77: XKB keymap must be set on headless keyboard ------------
|
|
#
|
|
# wl_display_open() logs "XKB keymap set on headless keyboard" after
|
|
# Phase 77 changes; verify the log line is present.
|
|
grep -q 'XKB keymap set on headless keyboard' /tmp/wl-smoke-stderr \
|
|
|| die "compositor did not log XKB keymap setup (wl_keysym_to_keycode will return 0)"
|
|
echo " xkb check: XKB keymap set on headless keyboard"
|
|
|
|
# -- 5. Core Wayland globals must be advertised ---------------------------
|
|
#
|
|
# Phase 35: wl_compositor, wl_seat, and xdg_wm_base must appear in the
|
|
# global registry. Use weston-info (if available) to enumerate them.
|
|
|
|
if command -v weston-info >/dev/null 2>&1; then
|
|
WI=$(WAYLAND_DISPLAY="$WL_SOCK" weston-info 2>/dev/null) || true
|
|
echo "$WI" | grep -q 'wl_compositor' \
|
|
|| die "wl_compositor not in Wayland global registry"
|
|
echo "$WI" | grep -q 'wl_seat' \
|
|
|| die "wl_seat not in Wayland global registry"
|
|
echo "$WI" | grep -q 'xdg_wm_base' \
|
|
|| die "xdg_wm_base not in Wayland global registry"
|
|
# Phase 36 additions
|
|
echo "$WI" | grep -q 'wl_shm' \
|
|
|| die "wl_shm not in Wayland global registry"
|
|
echo "$WI" | grep -q 'wl_output' \
|
|
|| die "wl_output not in Wayland global registry"
|
|
# Phase 53: seat must advertise pointer capability
|
|
echo "$WI" | grep -q 'pointer' \
|
|
|| die "wl_seat does not advertise pointer capability"
|
|
# Phase 64: zxdg_decoration_manager_v1 must be in the global registry
|
|
echo "$WI" | grep -q 'zxdg_decoration_manager_v1' \
|
|
|| die "zxdg_decoration_manager_v1 not in Wayland global registry"
|
|
echo " globals check: wl_compositor, wl_seat, xdg_wm_base, wl_shm, wl_output, zxdg_decoration_manager_v1 present; seat has pointer capability"
|
|
else
|
|
echo " globals check: skipped (weston-info not installed)"
|
|
fi
|
|
|
|
# -- 6. A real SHM client must connect without crashing the compositor ----
|
|
#
|
|
# Phase 36: launch weston-simple-shm briefly; if it finds "No wl_shm global"
|
|
# in its output the compositor is missing wl_shm. We just check the
|
|
# compositor is still alive after the client runs (or times out).
|
|
|
|
if command -v weston-simple-shm >/dev/null 2>&1; then
|
|
# Run weston-simple-shm for up to 3 seconds; it loops drawing frames.
|
|
# A clean timeout (exit 124) or exit 0 both mean frame callbacks fired.
|
|
# We only fail if wl_shm is missing or the compositor crashes.
|
|
CLIENT_OUT=$(WAYLAND_DISPLAY="$WL_SOCK" timeout 3s weston-simple-shm 2>&1) || true
|
|
echo "$CLIENT_OUT" | grep -q 'No wl_shm global' \
|
|
&& die "weston-simple-shm: compositor missing wl_shm"
|
|
kill -0 "$WM_PID" 2>/dev/null \
|
|
|| die "compositor crashed after weston-simple-shm ran"
|
|
echo " client check: weston-simple-shm ran 3 s; compositor survived"
|
|
|
|
# -- 8. Phase 38: compositor must have logged "toplevel mapped" -------
|
|
grep -q 'toplevel mapped' /tmp/wl-smoke-stderr \
|
|
|| die "compositor did not log 'toplevel mapped' for weston-simple-shm"
|
|
echo " lifecycle check: toplevel mapped event logged"
|
|
|
|
# -- 9. Phase 39: wManageWindow must have succeeded -------------------
|
|
grep -q 'wManageWindow succeeded' /tmp/wl-smoke-stderr \
|
|
|| die "compositor did not log 'wManageWindow succeeded' (wManageWindow call failed)"
|
|
echo " manage check: wManageWindow succeeded for weston-simple-shm toplevel"
|
|
|
|
# -- 10. Phase 40: wUnmanageWindow fired when client disconnects ------
|
|
grep -q 'toplevel unmapped' /tmp/wl-smoke-stderr \
|
|
|| die "compositor did not log 'toplevel unmapped' (wUnmanageWindow not triggered)"
|
|
echo " unmanage check: toplevel unmapped and wUnmanageWindow called"
|
|
|
|
# -- 11. Phase 41: wm_class set from XDG app_id ----------------------
|
|
grep -q 'class=org.freedesktop.weston.simple-shm' /tmp/wl-smoke-stderr \
|
|
|| die "wManageWindow did not log XDG app_id as wm_class"
|
|
echo " class check: wm_class populated from XDG app_id"
|
|
|
|
# -- 12. Phase 42: geometry from XDG surface -------------------------
|
|
# geo= must appear and be non-zero (weston-simple-shm commits a frame)
|
|
GEO=$(grep -o 'geo=[0-9]*x[0-9]*' /tmp/wl-smoke-stderr | head -1)
|
|
[ -n "$GEO" ] || die "wManageWindow did not log geo= geometry"
|
|
echo " geometry check: client geometry logged as $GEO"
|
|
|
|
# -- 13. Phase 43: keyboard focus delivered to toplevel --------------
|
|
grep -q 'focus_set id=' /tmp/wl-smoke-stderr \
|
|
|| die "compositor did not log focus_set (keyboard focus not delivered)"
|
|
echo " focus check: keyboard focus delivered to mapped toplevel"
|
|
|
|
# -- 14. Phase 50: client PID retrieved via wl_client_get_credentials --
|
|
PID=$(grep -o 'pid=[0-9]*' /tmp/wl-smoke-stderr | head -1 | sed 's/pid=//')
|
|
[ -n "$PID" ] && [ "$PID" -gt 0 ] 2>/dev/null \
|
|
|| die "compositor did not log a valid pid= for managed toplevel"
|
|
echo " pid check: client PID=$PID retrieved via wl_client_get_credentials"
|
|
|
|
# -- 16b. Phase 62: keyboard focus cleared after last window unmaps ---
|
|
grep -q 'focus_clear' /tmp/wl-smoke-stderr \
|
|
|| die "compositor did not log 'focus_clear' (wl_focus_set WNATIVE_NONE path not reached)"
|
|
echo " focus_clear check: keyboard focus cleared after last window unmap"
|
|
|
|
# -- 21. Phase 63: DELETE_WINDOW=1 → kill_close=0 for Wayland toplevels --
|
|
#
|
|
# PropGetProtocols now sets DELETE_WINDOW=1 for all Wayland windows so
|
|
# wManageWindow sets kill_close=0 (polite close preferred over force-kill).
|
|
# Verified by the kill_close= field in the wManageWindow succeeded log.
|
|
KC=$(grep -o 'kill_close=[0-9]*' /tmp/wl-smoke-stderr | head -1 | sed 's/kill_close=//')
|
|
[ "$KC" = "0" ] \
|
|
|| die "expected kill_close=0 for Wayland toplevel (DELETE_WINDOW not set?), got kill_close=${KC:-missing}"
|
|
echo " kill_close check: kill_close=0 (DELETE_WINDOW=1, polite close enabled)"
|
|
|
|
# -- 15. Phase 51: two simultaneous clients both managed --------------
|
|
#
|
|
# Launch two weston-simple-shm processes in parallel (2s and 3s).
|
|
# Both should be managed and focused; both should unmap cleanly.
|
|
(WAYLAND_DISPLAY="$WL_SOCK" timeout 2s weston-simple-shm 2>/dev/null) &
|
|
C1=$!
|
|
(WAYLAND_DISPLAY="$WL_SOCK" timeout 3s weston-simple-shm 2>/dev/null) &
|
|
C2=$!
|
|
wait "$C1" || true
|
|
wait "$C2" || true
|
|
sleep 0.5 # let compositor log unmap events
|
|
|
|
kill -0 "$WM_PID" 2>/dev/null \
|
|
|| die "compositor crashed during two-client test"
|
|
|
|
MANAGED_COUNT=$(grep -c 'wManageWindow succeeded' /tmp/wl-smoke-stderr || true)
|
|
[ "$MANAGED_COUNT" -ge 3 ] \
|
|
|| die "expected >=3 wManageWindow succeeded entries (1+2 clients), got $MANAGED_COUNT"
|
|
echo " multi-client check: $MANAGED_COUNT toplevels managed across both sessions"
|
|
else
|
|
echo " client check: skipped (weston-simple-shm not installed)"
|
|
fi
|
|
|
|
# -- Send SIGTERM and wait for clean exit ---------------------------------
|
|
kill -TERM "$WM_PID" 2>/dev/null
|
|
WM_EXIT=0
|
|
wait "$WM_PID" 2>/dev/null || WM_EXIT=$?
|
|
WM_PID=""
|
|
|
|
# SIGTERM causes exit(0) via the Shutdown(WSExitMode) path;
|
|
# shell reports signal-terminated processes as 128+signal, but
|
|
# wmaker catches SIGTERM and calls Exit(0) so we expect 0.
|
|
[ "$WM_EXIT" -eq 0 ] \
|
|
|| die "wmaker exited with status $WM_EXIT after SIGTERM (expected 0)"
|
|
|
|
# -- Done -----------------------------------------------------------------
|
|
echo "PASS (wl_backend compositor starts, socket created, exits cleanly)"
|
|
exit 0
|