testsAndMisc/linux_configuration/tests/test_i3blocks_shutdown_hint.sh
Krzysztof kuhy Rudnicki 5f14add664
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
i3blocks: daily-limits click works under i3blocks' PATH; deps: anthropic 1.12.1, pyflakes 4.0.3
i3blocks' environment has no ~/.local/bin, so `command -v daily-limits`
failed and every left-click on ⏻/🎮 was silently dropped. Call the launcher
by path (DAILY_LIMITS_BIN), detach with setsid, and notify when it is
missing. Also bumps the two pins behind latest stable that turned the
freshness gate red on 433cec27.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrjUTo1yhgAsUUasdw4bpG
2026-10-09 11:53:37 +02:00

128 lines
5.7 KiB
Bash
Executable File

#!/bin/bash
# Behaviour tests for shutdown_countdown.sh's rendering of daily-limits' cache
# ($DAILY_LIMITS_CACHE): the applied time, the unearned-earner hint (todo and
# unknown), the ceiling note driven by `shutdown_after`, and the stale/missing/
# garbage markers.
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
BLOCK="$REPO_DIR/i3blocks/shutdown_countdown.sh"
TMP_DIR=$(mktemp -d)
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
assert_eq() {
if [[ "$1" != "$2" ]]; then
fail "$3 (expected '$1', actual '$2')"
fi
}
printf '{}' | jq -e . >/dev/null 2>&1 ||
fail 'a working jq is required for the shutdown hint tests (pacman -S jq)'
CACHE="$TMP_DIR/daily-limits.json"
RENDER="$TMP_DIR/render"
SCHEDULE="$TMP_DIR/schedule.conf"
NO_OVERRIDES="$TMP_DIR/overrides.conf"
: >"$NO_OVERRIDES"
# 2026-05-01 is a Friday, so THU_SUN_MINUTES applies.
NOW=$(TZ=UTC date -d '2026-05-01 18:00:00' +%s)
# write_cache APPLIED GENERATED_AT TODO... where TODO is NAME:STATUS:MINUTES:AFTER.
# APPLIED may be "null" (then resolved = 20:00 is shown). Each todo entry is also
# an earner, carrying its shutdown_minutes.
write_cache() {
local applied=$1 generated=$2
shift 2
[[ $applied == null ]] || applied="\"$applied\""
local earners=() todos=() name status minutes after spec
for spec in "$@"; do
IFS=: read -r name status minutes after <<<"$spec"
earners+=("{\"name\":\"$name\",\"label\":\"$name\",\"status\":\"$status\",\"shutdown_minutes\":$minutes,\"gaming_minutes\":$minutes}")
todos+=("{\"name\":\"$name\",\"label\":\"$name\",\"status\":\"$status\",\"shutdown_after\":\"$after\"}")
done
local IFS=,
printf '{"date":"2026-05-01","generated_at":%s,"shutdown":{"applied":%s,"resolved":"20:00","floor":"20:00","ceiling":"23:00"},"gaming":{"budget_minutes":180,"used_minutes":null,"ceiling_minutes":480,"day":"2026-05-01"},"earners":[%s],"todo":[%s]}\n' \
"$generated" "$applied" "${earners[*]}" "${todos[*]}" >"$CACHE"
rm -f "$RENDER" # force a re-render for the new content
}
# first_line SHUTDOWN_MINUTES -- the block's full_text at 18:00.
first_line() {
printf 'THU_SUN_MINUTES=%s\nMON_WED_MINUTES=%s\nMORNING_END_MINUTES=300\n' \
"$1" "$1" >"$SCHEDULE"
TZ=UTC NOW_EPOCH="$NOW" DAILY_LIMITS_CACHE="$CACHE" DAILY_LIMITS_RENDER_CACHE="$RENDER" \
SHUTDOWN_CONFIG="$SCHEDULE" OVERRIDES_FILE="$NO_OVERRIDES" \
SKIP_DATES_FILE=/dev/null bash "$BLOCK" | sed -n 1p
}
printf 'Checking not-done earners are listed with icon and amount...\n'
write_cache 20:00 "$NOW" leetcode:todo:60:21:00 reading:todo:60:22:00 anki:todo:30:22:30 automation:todo:30:23:00
assert_eq '⏻ 20:00 +🧩1h +📖1h +🗂30m +⚙30m →max 23:00' "$(first_line 1200)" \
'cumulative shutdown_after reaching 23:00 gives the ceiling note'
write_cache 20:00 "$NOW" leetcode:todo:60:21:00 reading:todo:60:22:00
assert_eq '⏻ 20:00 +🧩1h +📖1h' "$(first_line 1200)" 'below the ceiling: no note'
printf 'Checking unknown earners are shown with ? and counted...\n'
write_cache 20:00 "$NOW" leetcode:unknown:60:21:00 reading:todo:60:22:00 anki:unknown:30:23:00
assert_eq '⏻ 20:00 +🧩?1h +📖1h +🗂?30m →max 23:00' "$(first_line 1200)" \
'unknown marked, and its shutdown_after still drives →max'
printf 'Checking the applied time wins, resolved is the fallback...\n'
write_cache 21:30 "$NOW"
assert_eq '⏻ 21:30' "$(first_line 1200)" 'applied time shown'
write_cache null "$NOW"
assert_eq '⏻ 20:00' "$(first_line 1200)" 'applied null falls back to resolved'
printf 'Checking nothing is listed at the ceiling or with all done...\n'
write_cache 23:00 "$NOW" leetcode:todo:60:23:00
assert_eq '⏻ 23:00' "$(first_line 1380)" 'no headroom, no hint'
write_cache 20:00 "$NOW"
assert_eq '⏻ 20:00' "$(first_line 1200)" 'everything done'
printf 'Checking odd amounts and unmapped earners...\n'
write_cache 20:00 "$NOW" leetcode:todo:90:21:30 newgate:todo:15:21:45
assert_eq '⏻ 20:00 +🧩1h30 +newgate15m' "$(first_line 1200)" \
'1h30 padded like gaming_budget.sh; unmapped earner falls back to label'
printf 'Checking stale, missing and unreadable caches show a marker...\n'
write_cache 21:30 "$((NOW - 181))" leetcode:todo:60:22:30
assert_eq '⏻ 20:00 ⏱? 3m' "$(first_line 1200)" 'stale: config time, no cache numbers'
write_cache 21:30 "$((NOW - 180))"
assert_eq '⏻ 21:30' "$(first_line 1200)" '180 s is still fresh'
rm -f "$CACHE"
assert_eq '⏻ 20:00 ⏱?' "$(first_line 1200)" 'missing cache'
printf 'not json\n' >"$CACHE"
rm -f "$RENDER"
assert_eq '⏻ 20:00 ⏱!' "$(first_line 1200)" 'unreadable cache'
printf 'Checking a left-click opens the popup without ~/.local/bin on PATH...\n'
mkdir -p "$TMP_DIR/click-bin"
printf '#!/bin/bash\necho "gui $*" >>"%s/clicks"\n' "$TMP_DIR" >"$TMP_DIR/daily-limits-stub"
printf '#!/bin/bash\necho "notify $*" >>"%s/clicks"\n' "$TMP_DIR" >"$TMP_DIR/click-bin/notify-send"
chmod +x "$TMP_DIR/daily-limits-stub" "$TMP_DIR/click-bin/notify-send"
# i3blocks' PATH has no ~/.local/bin: the launcher must be found by its path.
BLOCK_BUTTON=1 DAILY_LIMITS_BIN="$TMP_DIR/daily-limits-stub" PATH="$TMP_DIR/click-bin:/usr/bin:/bin" first_line 1200 >/dev/null
for _ in 1 2 3 4 5 6 7 8 9 10; do
[[ -s "$TMP_DIR/clicks" ]] && break
sleep 0.1
done
assert_eq 'gui --gui' "$(cat "$TMP_DIR/clicks" 2>/dev/null)" 'left-click runs daily-limits --gui'
rm -f "$TMP_DIR/clicks"
# A missing launcher says so instead of a silent no-op.
BLOCK_BUTTON=1 DAILY_LIMITS_BIN="$TMP_DIR/absent" PATH="$TMP_DIR/click-bin:/usr/bin:/bin" first_line 1200 >/dev/null
assert_eq "notify -u critical daily-limits not installed: $TMP_DIR/absent" "$(cat "$TMP_DIR/clicks" 2>/dev/null)" \
'a missing launcher raises a notification'
rm -f "$TMP_DIR/clicks"
printf 'All shutdown hint tests passed.\n'