#!/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. # # 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=" # 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" # -- 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" echo " globals check: wl_compositor, wl_seat, xdg_wm_base, wl_shm, wl_output 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