Let the contracts assert properties, not the machine they were written on
The first run of the suite on a laptop found five contracts asserting the desktop instead of the code. settings-system pinned DP-2 at 4500x3000 in XRGB2101010; it now asks Hyprland what is actually primary. ssh-keys hardcoded id_ed25519; it now uses whichever key exists. switcher's live half stepped a session with one window, which step() deliberately refuses. displays raced the service's revert readback -- the compositor looks restored while verification still holds busy, so an immediate apply was refused with its error already cleared; the harness now exposes settled and the contract waits for it. declared-dependencies gets an OPTIONAL list for docker: the aliases serve machines that run Docker deliberately, Panama's runtime is rootless podman, and a missing docker fails by naming the command, which is loud enough. Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh
This commit is contained in:
@@ -32,7 +32,14 @@ ShellRoot {
|
|||||||
canConfirm: Displays.canConfirm,
|
canConfirm: Displays.canConfirm,
|
||||||
secondsLeft: Displays.secondsLeft,
|
secondsLeft: Displays.secondsLeft,
|
||||||
lastError: Displays.lastError,
|
lastError: Displays.lastError,
|
||||||
overridden: monitor ? Displays.isOverridden(monitor.name) : false
|
overridden: monitor ? Displays.isOverridden(monitor.name) : false,
|
||||||
|
// The compositor being visually restored is not the service
|
||||||
|
// being done: revert verification keeps its own readback
|
||||||
|
// running for a few ticks, and busy blocks a new apply until
|
||||||
|
// it settles. A caller that only watched `awaiting` raced
|
||||||
|
// this and got a refusal with no error text.
|
||||||
|
settled: !Displays.busy && !Displays.revertVerificationActive
|
||||||
|
&& !Displays.awaitingConfirmation
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,15 @@ SESSION='^(systemctl|busctl|journalctl|loginctl|hostnamectl|localectl|systemd-in
|
|||||||
# Anything added here needs a matching install block and a stated reason.
|
# Anything added here needs a matching install block and a stated reason.
|
||||||
SELF_INSTALLED='^(bun|claude|node|npm|pnpm)$'
|
SELF_INSTALLED='^(bun|claude|node|npm|pnpm)$'
|
||||||
|
|
||||||
|
# Tools an alias may lean on without Panama installing them anywhere. The
|
||||||
|
# docker aliases serve the machines that run Docker by deliberate choice;
|
||||||
|
# Panama's container runtime is rootless podman (development-packages), and
|
||||||
|
# declaring docker in a list would put a second container daemon on every
|
||||||
|
# fresh machine to keep five aliases company. Where docker is absent the
|
||||||
|
# aliases fail by naming the missing command, which is the honest outcome.
|
||||||
|
# Anything added here needs that same property: absence must be loud.
|
||||||
|
OPTIONAL='^(docker)$'
|
||||||
|
|
||||||
# jq programs are quoted arguments, but the scanner is line-based and cannot
|
# jq programs are quoted arguments, but the scanner is line-based and cannot
|
||||||
# tell a filter from a command. `not` is a jq builtin appearing inside one.
|
# tell a filter from a command. `not` is a jq builtin appearing inside one.
|
||||||
JQ_BUILTINS='^(not|empty|error|env|input|inputs)$'
|
JQ_BUILTINS='^(not|empty|error|env|input|inputs)$'
|
||||||
@@ -105,6 +114,7 @@ while read -r script; do
|
|||||||
[[ "$cmd" =~ $BASELINE ]] && continue
|
[[ "$cmd" =~ $BASELINE ]] && continue
|
||||||
[[ "$cmd" =~ $SESSION ]] && continue
|
[[ "$cmd" =~ $SESSION ]] && continue
|
||||||
[[ "$cmd" =~ $SELF_INSTALLED ]] && continue
|
[[ "$cmd" =~ $SELF_INSTALLED ]] && continue
|
||||||
|
[[ "$cmd" =~ $OPTIONAL ]] && continue
|
||||||
[[ "$cmd" =~ $JQ_BUILTINS ]] && continue
|
[[ "$cmd" =~ $JQ_BUILTINS ]] && continue
|
||||||
|
|
||||||
pkg="$(package_for "$cmd")"
|
pkg="$(package_for "$cmd")"
|
||||||
|
|||||||
@@ -309,7 +309,10 @@ target_scale=$(awk -v s="$original_scale" 'BEGIN { print (s == 1.25) ? 1.5 : 1.2
|
|||||||
run ipc call displays-test revertChange >/dev/null
|
run ipc call displays-test revertChange >/dev/null
|
||||||
immediate_reverted=false
|
immediate_reverted=false
|
||||||
for _ in $(seq 1 60); do
|
for _ in $(seq 1 60); do
|
||||||
if display_is_restored && [[ "$(status | jq -r .awaiting)" == "false" ]]; then
|
# settled, not awaiting: the compositor can look restored while the
|
||||||
|
# service's revert verification is still reading back, and an apply in
|
||||||
|
# that window is refused as busy. See the settled field in the harness.
|
||||||
|
if display_is_restored && [[ "$(status | jq -r .settled)" == "true" ]]; then
|
||||||
immediate_reverted=true
|
immediate_reverted=true
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
@@ -345,6 +348,11 @@ done
|
|||||||
[[ "$(status | jq -r .overridden)" == "false" ]] || fail 'an unconfirmed change was written to the settings store'
|
[[ "$(status | jq -r .overridden)" == "false" ]] || fail 'an unconfirmed change was written to the settings store'
|
||||||
|
|
||||||
# ── A confirmed change is what writes ────────────────────────────────────────
|
# ── A confirmed change is what writes ────────────────────────────────────────
|
||||||
|
# Wait out the revert readback before applying again -- same race as above.
|
||||||
|
for _ in $(seq 1 40); do
|
||||||
|
[[ "$(status | jq -r .settled)" == "true" ]] && break
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
[[ "$(run ipc call displays-test applyScale "$target_scale")" == "true" ]] \
|
[[ "$(run ipc call displays-test applyScale "$target_scale")" == "true" ]] \
|
||||||
|| fail 'the confirmed-change fixture could not apply'
|
|| fail 'the confirmed-change fixture could not apply'
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,19 @@ for _ in $(seq 1 40); do
|
|||||||
fi
|
fi
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
done
|
done
|
||||||
jq -e '.monitorName == "DP-2" and .width == 4500 and .height == 3000 and .scale == 1.5 and .format == "XRGB2101010"' <<<"$status" >/dev/null \
|
# Against the live compositor, not a named machine: this once asserted DP-2 at
|
||||||
|| fail "live monitor data was not normalized: $status"
|
# 4500x3000 in XRGB2101010, which pinned the desktop it was written on and
|
||||||
|
# could never pass on a laptop's eDP-1. The property is that SystemSettings
|
||||||
|
# mirrors whatever monitor is actually primary, normalized -- so ask Hyprland
|
||||||
|
# what that is.
|
||||||
|
live_monitor="$(hyprctl -j monitors | jq -c '.[0]')"
|
||||||
|
jq -e --argjson live "$live_monitor" '
|
||||||
|
.monitorName == $live.name
|
||||||
|
and .width == $live.width
|
||||||
|
and .height == $live.height
|
||||||
|
and ((.scale - $live.scale) | fabs) < 0.001
|
||||||
|
and (.format | length) > 0' <<<"$status" >/dev/null \
|
||||||
|
|| fail "live monitor data was not normalized: $status (compositor: $live_monitor)"
|
||||||
|
|
||||||
[[ "$(qs_for_harness ipc call settings-system-test panelAllowed '__definitely_not_a_panel__' | jq -r .)" == "false" ]] \
|
[[ "$(qs_for_harness ipc call settings-system-test panelAllowed '__definitely_not_a_panel__' | jq -r .)" == "false" ]] \
|
||||||
|| fail 'unsupported GNOME panel was accepted'
|
|| fail 'unsupported GNOME panel was accepted'
|
||||||
|
|||||||
@@ -96,9 +96,18 @@ grep -q 'durableRemoval' "$helper" \
|
|||||||
|
|
||||||
kind="$(printf '%s' "$state" | field "['agent'].get('kind','')")"
|
kind="$(printf '%s' "$state" | field "['agent'].get('kind','')")"
|
||||||
if [[ "$kind" == "gnome-keyring" ]]; then
|
if [[ "$kind" == "gnome-keyring" ]]; then
|
||||||
reason="$(printf '%s' "$("$helper" agent-remove "$HOME/.ssh/id_ed25519")" | field "['error']")"
|
# Whichever key this machine actually has. This used to hardcode
|
||||||
[[ "$reason" == *"does not stick"* ]] \
|
# id_ed25519, which asserted the author's machine: any other key name
|
||||||
|| fail "removal against a keyring agent was not refused with its reason (got: $reason)"
|
# earned "That key no longer exists" instead of the refusal under test.
|
||||||
|
# No key at all means the property cannot be exercised here, not that it
|
||||||
|
# failed.
|
||||||
|
real_key="$(compgen -G "$HOME/.ssh/id_*.pub" | head -1)"
|
||||||
|
real_key="${real_key%.pub}"
|
||||||
|
if [[ -n "$real_key" ]]; then
|
||||||
|
reason="$(printf '%s' "$("$helper" agent-remove "$real_key")" | field "['error']")"
|
||||||
|
[[ "$reason" == *"does not stick"* ]] \
|
||||||
|
|| fail "removal against a keyring agent was not refused with its reason (got: $reason)"
|
||||||
|
fi
|
||||||
grep -q 'does not stick' "$page" \
|
grep -q 'does not stick' "$page" \
|
||||||
|| fail 'the page does not say that removing a key from this agent has no effect'
|
|| fail 'the page does not say that removing a key from this agent has no effect'
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -73,7 +73,12 @@ grep -q 'WlrKeyboardFocus.None' "$window" \
|
|||||||
|
|
||||||
# ── Live ────────────────────────────────────────────────────────────────────
|
# ── Live ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
if command -v qs >/dev/null 2>&1 && qs ipc call switcher cancel >/dev/null 2>&1; then
|
# Stepping refuses to open with fewer than two windows (see step() in the
|
||||||
|
# state service) -- that is designed behavior, not a failure, so a session
|
||||||
|
# with one window skips the live half rather than failing against it.
|
||||||
|
open_windows="$(hyprctl -j clients 2>/dev/null | jq length 2>/dev/null || echo 0)"
|
||||||
|
if command -v qs >/dev/null 2>&1 && (( open_windows >= 2 )) \
|
||||||
|
&& qs ipc call switcher cancel >/dev/null 2>&1; then
|
||||||
qs ipc call switcher next >/dev/null 2>&1
|
qs ipc call switcher next >/dev/null 2>&1
|
||||||
sleep 0.6
|
sleep 0.6
|
||||||
mapped="$(hyprctl layers -j 2>/dev/null | grep -c 'qs-switcher' || true)"
|
mapped="$(hyprctl layers -j 2>/dev/null | grep -c 'qs-switcher' || true)"
|
||||||
|
|||||||
Reference in New Issue
Block a user