settings-hyprland-write-contract failed in full suite runs and passed on its own, reporting "a typed batch did not reach the compositor" with every value at its default. Both it and settings-commit-reset-contract drive the same harness file, and Quickshell identifies an instance by its config path -- not by the environment it was launched with. So when one run's instance has not fully exited, the other's wait for `ipc show` is satisfied by that instance's target, and the whole contract then talks to a shell it did not start. The two directions fail differently, and the second is the alarming one: The write contract lands on the isolated instance, whose compositor write seam is deliberately stubbed. Its writes go nowhere, which is exactly the symptom above. The commit/reset contract lands on the non-isolated instance and drives the DAILY DESKTOP's real compositor while believing it is isolated. Both now refuse to start while another instance of that harness is alive, and say which hazard they are avoiding rather than failing on an assertion much later. One trap worth naming, since it bit me writing this: `rg -c` prints nothing at all when there are no matches, so an unguarded command substitution yields "" and not "0" -- the first version of the guard fired on a perfectly clean machine. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
163 lines
8.3 KiB
Bash
Executable File
163 lines
8.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Two behaviours the settings rows depend on:
|
|
#
|
|
# commitPreference(key, value)
|
|
# One entry point for every row. A compositor-backed key must reach Hyprland
|
|
# and be confirmed before it is stored; a local key is written directly.
|
|
# Rows bind a schema key and call this, so they never need to know which
|
|
# kind they hold -- and a row must not be able to store a value the
|
|
# compositor rejected.
|
|
#
|
|
# restoreDefaults()
|
|
# Panama keeps user state in more than one file. Resetting only the schema
|
|
# store would leave a customised Home accessory arrangement in place while
|
|
# claiming to have restored Panama's defaults. That is worse than having no
|
|
# reset at all, because it is silent.
|
|
|
|
set -euo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
harness="$repo_dir/config/dot/quickshell/settings-system-harness.qml"
|
|
system_settings="$repo_dir/config/dot/quickshell/services/SystemSettings.qml"
|
|
wallpaper_service="$repo_dir/config/dot/quickshell/services/Wallpaper.qml"
|
|
|
|
# Preferences are committed to $XDG_CONFIG_HOME, and the Home store lives under
|
|
# $XDG_STATE_HOME. Both are isolated so this contract cannot touch the real
|
|
# desktop's settings. The harness replaces the compositor write seam as well,
|
|
# so interruption cannot leave the daily desktop modified.
|
|
config_home="$(mktemp -d /tmp/panama-commit-config.XXXXXX)"
|
|
state_home="$(mktemp -d /tmp/panama-commit-state.XXXXXX)"
|
|
|
|
fail() {
|
|
printf 'settings commit/reset contract: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
rg -Fq 'HomePreferences.resetHomeDefaults();' "$system_settings" \
|
|
|| fail 'restoreDefaults does not use the durable Home reset boundary'
|
|
if rg -q 'HomePreferences\.(favorites|initialized)\s*=' "$system_settings"; then
|
|
fail 'restoreDefaults mutates Home aliases instead of using resetHomeDefaults'
|
|
fi
|
|
rg -Fq 'root.protectDisplays(protectedDisplays)' "$system_settings" \
|
|
|| fail 'restoreDefaults can apply unconfirmed display geometry during reload'
|
|
rg -Fq 'Keybinds.applyReload();' "$system_settings" \
|
|
|| fail 'restoreDefaults does not replay shipped keybindings'
|
|
rg -Fq 'root.applyWallpaper(String(DesktopPreferences.get("wallpaperPath") ?? ""));' "$system_settings" \
|
|
|| fail 'restoreDefaults does not visibly reapply the shipped wallpaper'
|
|
rg -Fq 'const effectivePath = path === "" ? root.shippedPath : path;' "$wallpaper_service" \
|
|
|| fail 'clearing wallpaper preference leaves the old image visible'
|
|
rg -Fq 'property string storedValue:' "$wallpaper_service" \
|
|
|| fail 'the shipped wallpaper cannot remain represented by the default empty preference'
|
|
|
|
qs_for_harness() {
|
|
XDG_CONFIG_HOME="$config_home" XDG_STATE_HOME="$state_home" \
|
|
PANAMA_SETTINGS_TEST_ISOLATE_COMPOSITOR=1 qs -p "$harness" "$@"
|
|
}
|
|
|
|
restore() {
|
|
qs_for_harness kill >/dev/null 2>&1 || true
|
|
rm -rf "$config_home" "$state_home"
|
|
}
|
|
trap restore EXIT
|
|
|
|
# This contract shares its harness file with settings-hyprland-write-contract,
|
|
# and Quickshell identifies an instance by config path -- so if that run's
|
|
# instance is still alive, the IPC wait below is satisfied by ITS target. That
|
|
# direction is the dangerous one: this contract believes the compositor seam is
|
|
# stubbed, so it would happily drive the DAILY DESKTOP's real compositor while
|
|
# reporting isolation. Refuse to start rather than find out.
|
|
harness_instances() {
|
|
# rg -c prints nothing when there are no matches, so an unguarded
|
|
# substitution yields "" rather than "0".
|
|
local count
|
|
count="$(qs list 2>/dev/null | rg -c "^ Config path: $harness\$" || true)"
|
|
printf '%s' "${count:-0}"
|
|
}
|
|
for _ in $(seq 1 50); do
|
|
[[ "$(harness_instances)" == "0" ]] && break
|
|
sleep 0.1
|
|
done
|
|
[[ "$(harness_instances)" == "0" ]] \
|
|
|| fail 'another instance of the settings harness is still running -- this contract would drive it instead of its own isolated one, and that instance may be writing to the real compositor'
|
|
|
|
XDG_CONFIG_HOME="$config_home" XDG_STATE_HOME="$state_home" \
|
|
PANAMA_SETTINGS_TEST_ISOLATE_COMPOSITOR=1 qs -p "$harness" --daemonize >/dev/null
|
|
for _ in $(seq 1 40); do
|
|
qs_for_harness ipc show 2>/dev/null | rg -q '^target settings-system-test$' && break
|
|
sleep 0.1
|
|
done
|
|
qs_for_harness ipc show 2>/dev/null | rg -q '^target settings-system-test$' || fail 'test IPC target did not start'
|
|
|
|
# ── A local key is stored directly ───────────────────────────────────────────
|
|
[[ "$(qs_for_harness ipc call settings-system-test commit showSeconds false)" == "true" ]] \
|
|
|| fail 'commitPreference refused a local key'
|
|
[[ "$(qs_for_harness ipc call settings-system-test stored showSeconds)" == "false" ]] \
|
|
|| fail 'a local key was not stored'
|
|
|
|
# ── A compositor key reaches the verified apply boundary, then is stored ────
|
|
target_rounding=11
|
|
[[ "$(qs_for_harness ipc call settings-system-test commit windowRounding "$target_rounding")" == "true" ]] \
|
|
|| fail 'commitPreference refused a compositor key'
|
|
apply_state="$(qs_for_harness ipc call settings-system-test applyState)"
|
|
jq -e '.[-1].windowRounding == 11' <<<"$apply_state" >/dev/null \
|
|
|| fail "a compositor-backed commit did not reach the apply boundary: $apply_state"
|
|
[[ "$(qs_for_harness ipc call settings-system-test stored windowRounding)" == "$target_rounding" ]] \
|
|
|| fail 'a verified compositor commit was not stored'
|
|
|
|
# ── A value the schema rejects is never stored ───────────────────────────────
|
|
before="$(qs_for_harness ipc call settings-system-test stored windowRounding)"
|
|
[[ "$(qs_for_harness ipc call settings-system-test commit windowRounding 9999)" == "true" ]] \
|
|
|| fail 'an out-of-range value should be clamped by the schema, not refused outright'
|
|
[[ "$(qs_for_harness ipc call settings-system-test stored windowRounding)" != "9999" ]] \
|
|
|| fail 'an out-of-range value was stored unclamped'
|
|
|
|
[[ "$(qs_for_harness ipc call settings-system-test commit __not_a_setting__ 1)" == "false" ]] \
|
|
|| fail 'commitPreference accepted a key outside the schema'
|
|
|
|
# ── Reset spans every store, not just the schema one ─────────────────────────
|
|
qs_for_harness ipc call settings-system-test seedHome >/dev/null
|
|
qs_for_harness ipc call settings-system-test commit dockHideDelayMs 900 >/dev/null
|
|
display_fixture='{"DP-2":{"mode":"4500x3000@60","scale":1.5,"transform":0}}'
|
|
[[ "$(qs_for_harness ipc call settings-system-test commit displays "$display_fixture")" == "true" ]] \
|
|
|| fail 'the protected display fixture did not apply'
|
|
sleep 0.4
|
|
|
|
home_before="$(qs_for_harness ipc call settings-system-test homeState)"
|
|
jq -e '.count == 1 and .initialized == true' <<<"$home_before" >/dev/null \
|
|
|| fail "the Home fixture did not apply: $home_before"
|
|
[[ "$(qs_for_harness ipc call settings-system-test stored dockHideDelayMs)" == "900" ]] \
|
|
|| fail 'the dock fixture did not apply'
|
|
|
|
[[ "$(qs_for_harness ipc call settings-system-test restoreDefaults)" == "true" ]] \
|
|
|| fail 'restoreDefaults refused a safe reset'
|
|
sleep 0.6
|
|
|
|
reset_state="$(qs_for_harness ipc call settings-system-test resetState)"
|
|
jq -e '.calls == [
|
|
"display.block:true",
|
|
"display.protect",
|
|
"keybinds.reload",
|
|
"wallpaper.set:",
|
|
"display.block:false"
|
|
] and .displayBlocked == false' <<<"$reset_state" >/dev/null \
|
|
|| fail "reset did not safely replay non-reactive state: $reset_state"
|
|
|
|
[[ "$(qs_for_harness ipc call settings-system-test stored dockHideDelayMs)" == "250" ]] \
|
|
|| fail 'reset did not restore a schema default'
|
|
[[ "$(qs_for_harness ipc call settings-system-test stored displays | jq -cS .)" == "$(jq -cS . <<<"$display_fixture")" ]] \
|
|
|| fail 'reset replaced confirmed display geometry without confirmation'
|
|
|
|
home_after="$(qs_for_harness ipc call settings-system-test homeState)"
|
|
jq -e '.count == 0 and .initialized == false' <<<"$home_after" >/dev/null \
|
|
|| fail "reset left the Home accessory store customised: $home_after"
|
|
|
|
# Resetting a stored value does not itself apply compositor policy, so the last
|
|
# isolated batch must contain the shipped default.
|
|
jq -e '.appliedBatches[-1].windowRounding == 18' <<<"$reset_state" >/dev/null \
|
|
|| fail "reset did not re-apply the compositor default: $reset_state"
|
|
|
|
trap - EXIT
|
|
restore
|
|
printf 'settings commit/reset contract: PASS\n'
|