#!/bin/bash # # Copyright (C) 2026 Danilo M. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # The one runnable check for statusctl. It points XDG_RUNTIME_DIR at a # temporary directory, so nothing here touches the live modes. # # Usage: ./test-statusctl.sh (exit 0 = all passed) set -u here="$(cd "$(dirname "$0")" && pwd)" ctl="$here/statusctl" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT export XDG_RUNTIME_DIR="$tmp" pass=0 fail=0 check() { local label="$1" want="$2" got="$3" if [[ "$want" == "$got" ]]; then printf 'ok %s\n' "$label" pass=$((pass + 1)) else printf 'FAIL %s: want %q, got %q\n' "$label" "$want" "$got" fail=$((fail + 1)) fi } # A mode with no file reads as off. check "missing file reads 0" "0" "$("$ctl" dnd get)" # set writes the file and get reads it back. "$ctl" dnd set 1 check "set 1 writes the file" "1" "$(cat "$tmp/status.dnd" | tr -d '[:space:]')" check "get after set 1" "1" "$("$ctl" dnd get)" # toggle flips it. "$ctl" dnd toggle check "toggle from 1" "0" "$("$ctl" dnd get)" "$ctl" dnd toggle check "toggle from 0" "1" "$("$ctl" dnd get)" # set 0 writes rather than removing, so a reader sees an explicit off. "$ctl" dnd set 0 check "set 0 writes the file" "0" "$("$ctl" dnd get)" # nolock is a mode like any other: the mechanism is generic, this only pins # that the name is accepted by the mode list. "$ctl" nolock set 1 check "nolock set 1" "1" "$("$ctl" nolock get)" "$ctl" nolock toggle check "nolock toggle" "0" "$("$ctl" nolock get)" # An unknown mode is an error, not a silent success: a typo must not look # like a mode that is off. "$ctl" nosuch get >/dev/null 2>&1 check "unknown mode exits non-zero" "1" "$?" # watch prints a line on change, and the class reflects the value. The # atomic write arrives as a rename, which is why the watch needs moved_to. out="$tmp/watch.out" "$ctl" presentation watch > "$out" 2>/dev/null & watcher=$! sleep 0.3 "$ctl" presentation set 1 sleep 0.5 kill "$watcher" 2>/dev/null wait "$watcher" 2>/dev/null check "watch reports activated" "1" "$(grep -c '"class": *"activated"' "$out")" # A missing file is the off state, so the absent-file watch reports off rather # than a distinct warning: a fresh session has no file until something writes # one, and that is the normal state. rm -f "$tmp/status.presentation" out2="$tmp/watch2.out" "$ctl" presentation watch > "$out2" 2>/dev/null & watcher2=$! sleep 0.5 kill "$watcher2" 2>/dev/null wait "$watcher2" 2>/dev/null check "watch reports deactivated when absent" "1" "$(grep -c '"class": *"deactivated"' "$out2")" # A running game is reported as its own state, not as plain activated: the # widget draws a different icon and the tooltip says why the mode is on. rm -f "$tmp/status.presentation" printf '1\n' > "$tmp/status.gaming" out3="$tmp/watch3.out" "$ctl" presentation watch > "$out3" 2>/dev/null & watcher3=$! sleep 0.5 kill "$watcher3" 2>/dev/null wait "$watcher3" 2>/dev/null check "watch reports gaming" "1" "$(grep -c '"class": *"gaming"' "$out3")" # The gaming file is the shell's, so presentation's own file stays off and # `get` keeps reporting the manual half rather than the OR. check "get reads the manual half" "0" "$("$ctl" presentation get)" # Toggling while a game runs is refused: the detector would reassert it, so a # write here would not stick. "$ctl" presentation toggle 2>/dev/null check "toggle refused while gaming" "1" "$?" check "toggle wrote nothing" "0" "$("$ctl" presentation get)" # The refusal is specific to presentation; another mode toggles normally with # a game running. "$ctl" dnd set 0 "$ctl" dnd toggle check "dnd toggles while gaming" "1" "$("$ctl" dnd get)" # A game ending redraws the widget, which is why the watch follows # status.gaming as well as its own file. out4="$tmp/watch4.out" "$ctl" presentation watch > "$out4" 2>/dev/null & watcher4=$! sleep 0.3 printf '0\n' > "$tmp/status.gaming.tmp" mv -f "$tmp/status.gaming.tmp" "$tmp/status.gaming" sleep 0.5 kill "$watcher4" 2>/dev/null wait "$watcher4" 2>/dev/null check "game ending redraws" "1" "$(grep -c '"class": *"deactivated"' "$out4")" # The first write of a session creates the file, and Qt's atomic write does # that by linking an unnamed temp file into place: the only event naming the # mode file is IN_CREATE, no close_write and no moved_to. A hard link from # another name reproduces exactly that event. rm -f "$tmp/status.gaming" "$tmp/status.presentation" out5="$tmp/watch5.out" "$ctl" presentation watch > "$out5" 2>/dev/null & watcher5=$! sleep 0.3 printf '1\n' > "$tmp/unnamed" ln "$tmp/unnamed" "$tmp/status.gaming" sleep 0.5 kill "$watcher5" 2>/dev/null wait "$watcher5" 2>/dev/null check "first write, created by link, redraws" "1" "$(grep -c '"class": *"gaming"' "$out5")" printf '\n%d passed, %d failed\n' "$pass" "$fail" [[ "$fail" -eq 0 ]]