From 37688f702a28b5224fb888d18d77489d53f1d4b8 Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Wed, 26 Aug 2026 23:12:14 -0400 Subject: [PATCH] Test: Isolate dock geometry fixtures --- tests/quickshell/dock-position-contract | 117 ++++++++++++++++++++---- 1 file changed, 100 insertions(+), 17 deletions(-) diff --git a/tests/quickshell/dock-position-contract b/tests/quickshell/dock-position-contract index e853aec..348d475 100755 --- a/tests/quickshell/dock-position-contract +++ b/tests/quickshell/dock-position-contract @@ -27,8 +27,7 @@ # slot from a real icon rather than assuming one -- a DockItem is taller # than it is wide, so a constant is wrong on one of the two orientations. # -# The geometry checks launch isolated shells against a temporary config. The -# real settings are read to build them and never written. +# The geometry checks launch isolated shells against a temporary config. set -uo pipefail @@ -47,8 +46,69 @@ for path in "$dock" "$body" "$strip"; do [[ -r "$path" ]] || fail "missing $path" done +# Keep this contract honest: it must never add a probe to the linked source +# tree, and every exit path must leave that tree byte-for-byte as it began. +source_probe="$shell_dir/dock-position-probe.qml" +source_probe_existed=0 +source_probe_checksum="" +if [[ -e "$source_probe" ]]; then + source_probe_existed=1 + source_probe_checksum="$(sha256sum -- "$source_probe")" +fi +source_tree_checksum() { + find "$shell_dir" -type f -print0 \ + | LC_ALL=C sort -z \ + | xargs -0 sha256sum -- \ + | sha256sum +} +source_tree_initial_checksum="$(source_tree_checksum)" + +assert_source_tree_unchanged() { + [[ "$(source_tree_checksum)" == "$source_tree_initial_checksum" ]] \ + || { printf 'dock position contract: the Quickshell source tree changed\n' >&2; return 1; } + + if (( source_probe_existed )); then + [[ -e "$source_probe" && "$(sha256sum -- "$source_probe")" == "$source_probe_checksum" ]] \ + || { printf 'dock position contract: the source probe changed\n' >&2; return 1; } + else + [[ ! -e "$source_probe" ]] \ + || { printf 'dock position contract: the contract created a source probe\n' >&2; return 1; } + fi +} + work="$(mktemp -d)" -trap 'rm -rf "$work" "$shell_dir/dock-position-probe.qml"' EXIT +fixture_shell="$work/quickshell" +cp -a "$shell_dir/." "$fixture_shell/" \ + || fail 'could not copy the Quickshell fixture' +fixture_pid="" +stop_fixture() { + [[ -n "$fixture_pid" ]] || return 0 + if kill -0 "$fixture_pid" 2>/dev/null; then + kill -TERM "$fixture_pid" 2>/dev/null || true + wait "$fixture_pid" 2>/dev/null || true + fi + fixture_pid="" +} +cleanup() { + local status=$? + trap - EXIT HUP INT TERM + stop_fixture + rm -rf "$work" + assert_source_tree_unchanged || status=1 + exit "$status" +} +trap cleanup EXIT HUP INT TERM + +# These checks are deliberately against this harness. A live settings read or +# source-tree probe means the contract itself is unsafe before it maps a panel. +probe_name='dock-position-probe.qml' +source_write_marker="$(printf 'cat >\"$shell_dir/%s\"' "$probe_name")" +home_marker="$(printf '\044')HOME" +unsafe_harness=() +grep -Fq "$source_write_marker" "$0" && unsafe_harness+=('writes its probe in the Quickshell source tree') +grep -Fq "$home_marker" "$0" && unsafe_harness+=('reads the user configuration directory') +(( ${#unsafe_harness[@]} == 0 )) \ + || fail "unsafe harness: ${unsafe_harness[*]}" # ── 3, 4, 6. What can be read ─────────────────────────────────────────────── @@ -111,7 +171,8 @@ PY # ── 1, 2, 7. Geometry, measured ──────────────────────────────────────────────── -cat >"$shell_dir/dock-position-probe.qml" <<'QML' +probe="$fixture_shell/$probe_name" +cat >"$probe" <<'QML' import Quickshell import QtQuick import qs.modules.dock @@ -164,28 +225,50 @@ ShellRoot { } QML -settings_source="${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json" -[[ -r "$settings_source" ]] || fail 'no settings to build a probe configuration from' - measure() { local position="$1" - python3 - "$settings_source" "$work/panama/settings.json" "$position" <<'PY' + python3 - "$work/config/panama/settings.json" "$position" <<'PY' import json, pathlib, sys -data = json.loads(pathlib.Path(sys.argv[1]).read_text()) -data["dockPosition"] = sys.argv[3] -out = pathlib.Path(sys.argv[2]) +out = pathlib.Path(sys.argv[1]) +data = { + "schemaVersion": 1, + "dockAutohide": True, + "dockPosition": sys.argv[2], + "dockScreens": [], + "dockIconSize": 48, + "dockRevealDelayMs": 0, + "dockHideDelayMs": 250, + "dockPinned": [], +} out.parent.mkdir(parents=True, exist_ok=True) -out.write_text(json.dumps(data)) +out.write_text(json.dumps(data) + "\n") PY - ( cd "$shell_dir" && XDG_CONFIG_HOME="$work" timeout 40 qs -p ./dock-position-probe.qml 2>&1 ) \ - | grep -oE '(DOCKGEOM|MASKGEOM) .*' + local output="$work/$position.output" + ( + cd "$fixture_shell" \ + && XDG_CONFIG_HOME="$work/config" XDG_STATE_HOME="$work/state" \ + exec timeout --foreground --kill-after=5s 40s qs -p "./$probe_name" + ) >"$output" 2>&1 & + fixture_pid=$! + if ! wait "$fixture_pid"; then + fixture_pid="" + cat "$output" >&2 + fail "the $position fixture did not exit cleanly" + fi + fixture_pid="" + geometry="$(grep -oE '(DOCKGEOM|MASKGEOM) .*' "$output")" \ + || fail "the $position fixture produced no geometry" } -bottom="$(measure bottom)" +geometry="" +measure bottom +bottom="$geometry" [[ -n "$bottom" ]] || fail 'the bottom dock produced no geometry at all' -left="$(measure left)" +measure left +left="$geometry" [[ -n "$left" ]] || fail 'the left dock produced no geometry at all' -right="$(measure right)" +measure right +right="$geometry" [[ -n "$right" ]] || fail 'the right dock produced no geometry at all' # The span checks read the window; the region checks read every line.