#!/bin/bash set -uo pipefail # Tests the session resolution in classroom-notify-test. # # The compositor is stood in for by a real background process started with a known # environment, so /proc//environ is genuine rather than mocked. id, # pgrep, runuser and notify-send are shimmed onto PATH so every branch # can be reached from an ordinary account. # # tests/test-classroom-notify-test.sh ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SCRIPT="$ROOT/scripts/classroom-notify" WORK="$(mktemp -d)" trap 'rm -rf "$WORK"; kill "${FAKE_SESSION_PID:-}" 2>/dev/null' EXIT passed=0 failed=0 check() { local label="$1" expected="$2" actual="$3" if [[ "$actual" == *"$expected"* ]]; then echo " ok $label" passed=$((passed + 1)) else echo " FAIL $label" echo " expected to contain: $expected" echo " got: $actual" failed=$((failed + 1)) fi } check_absent() { local label="$1" unexpected="$2" actual="$3" if [[ "$actual" != *"$unexpected"* ]]; then echo " ok $label" passed=$((passed + 1)) else echo " FAIL $label" echo " expected NOT to contain: $unexpected" echo " got: $actual" failed=$((failed + 1)) fi } # ---------------------------------------------------------------- shims mkdir -p "$WORK/bin" cat > "$WORK/bin/id" <<'SHIM' #!/bin/bash if [[ "${1:-}" == "-u" && -n "${2:-}" ]]; then echo "$FAKE_CLASSROOM_UID" exit 0 fi if [[ "${1:-}" == "-u" ]]; then echo "$FAKE_CURRENT_UID" exit 0 fi exec /usr/bin/id "$@" SHIM cat > "$WORK/bin/pgrep" <<'SHIM' #!/bin/bash case "${!#}" in kwin_wayland) [[ -n "${FAKE_SESSION_PID:-}" ]] || exit 1 echo "$FAKE_SESSION_PID" ;; plasmashell) [[ "${FAKE_SHELL:-1}" == "1" ]] || exit 1 echo 4242 ;; *) exit 1 ;; esac SHIM cat > "$WORK/bin/runuser" <<'SHIM' #!/bin/bash echo "runuser $*" >> "$RUNUSER_LOG" shift 2 [[ "${1:-}" == "--" ]] && shift exec "$@" SHIM cat > "$WORK/bin/notify-send" <<'SHIM' #!/bin/bash { echo "args: $*" echo "XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-}" echo "DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS:-}" echo "WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-}" } >> "$NOTIFY_LOG" SHIM chmod +x "$WORK/bin"/* export PATH="$WORK/bin:$PATH" export NOTIFY_LOG="$WORK/notify.log" export RUNUSER_LOG="$WORK/runuser.log" export FAKE_CLASSROOM_UID=4000 export FAKE_CURRENT_UID=4000 RUNTIME="$WORK/run" mkdir -p "$RUNTIME" # A real unix socket, because the script checks the bus is actually there. python3 -c 'import socket, sys; socket.socket(socket.AF_UNIX).bind(sys.argv[1])' \ "$RUNTIME/bus" run() { : > "$NOTIFY_LOG" : > "$RUNUSER_LOG" "$SCRIPT" "$@" 2>&1 } start_fake_session() { env "$@" sleep 300 & FAKE_SESSION_PID=$! export FAKE_SESSION_PID # Give the exec time to land so /proc//environ is populated. while [[ ! -r "/proc/$FAKE_SESSION_PID/environ" ]]; do sleep 0.05; done } stop_fake_session() { [[ -n "${FAKE_SESSION_PID:-}" ]] && kill "$FAKE_SESSION_PID" 2>/dev/null wait "${FAKE_SESSION_PID:-}" 2>/dev/null unset FAKE_SESSION_PID } unset WAYLAND_DISPLAY DBUS_SESSION_BUS_ADDRESS # --------------------------------------------- 1. resolve from environ echo "1. resolves the session from the compositor's environment" start_fake_session \ XDG_RUNTIME_DIR="$RUNTIME" \ DBUS_SESSION_BUS_ADDRESS="unix:path=$RUNTIME/bus" \ WAYLAND_DISPLAY=wayland-7 out="$(run)" check "reports delivery to the classroom session" "Sent normal notification to the classroom desktop session" "$out" log="$(cat "$NOTIFY_LOG")" check "runtime dir taken from the compositor" "XDG_RUNTIME_DIR=$RUNTIME" "$log" check "bus address taken from the compositor" "DBUS_SESSION_BUS_ADDRESS=unix:path=$RUNTIME/bus" "$log" check "wayland display from the compositor" "WAYLAND_DISPLAY=wayland-7" "$log" check "styling preserved" "--app-name=Ekklesia Classroom" "$log" check "default sample title" "DVD Ready" "$log" check "default sample body" "A disc is ready to play." "$log" # ------------------------------------------------- 2. dry run reporting echo "2. --dry-run reports the resolved session and sends nothing" out="$(run --dry-run)" check "shows the compositor pid" "compositor pid: $FAKE_SESSION_PID" "$out" check "shows the runtime dir" "XDG_RUNTIME_DIR: $RUNTIME" "$out" check "shows the wayland display" "WAYLAND_DISPLAY: wayland-7" "$out" check_absent "nothing was sent" "args:" "$(cat "$NOTIFY_LOG")" # ------------------------------------------------------ 3. urgency/args echo "3. urgency and custom text still work" out="$(run --critical "Parent Call" "Room 3 needs a parent.")" log="$(cat "$NOTIFY_LOG")" check "critical urgency passed" "--urgency=critical" "$log" check "custom title passed" "Parent Call" "$log" check "custom body passed" "Room 3 needs a parent." "$log" # ------------------------------------------------ 4. root uses runuser echo "4. root delivers as the classroom user" FAKE_CURRENT_UID=0 out="$(run)" check "runuser targets classroom" "runuser -u classroom --" "$(cat "$RUNUSER_LOG")" check "notification still sent" "--app-name=Ekklesia Classroom" "$(cat "$NOTIFY_LOG")" FAKE_CURRENT_UID=4000 # ------------------------------------ 5. unprivileged admin is told how echo "5. an unprivileged administrator gets told what to do" FAKE_CURRENT_UID=9999 out="$(run)" check "explains it needs root" "run this as root (sudo classroom-notify)" "$out" check_absent "nothing was sent" "args:" "$(cat "$NOTIFY_LOG")" FAKE_CURRENT_UID=4000 # ------------------------------- 6. the shell must already be up echo "6. refuses to let D-Bus start a notification service" export FAKE_SHELL=0 out="$(run)" check "explains the shell is not running" "the classroom shell is not running" "$out" check_absent "nothing was sent" "args:" "$(cat "$NOTIFY_LOG")" unset FAKE_SHELL stop_fake_session # --------------------------------- 7. display derived from the lock fd echo "7. derives the display from the compositor's wayland lock when environ has none" touch "$RUNTIME/wayland-9.lock" env -u WAYLAND_DISPLAY \ XDG_RUNTIME_DIR="$RUNTIME" \ DBUS_SESSION_BUS_ADDRESS="unix:path=$RUNTIME/bus" \ bash -c "exec 9< '$RUNTIME/wayland-9.lock'; sleep 300" & FAKE_SESSION_PID=$! export FAKE_SESSION_PID while [[ ! -r "/proc/$FAKE_SESSION_PID/fd/9" ]]; do sleep 0.05; done out="$(run --dry-run)" check "display derived from the lock" "WAYLAND_DISPLAY: wayland-9" "$out" stop_fake_session # ------------------------------------------- 8. no session at all echo "8. says so clearly when no classroom session is running" out="$(run)" check "explains there is no session" "no classroom desktop session is running" "$out" check_absent "nothing was sent" "args:" "$(cat "$NOTIFY_LOG")" # ------------------------------------- 9. falls back when environ is thin echo "9. falls back to session defaults when the compositor's environment is unreadable" start_fake_session XDG_RUNTIME_DIR="$RUNTIME" out="$(run --dry-run)" check "derives the bus from the runtime dir" \ "DBUS_SESSION_BUS_ADDRESS: unix:path=$RUNTIME/bus" "$out" out="$(run)" check "still delivers" "Sent normal notification to the classroom desktop session" "$out" check "bus reached notify-send" "DBUS_SESSION_BUS_ADDRESS=unix:path=$RUNTIME/bus" \ "$(cat "$NOTIFY_LOG")" stop_fake_session # ------------------------------------------ 10. missing bus is explained echo "10. explains a missing session bus instead of hanging" MISSING="$WORK/empty" mkdir -p "$MISSING" start_fake_session XDG_RUNTIME_DIR="$MISSING" out="$(run)" check "names the missing socket" "bus socket $MISSING/bus does not exist" "$out" check_absent "nothing was sent" "args:" "$(cat "$NOTIFY_LOG")" stop_fake_session # ----------------------------- 11. already inside the classroom session echo "11. sends directly when already inside the classroom session" # No compositor process to find: an in-session caller must not need one. export WAYLAND_DISPLAY=wayland-4 export DBUS_SESSION_BUS_ADDRESS="unix:path=$RUNTIME/bus" export XDG_RUNTIME_DIR="$RUNTIME" out="$(run)" check "reports delivery to this session" "Sent normal notification to this session" "$out" check "notification was sent" "--app-name=Ekklesia Classroom" "$(cat "$NOTIFY_LOG")" check "inherits the session display" "WAYLAND_DISPLAY=wayland-4" "$(cat "$NOTIFY_LOG")" check_absent "did not go through runuser" "runuser" "$(cat "$RUNUSER_LOG")" out="$(run --dry-run)" check "dry run names this session" "Would notify this session" "$out" unset WAYLAND_DISPLAY DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR # --------------------------------- 12. classroom-notify-test delegates echo "12. the sample sender delegates to classroom-notify" export WAYLAND_DISPLAY=wayland-4 export DBUS_SESSION_BUS_ADDRESS="unix:path=$RUNTIME/bus" export XDG_RUNTIME_DIR="$RUNTIME" : > "$NOTIFY_LOG" out="$("$ROOT/scripts/classroom-notify-test" 2>&1)" check "sample still reports being sent" "Sent normal notification" "$out" check "sample title unchanged" "DVD Ready" "$(cat "$NOTIFY_LOG")" check "sample body unchanged" "A disc is ready to play." "$(cat "$NOTIFY_LOG")" unset WAYLAND_DISPLAY DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR # ----------------------------------------------------------- summary echo echo "passed: $passed failed: $failed" [[ "$failed" -eq 0 ]]