Test: Classify every contract capability
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
# Contract capability manifest.
|
||||
#
|
||||
# Every path collected by `panama test` appears once. `hermetic` contracts use
|
||||
# only isolated fixtures or source inspection; other entries name the live
|
||||
# boundary they reach, with the immediately preceding comment stating its effect.
|
||||
# `privileged` is supported for future contracts but has no current entry.
|
||||
|
||||
hermetic tests/hypr/gestures-contract
|
||||
hermetic tests/hypr/hypr-prefs-contract
|
||||
hermetic tests/hypr/idle-config-contract
|
||||
hermetic tests/hypr/idle-defaults-contract
|
||||
# Reloads and reads the live Hyprland keybind categories from the compositor.
|
||||
live-compositor tests/hypr/keybind-categories-contract
|
||||
hermetic tests/hypr/prefs-fallback-contract
|
||||
hermetic tests/hypr/session-teardown-contract
|
||||
hermetic tests/hypr/window-rules-contract
|
||||
hermetic tests/hypr/workspace-rules-contract
|
||||
hermetic tests/quickshell/accent-controls-contract
|
||||
hermetic tests/quickshell/accessibility-contract
|
||||
# Pushes a fixture privacy state into the running shell and opens the activity\npanel over the desktop.
|
||||
live-compositor,live-desktop tests/quickshell/activity-indicator-contract
|
||||
# Pushes fixture privacy states and Signal Glass events into the running shell,\nand dismisses the live capsule.
|
||||
live-desktop tests/quickshell/activity-state-contract
|
||||
# Reads the host GNOME accent enum through gsettings to verify Panama's accent\nmapping.
|
||||
live-host tests/quickshell/adwaita-accent-contract
|
||||
hermetic tests/quickshell/agent-usage-contract
|
||||
hermetic tests/quickshell/app-library-contract
|
||||
# Reads the host PipeWire application-volume state through the live Quickshell\nservice.
|
||||
live-host tests/quickshell/application-volume-contract
|
||||
hermetic tests/quickshell/applications-settings-contract
|
||||
hermetic tests/quickshell/bar-visibility-contract
|
||||
hermetic tests/quickshell/battery-contract
|
||||
hermetic tests/quickshell/bluetooth-discovery-contract
|
||||
hermetic tests/quickshell/brightness-helper-contract
|
||||
# Opens the live agenda popover and notification centre, and starts a real focus\nsession on the running shell.
|
||||
live-compositor,live-desktop tests/quickshell/calendar-agenda-contract
|
||||
# Queries the host calendar helper for real configured sources and upcoming\nevents.
|
||||
live-host tests/quickshell/calendar-agenda-helper-contract
|
||||
hermetic tests/quickshell/calendar_agenda_bridge_test.py
|
||||
# Opens and closes the cheatsheet overlay on the running shell.
|
||||
live-compositor,live-desktop tests/quickshell/cheatsheet-contract
|
||||
# Reads the host Quickshell networking type metadata to verify the live\nNetworkManager boundary.
|
||||
live-host tests/quickshell/connectivity-contract
|
||||
hermetic tests/quickshell/containers-contract
|
||||
# Maps the Control Center and reads its live compositor layer to verify the\nwindow is present.
|
||||
live-compositor,live-desktop tests/quickshell/control-center-contract
|
||||
# Maps the Control Center service surface and reads the live compositor while\nexercising its IPC services.
|
||||
live-compositor,live-desktop tests/quickshell/control-center-services-contract
|
||||
hermetic tests/quickshell/curated-events-policy-contract
|
||||
hermetic tests/quickshell/declared-assets-contract
|
||||
hermetic tests/quickshell/declared-dependencies-contract
|
||||
hermetic tests/quickshell/default-apps-contract
|
||||
# Reads the host xdg-mime default handlers for the configured application\nfamilies.
|
||||
live-host tests/quickshell/default-apps-family-contract
|
||||
# Changes the real default browser through the default-apps helper -- xdg-mime\nand xdg-settings defaults on this machine -- and puts it back.
|
||||
live-desktop tests/quickshell/default-apps-roles-contract
|
||||
hermetic tests/quickshell/desktop-style-contract
|
||||
# Reads the host disk and filesystem snapshot through the production disks\nhelper.
|
||||
live-host tests/quickshell/disks-contract
|
||||
# Maps the display-arrangement surface and measures its live desktop geometry.
|
||||
live-desktop tests/quickshell/display-arrangement-contract
|
||||
hermetic tests/quickshell/display-layout-contract
|
||||
hermetic tests/quickshell/display-transaction-contract
|
||||
# Changes the real monitor's mode, position, scale and rotation through the live\ncompositor, restoring the display it started from.
|
||||
live-compositor,live-desktop tests/quickshell/displays-contract
|
||||
hermetic tests/quickshell/dock-pins-contract
|
||||
# Reads the live dock window geometry to verify its mapped position.
|
||||
live-desktop tests/quickshell/dock-position-contract
|
||||
# Reads Hyprland option descriptions from the live compositor to verify enum\nmappings.
|
||||
live-compositor tests/quickshell/enum-hypr-map-contract
|
||||
hermetic tests/quickshell/fingerprint-contract
|
||||
# Reads the host firewall state through the production firewall helper.
|
||||
live-host tests/quickshell/firewall-contract
|
||||
hermetic tests/quickshell/focus-modes-contract
|
||||
# Starts, pauses, reveals and ends a real focus session, and opens Mission\nControl on the running shell.
|
||||
live-compositor,live-desktop tests/quickshell/focus-session-contract
|
||||
# Restarts panama-quickshell.service (or `qs kill`s the shell when unsupervised)\nwith a focus session in flight; an interrupted run leaves caffeine latched on.
|
||||
live-desktop tests/quickshell/focus-session-expiry
|
||||
# Restarts panama-quickshell.service (or `qs kill`s the shell when unsupervised)\nmid-session to prove a paused focus session survives it.
|
||||
live-desktop tests/quickshell/focus-session-restart
|
||||
# Reads the host gaming and graphics state through the production helper.
|
||||
live-host tests/quickshell/gaming-contract
|
||||
hermetic tests/quickshell/gnome-handoff-contract
|
||||
hermetic tests/quickshell/gtk-theme-contract
|
||||
hermetic tests/quickshell/health-service-contract
|
||||
# Maps the Health UI test-shell surface to exercise desktop window integration.
|
||||
live-desktop tests/quickshell/health-ui-contract
|
||||
hermetic tests/quickshell/home-assistant-config-contract
|
||||
# Probes the configured Home Assistant endpoint over the network.
|
||||
network tests/quickshell/home-assistant-helper-contract
|
||||
hermetic tests/quickshell/home-brightness-slider-contract
|
||||
hermetic tests/quickshell/home-preferences-contract
|
||||
hermetic tests/quickshell/home_assistant_bridge_test.py
|
||||
hermetic tests/quickshell/ipc-targets-contract
|
||||
hermetic tests/quickshell/kdeconnect-helper-contract
|
||||
hermetic tests/quickshell/kdeconnect_bridge_test.py
|
||||
# Reloads the live Hyprland keybind configuration to verify rebinding behavior.
|
||||
live-compositor tests/quickshell/keybind-rebind-contract
|
||||
# Reads the live Hyprland bind list to verify every compositor binding is shown.
|
||||
live-compositor tests/quickshell/keybinds-contract
|
||||
hermetic tests/quickshell/keyring-helper-contract
|
||||
hermetic tests/quickshell/layer-margin-contract
|
||||
hermetic tests/quickshell/lock-screen-helper-contract
|
||||
hermetic tests/quickshell/lock-screen-service-contract
|
||||
hermetic tests/quickshell/lock-screen-settings-contract
|
||||
hermetic tests/quickshell/lock-screen-theme-contract
|
||||
hermetic tests/quickshell/manual-contract
|
||||
hermetic tests/quickshell/migrations-contract
|
||||
# Maps the My Home Settings surface and reads the live compositor during the\nsettings flow.
|
||||
live-compositor,live-desktop tests/quickshell/my-home-settings-contract
|
||||
hermetic tests/quickshell/network-tools-contract
|
||||
hermetic tests/quickshell/notification-app-rules-contract
|
||||
hermetic tests/quickshell/online-accounts-contract
|
||||
hermetic tests/quickshell/osd-helper-contract
|
||||
hermetic tests/quickshell/osd-model-contract
|
||||
hermetic tests/quickshell/osd-ui-contract
|
||||
hermetic tests/quickshell/overview-keyboard-contract
|
||||
# Spawns a real kitty window, moves it between your workspaces and into the\nscratchpad, and opens the overview.
|
||||
live-compositor,live-desktop tests/quickshell/overview-live-actions
|
||||
# Opens the overview on the running shell and types a search into it.
|
||||
live-compositor,live-desktop tests/quickshell/overview-search-contract
|
||||
hermetic tests/quickshell/overview-thumbnail-contract
|
||||
# Queries the live Quickshell overview-actions IPC surface for window action\navailability.
|
||||
live-host tests/quickshell/overview-window-actions-contract
|
||||
hermetic tests/quickshell/palette-contract
|
||||
hermetic tests/quickshell/panama-action-contract
|
||||
# Toggles the live caffeine inhibitor and Night Light on the running shell.
|
||||
live-desktop tests/quickshell/panama-action-ipc-contract
|
||||
hermetic tests/quickshell/panama-agent-contract
|
||||
hermetic tests/quickshell/panama-command-install-contract
|
||||
hermetic tests/quickshell/panama-commands-contract
|
||||
hermetic tests/quickshell/panama-doctor-contract
|
||||
hermetic tests/quickshell/per-screen-surface-contract
|
||||
hermetic tests/quickshell/permissions-contract
|
||||
hermetic tests/quickshell/phone-messages-contract
|
||||
# Maps the Phone page test-shell surface and reads the live compositor during\nthe page flow.
|
||||
live-compositor,live-desktop tests/quickshell/phone-page-contract
|
||||
# Reads the host polkit-agent installation and session service configuration.
|
||||
live-host tests/quickshell/polkit-agent-contract
|
||||
hermetic tests/quickshell/polkit-reason-contract
|
||||
hermetic tests/quickshell/power-page-contract
|
||||
hermetic tests/quickshell/power-profile-contract
|
||||
hermetic tests/quickshell/powermenu-contract
|
||||
hermetic tests/quickshell/preference-schema-contract
|
||||
# Reads the host printer state through the production printer helper.
|
||||
live-host tests/quickshell/printers-contract
|
||||
hermetic tests/quickshell/prism-gallery-contract
|
||||
hermetic tests/quickshell/privacy-traces-contract
|
||||
hermetic tests/quickshell/qmldir-registration-contract
|
||||
# Reads live Hyprland option JSON from the compositor to verify schema shape.
|
||||
live-compositor tests/quickshell/schema-hypr-shape-contract
|
||||
# Opens and closes the live overview.
|
||||
live-compositor,live-desktop tests/quickshell/scratchpad-shelf-contract
|
||||
# Opens the live screen-intelligence and capture overlays and runs an analysis\nthrough them.
|
||||
live-desktop tests/quickshell/screen-intelligence-contract
|
||||
hermetic tests/quickshell/screen-intelligence-helper-contract
|
||||
hermetic tests/quickshell/search-routing-contract
|
||||
hermetic tests/quickshell/secrets-contract
|
||||
hermetic tests/quickshell/settings-backup-contract
|
||||
hermetic tests/quickshell/settings-backup-live-contract
|
||||
hermetic tests/quickshell/settings-buttons-contract
|
||||
hermetic tests/quickshell/settings-commit-reset-contract
|
||||
hermetic tests/quickshell/settings-docs-contract
|
||||
hermetic tests/quickshell/settings-hardcoded-values-contract
|
||||
# Flips real compositor policy -- gaps, blur, inactive opacity, keyboard layout\n-- on the live compositor and restores it.
|
||||
live-compositor,live-desktop tests/quickshell/settings-hyprland-write-contract
|
||||
hermetic tests/quickshell/settings-idiom-contract
|
||||
hermetic tests/quickshell/settings-jump-contract
|
||||
hermetic tests/quickshell/settings-nav-contract
|
||||
hermetic tests/quickshell/settings-ownership-contract
|
||||
# Maps Settings pages through a test shell and reads the live compositor during\npage routing.
|
||||
live-compositor,live-desktop tests/quickshell/settings-pages-contract
|
||||
hermetic tests/quickshell/settings-preferences-contract
|
||||
hermetic tests/quickshell/settings-search-contract
|
||||
hermetic tests/quickshell/settings-sidebar-layout-contract
|
||||
hermetic tests/quickshell/settings-sync-contract
|
||||
# Applies compositor policy through the production write path against the live\ncompositor, and writes preferences to the real settings store (no isolated\nconfig home).
|
||||
live-compositor,live-desktop tests/quickshell/settings-system-contract
|
||||
hermetic tests/quickshell/settings-titlebar-contract
|
||||
# Opens the real Settings window, routes it between pages, and closes it through\nthe compositor.
|
||||
live-compositor,live-desktop tests/quickshell/settings-window-contract
|
||||
# Flips every compositor-backed setting to a value it does not hold, one at a\ntime, on the live compositor.
|
||||
live-compositor,live-desktop tests/quickshell/settings-write-sweep-contract
|
||||
# Reads the host sharing-service snapshot through the production helper.
|
||||
live-host tests/quickshell/sharing-contract
|
||||
# Publishes a critical fixture event into the live Signal Glass capsule.
|
||||
live-compositor,live-desktop tests/quickshell/signal-glass-contract
|
||||
# Reads the host snapshot state through the production snapshots helper.
|
||||
live-host tests/quickshell/snapshots-contract
|
||||
hermetic tests/quickshell/sound-cards-contract
|
||||
hermetic tests/quickshell/sound-defaults-contract
|
||||
# Reads the host PipeWire device state through Quickshell's live audio service.
|
||||
live-host tests/quickshell/sound-page-contract
|
||||
hermetic tests/quickshell/sound-routing-contract
|
||||
# Reads the host SSH-agent and key-management availability while keeping test\nkeys in a throwaway home.
|
||||
live-host tests/quickshell/ssh-keys-contract
|
||||
# Publishes fixture events into the live capsule and toggles Do Not Disturb.
|
||||
live-desktop tests/quickshell/status-events-contract
|
||||
# Drives the live window switcher and commits a switch, which moves your focus.
|
||||
live-compositor,live-desktop tests/quickshell/switcher-contract
|
||||
hermetic tests/quickshell/theme-catalog-contract
|
||||
hermetic tests/quickshell/theme-profiles-contract
|
||||
# Reads the host package-update snapshot through the production updates helper.
|
||||
live-host tests/quickshell/updates-contract
|
||||
# Reads the host AccountsService snapshot of local user accounts.
|
||||
live-host tests/quickshell/user-accounts-contract
|
||||
hermetic tests/quickshell/video-wallpaper-contract
|
||||
hermetic tests/quickshell/vpn-contract
|
||||
hermetic tests/quickshell/wallpaper-policy-contract
|
||||
hermetic tests/quickshell/wallpaper-service-contract
|
||||
hermetic tests/quickshell/wallpaper-settings-contract
|
||||
# Opens and closes the welcome overlay on the running shell.
|
||||
live-compositor,live-desktop tests/quickshell/welcome-contract
|
||||
hermetic tests/quickshell/wifi-qr-contract
|
||||
hermetic tests/quickshell/xkb-presets-contract
|
||||
hermetic tests/server/compose-secrets-contract
|
||||
hermetic tests/server/containers-shape-contract
|
||||
hermetic tests/server/panama-server-contract
|
||||
hermetic tests/setup/apps-contract
|
||||
hermetic tests/setup/boot-contract
|
||||
hermetic tests/setup/contract-manifest-contract
|
||||
hermetic tests/setup/crash-watch-contract
|
||||
hermetic tests/setup/desktop-first-contract
|
||||
hermetic tests/setup/dictation-contract
|
||||
hermetic tests/setup/dotfile-classification-contract
|
||||
# Contacts the configured package sources through dnf and Flathub availability\nchecks.
|
||||
network tests/setup/extras-contract
|
||||
hermetic tests/setup/firefox-chrome-contract
|
||||
hermetic tests/setup/hardware-contract
|
||||
# Reads the host hardware predicates after exercising fixture sysfs trees.
|
||||
live-host tests/setup/hardware-predicates-contract
|
||||
hermetic tests/setup/hooks-contract
|
||||
hermetic tests/setup/interview-contract
|
||||
hermetic tests/setup/launch-or-focus-contract
|
||||
hermetic tests/setup/launcher-commands-contract
|
||||
hermetic tests/setup/launcher-search-contract
|
||||
hermetic tests/setup/lid-contract
|
||||
hermetic tests/setup/migrations-contract
|
||||
hermetic tests/setup/nautilus-extensions-contract
|
||||
hermetic tests/setup/package-lists-contract
|
||||
hermetic tests/setup/projects-contract
|
||||
hermetic tests/setup/readme-contract
|
||||
hermetic tests/setup/role-contract
|
||||
hermetic tests/setup/skills-contract
|
||||
hermetic tests/setup/update-command-contract
|
||||
hermetic tests/setup/user-content-contract
|
||||
hermetic tests/setup/webapp-contract
|
||||
@@ -1,99 +0,0 @@
|
||||
# Contracts that take over the desktop you are sitting in front of.
|
||||
#
|
||||
# Most of the suite is hermetic: stub commands on PATH, a throwaway HOME, or a
|
||||
# Quickshell harness booted with `qs -p` under its own semantic entry file. Those
|
||||
# are safe to run mid-session and are not listed here.
|
||||
#
|
||||
# The ones below are not. Each drives the LIVE shell, compositor or machine --
|
||||
# it opens overlays over whatever you were doing, restarts the running shell,
|
||||
# moves your windows, changes your monitor, or rewrites a real default. Run
|
||||
# during a working session they are, at best, a series of surprises; at worst
|
||||
# they leave state behind when interrupted.
|
||||
#
|
||||
# `panama test --safe` skips exactly this list, which is why every entry's
|
||||
# comment says what it does to the session rather than what it proves.
|
||||
# tests/setup/desktop-hijacking-contract checks each path exists and sweeps
|
||||
# tests/ for hijacking contracts that are missing from here.
|
||||
#
|
||||
# One repo-relative path per line.
|
||||
|
||||
# Pushes a fixture privacy state into the running shell and opens the activity
|
||||
# panel over the desktop.
|
||||
tests/quickshell/activity-indicator-contract
|
||||
|
||||
# Pushes fixture privacy states and Signal Glass events into the running shell,
|
||||
# and dismisses the live capsule.
|
||||
tests/quickshell/activity-state-contract
|
||||
|
||||
# Opens the live agenda popover and the notification centre, and starts a real
|
||||
# focus session on the running shell.
|
||||
tests/quickshell/calendar-agenda-contract
|
||||
|
||||
# Opens and closes the cheatsheet overlay on the running shell.
|
||||
tests/quickshell/cheatsheet-contract
|
||||
|
||||
# Changes the real default browser through the default-apps helper -- xdg-mime
|
||||
# and xdg-settings defaults on this machine -- and puts it back.
|
||||
tests/quickshell/default-apps-roles-contract
|
||||
|
||||
# Changes the real monitor's mode, position, scale and rotation through the live
|
||||
# compositor, restoring the display it started from.
|
||||
tests/quickshell/displays-contract
|
||||
|
||||
# Starts, pauses, reveals and ends a real focus session, and opens Mission
|
||||
# Control on the running shell.
|
||||
tests/quickshell/focus-session-contract
|
||||
|
||||
# Restarts panama-quickshell.service (or `qs kill`s the shell when unsupervised)
|
||||
# with a focus session in flight; an interrupted run leaves caffeine latched on.
|
||||
tests/quickshell/focus-session-expiry
|
||||
|
||||
# Restarts panama-quickshell.service (or `qs kill`s the shell when unsupervised)
|
||||
# mid-session to prove a paused focus session survives it.
|
||||
tests/quickshell/focus-session-restart
|
||||
|
||||
# Spawns a real kitty window, moves it between your workspaces and into the
|
||||
# scratchpad, and opens the overview.
|
||||
tests/quickshell/overview-live-actions
|
||||
|
||||
# Opens the overview on the running shell and types a search into it.
|
||||
tests/quickshell/overview-search-contract
|
||||
|
||||
# Toggles the live caffeine inhibitor and Night Light on the running shell.
|
||||
tests/quickshell/panama-action-ipc-contract
|
||||
|
||||
# Opens and closes the live overview.
|
||||
tests/quickshell/scratchpad-shelf-contract
|
||||
|
||||
# Opens the live screen-intelligence and capture overlays and runs an analysis
|
||||
# through them.
|
||||
tests/quickshell/screen-intelligence-contract
|
||||
|
||||
# Flips real compositor policy -- gaps, blur, inactive opacity, keyboard layout
|
||||
# -- on the live compositor and restores it.
|
||||
tests/quickshell/settings-hyprland-write-contract
|
||||
|
||||
# Applies compositor policy through the production write path against the live
|
||||
# compositor, and writes preferences to the real settings store (no isolated
|
||||
# config home).
|
||||
tests/quickshell/settings-system-contract
|
||||
|
||||
# Opens the real Settings window, routes it between pages, and closes it through
|
||||
# the compositor.
|
||||
tests/quickshell/settings-window-contract
|
||||
|
||||
# Flips every compositor-backed setting to a value it does not hold, one at a
|
||||
# time, on the live compositor.
|
||||
tests/quickshell/settings-write-sweep-contract
|
||||
|
||||
# Publishes a critical fixture event into the live Signal Glass capsule.
|
||||
tests/quickshell/signal-glass-contract
|
||||
|
||||
# Publishes fixture events into the live capsule and toggles Do Not Disturb.
|
||||
tests/quickshell/status-events-contract
|
||||
|
||||
# Drives the live window switcher and commits a switch, which moves your focus.
|
||||
tests/quickshell/switcher-contract
|
||||
|
||||
# Opens and closes the welcome overlay on the running shell.
|
||||
tests/quickshell/welcome-contract
|
||||
Executable
+172
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Every runner-visible contract has one capability classification in
|
||||
# tests/contracts.manifest. The manifest is deliberately complete: callers can
|
||||
# decide what is safe to run without rediscovering test behaviour themselves.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
manifest="$repo_dir/tests/contracts.manifest"
|
||||
|
||||
discover_contracts() {
|
||||
discovered_contracts=()
|
||||
while IFS= read -r path; do
|
||||
[[ -x "$path" || "$path" == *_test.py ]] || continue
|
||||
discovered_contracts+=("tests/${path#"$repo_dir/tests/"}")
|
||||
done < <(find "$repo_dir/tests" -type f \
|
||||
-not -path '*/fixtures/*' -not -path '*__pycache__*' | sort)
|
||||
}
|
||||
|
||||
validate_manifest() {
|
||||
local candidate="$1"
|
||||
local -n expected_contracts="$2"
|
||||
local line capabilities path extra previous_was_comment=0
|
||||
local -a capability_list=()
|
||||
local -A manifest_paths=() capability_counts=()
|
||||
local previous_path=""
|
||||
|
||||
validation_findings=()
|
||||
validation_note() { validation_findings+=("$1"); }
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" =~ ^[[:space:]]*# ]]; then
|
||||
previous_was_comment=1
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^[[:space:]]*$ ]]; then
|
||||
previous_was_comment=0
|
||||
continue
|
||||
fi
|
||||
|
||||
IFS=$' \t' read -r capabilities path extra <<<"$line"
|
||||
if [[ -z "${capabilities:-}" || -z "${path:-}" || -n "${extra:-}" ]]; then
|
||||
validation_note "manifest line is not exactly two fields: $line"
|
||||
previous_was_comment=0
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -n "$previous_path" && "$path" < "$previous_path" ]]; then
|
||||
validation_note 'paths are not lexicographically sorted'
|
||||
fi
|
||||
previous_path="$path"
|
||||
|
||||
if [[ -n "${manifest_paths[$path]:-}" ]]; then
|
||||
validation_note "duplicate path $path"
|
||||
fi
|
||||
manifest_paths["$path"]=1
|
||||
|
||||
IFS=',' read -r -a capability_list <<<"$capabilities"
|
||||
local -A line_capabilities=()
|
||||
local capability
|
||||
for capability in "${capability_list[@]}"; do
|
||||
if [[ -z "$capability" ]]; then
|
||||
validation_note "empty capability on $path"
|
||||
continue
|
||||
fi
|
||||
if [[ -n "${line_capabilities[$capability]:-}" ]]; then
|
||||
validation_note "duplicate capability $capability on $path"
|
||||
fi
|
||||
line_capabilities["$capability"]=1
|
||||
case "$capability" in
|
||||
hermetic|live-host|live-compositor|live-desktop|network|privileged)
|
||||
capability_counts["$capability"]=1
|
||||
;;
|
||||
*) validation_note "unknown capability $capability on $path" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "${line_capabilities[hermetic]:-}" && ${#line_capabilities[@]} -ne 1 ]]; then
|
||||
validation_note "hermetic must appear alone on $path"
|
||||
fi
|
||||
|
||||
if [[ "$capabilities" != hermetic && "$previous_was_comment" -ne 1 ]]; then
|
||||
validation_note "$path is non-hermetic but lacks a directly preceding comment"
|
||||
fi
|
||||
previous_was_comment=0
|
||||
done < "$candidate"
|
||||
|
||||
local expected
|
||||
for expected in "${!expected_contracts[@]}"; do
|
||||
[[ -n "${manifest_paths[$expected]:-}" ]] || validation_note "missing contract $expected"
|
||||
done
|
||||
for path in "${!manifest_paths[@]}"; do
|
||||
[[ -n "${expected_contracts[$path]:-}" ]] || validation_note "stale manifest path $path"
|
||||
done
|
||||
|
||||
for capability in live-host live-compositor live-desktop network; do
|
||||
[[ -n "${capability_counts[$capability]:-}" ]] || validation_note "manifest has no $capability contract"
|
||||
done
|
||||
|
||||
if (( ${#validation_findings[@]} > 0 )); then
|
||||
printf 'contract manifest: %d finding(s)\n' "${#validation_findings[@]}" >&2
|
||||
printf ' - %s\n' "${validation_findings[@]}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
run_parser_fixture() {
|
||||
local label="$1" expected_message="$2" contents="$3" output fixture
|
||||
shift 3
|
||||
local -A fixture_paths=()
|
||||
local fixture_path
|
||||
for fixture_path in "$@"; do
|
||||
fixture_paths["$fixture_path"]=1
|
||||
done
|
||||
|
||||
fixture="$(mktemp)"
|
||||
printf '%s' "$contents" > "$fixture"
|
||||
if output="$(validate_manifest "$fixture" fixture_paths 2>&1)"; then
|
||||
printf 'contract manifest: parser fixture %s unexpectedly passed\n' "$label" >&2
|
||||
rm -f "$fixture"
|
||||
return 1
|
||||
fi
|
||||
rm -f "$fixture"
|
||||
|
||||
if ! grep -Fq "$expected_message" <<<"$output"; then
|
||||
printf 'contract manifest: parser fixture %s did not name %q: %s\n' \
|
||||
"$label" "$expected_message" "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_parser_fixtures() {
|
||||
run_parser_fixture missing-contract 'missing contract tests/b' \
|
||||
$'hermetic tests/a\n' tests/a tests/b || return 1
|
||||
run_parser_fixture stale-path 'stale manifest path tests/stale' \
|
||||
$'hermetic tests/a\nhermetic tests/stale\n' tests/a || return 1
|
||||
run_parser_fixture duplicate-path 'duplicate path tests/a' \
|
||||
$'hermetic tests/a\nhermetic tests/a\n' tests/a || return 1
|
||||
run_parser_fixture unknown-capability 'unknown capability unknown on tests/a' \
|
||||
$'# Reads an external thing.\nunknown tests/a\n' tests/a || return 1
|
||||
run_parser_fixture mixed-hermetic 'hermetic must appear alone on tests/a' \
|
||||
$'# Uses the network.\nhermetic,network tests/a\n' tests/a || return 1
|
||||
run_parser_fixture unsorted-paths 'paths are not lexicographically sorted' \
|
||||
$'hermetic tests/b\nhermetic tests/a\n' tests/a tests/b || return 1
|
||||
run_parser_fixture uncommented-non-hermetic \
|
||||
'tests/a is non-hermetic but lacks a directly preceding comment' \
|
||||
$'network tests/a\n' tests/a || return 1
|
||||
}
|
||||
|
||||
[[ -r "$manifest" ]] || {
|
||||
printf 'contract manifest: %s is missing\n' "$manifest" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
discover_contracts
|
||||
declare -A discovered_paths=()
|
||||
for path in "${discovered_contracts[@]}"; do
|
||||
discovered_paths["$path"]=1
|
||||
done
|
||||
|
||||
status=0
|
||||
validate_manifest "$manifest" discovered_paths || status=1
|
||||
run_parser_fixtures || status=1
|
||||
|
||||
(( status == 0 )) || exit 1
|
||||
|
||||
printf 'contract manifest: PASS (%d discovered; %d manifested)\n' \
|
||||
"${#discovered_contracts[@]}" "${#discovered_paths[@]}"
|
||||
@@ -1,184 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# The desktop-hijacking ledger is complete, and `panama test --safe` obeys it.
|
||||
#
|
||||
# `panama test --safe` exists so the suite can be run from inside the session it
|
||||
# tests. That promise is only as good as tests/desktop-hijacking: a contract
|
||||
# that takes over the live shell and is not listed there is run by --safe, and
|
||||
# the desktop goes away in the middle of somebody's work -- with the command
|
||||
# line having just claimed it would not.
|
||||
#
|
||||
# A hand-kept list decays, so it is not trusted on its own. This sweeps tests/
|
||||
# for the shapes a hijacking contract has and fails on any that are missing from
|
||||
# the ledger. The heuristics live here, in the thing that runs, so a new
|
||||
# hijacking contract cannot stay unlisted quietly:
|
||||
#
|
||||
# * it calls `qs ipc call` without booting its own `qs -p` harness, so the
|
||||
# instance answering is the shell you are looking at;
|
||||
# * it restarts panama-quickshell.service;
|
||||
# * it calls a bare `qs kill`, which kills that same shell.
|
||||
#
|
||||
# The sweep is one direction only. The ledger is deliberately larger than what
|
||||
# these three shapes find -- a contract that rotates the real monitor or
|
||||
# rewrites a real xdg-mime default hijacks the session just as thoroughly and
|
||||
# looks like nothing in particular from the outside -- so an entry the sweep
|
||||
# does not reach is not a finding.
|
||||
#
|
||||
# Nothing here runs a listed contract. The one live check drives `--safe` with a
|
||||
# pattern that matches only ledger entries, so the run selects them, skips them
|
||||
# all, and executes nothing.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
ledger="$repo_dir/tests/desktop-hijacking"
|
||||
panama="$repo_dir/bin/panama"
|
||||
|
||||
findings=()
|
||||
note() { findings+=("$1"); }
|
||||
|
||||
[[ -r "$ledger" ]] || { printf 'desktop hijacking contract: %s is missing\n' "$ledger" >&2; exit 1; }
|
||||
|
||||
# ── The entries ──────────────────────────────────────────────────────────────
|
||||
#
|
||||
# Every line names a contract that exists AND that `panama test` would collect.
|
||||
# A listed path the runner never picks up (no executable bit, not a *_test.py)
|
||||
# is skipped by --safe in name only, which reads as protection and is not.
|
||||
|
||||
entries=()
|
||||
commented=()
|
||||
pending_comment=0
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
case "$line" in
|
||||
'#'*) pending_comment=1; continue ;;
|
||||
''|[[:space:]]*'') ;;
|
||||
esac
|
||||
trimmed="${line%%#*}"
|
||||
trimmed="${trimmed#"${trimmed%%[![:space:]]*}"}"
|
||||
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
|
||||
if [[ -z "$trimmed" ]]; then
|
||||
[[ -z "$line" ]] && pending_comment=0
|
||||
continue
|
||||
fi
|
||||
entries+=("$trimmed")
|
||||
commented+=("$pending_comment")
|
||||
pending_comment=0
|
||||
done < "$ledger"
|
||||
|
||||
(( ${#entries[@]} > 0 )) || note 'the ledger lists no contracts at all'
|
||||
|
||||
for index in "${!entries[@]}"; do
|
||||
entry="${entries[$index]}"
|
||||
path="$repo_dir/$entry"
|
||||
|
||||
[[ "$entry" == tests/* ]] \
|
||||
|| note "\"$entry\" is not a repo-relative path under tests/"
|
||||
|
||||
if [[ ! -e "$path" ]]; then
|
||||
note "the ledger lists $entry, which does not exist"
|
||||
continue
|
||||
fi
|
||||
|
||||
[[ -x "$path" || "$entry" == *_test.py ]] \
|
||||
|| note "$entry is listed but 'panama test' would never collect it, so skipping it protects nothing"
|
||||
|
||||
# The ledger's whole job is saying what a contract does to the session. An
|
||||
# entry with no comment is a path somebody has to go and read.
|
||||
(( commented[index] )) \
|
||||
|| note "$entry is listed with no comment saying what it does to the live session"
|
||||
done
|
||||
|
||||
duplicates="$(printf '%s\n' "${entries[@]}" | sort | uniq -d)"
|
||||
[[ -z "$duplicates" ]] || note "the ledger lists these twice: ${duplicates//$'\n'/, }"
|
||||
|
||||
# ── The honesty sweep ────────────────────────────────────────────────────────
|
||||
#
|
||||
# Whole-line comments are dropped first: contracts discuss `qs ipc call` in
|
||||
# their headers, and a header is not a call. What survives is matched only at a
|
||||
# command position -- start of line, or after a pipe, semicolon, &&, (, or ! --
|
||||
# so the same words quoted inside a grep pattern or a failure message do not
|
||||
# count as driving anything.
|
||||
|
||||
command_position='(^|[|;&({!]|\$\()[[:space:]]*'
|
||||
|
||||
is_listed() {
|
||||
local candidate="$1" listed
|
||||
for listed in "${entries[@]}"; do
|
||||
[[ "$listed" == "$candidate" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
while IFS= read -r file; do
|
||||
[[ -x "$file" || "$file" == *_test.py ]] || continue
|
||||
|
||||
code="$(grep -v '^[[:space:]]*#' "$file")"
|
||||
rel="tests/${file#"$repo_dir"/tests/}"
|
||||
reason=""
|
||||
|
||||
if grep -qE "${command_position}qs[[:space:]]+ipc[[:space:]]+call" <<<"$code"; then
|
||||
# Its own harness means its own Quickshell instance: `qs -p <entry>`
|
||||
# addresses that root, not the shell running the desktop.
|
||||
grep -qE "${command_position}[A-Za-z_]*[[:space:]]*=?[[:space:]]*.*qs[[:space:]]+-p" <<<"$code" \
|
||||
|| reason='calls `qs ipc call` without booting its own `qs -p` harness'
|
||||
fi
|
||||
|
||||
if [[ -z "$reason" ]] && grep -qE "${command_position}systemctl.*restart.*panama-quickshell\.service" <<<"$code"; then
|
||||
reason='restarts panama-quickshell.service'
|
||||
fi
|
||||
|
||||
if [[ -z "$reason" ]] && grep -qE "${command_position}qs[[:space:]]+kill" <<<"$code"; then
|
||||
reason='calls a bare `qs kill`, which stops the live shell'
|
||||
fi
|
||||
|
||||
[[ -n "$reason" ]] || continue
|
||||
is_listed "$rel" \
|
||||
|| note "$rel $reason, but is not in tests/desktop-hijacking"
|
||||
done < <(find "$repo_dir/tests" -type f -not -path '*/fixtures/*' -not -path '*__pycache__*' | sort)
|
||||
|
||||
# ── --safe actually reads it ─────────────────────────────────────────────────
|
||||
#
|
||||
# Static first, because the summary line is the only thing telling a reader that
|
||||
# anything was left out, and a --safe run that silently skips is worse than one
|
||||
# that does not skip at all.
|
||||
|
||||
if [[ ! -r "$panama" ]]; then
|
||||
note 'bin/panama is missing'
|
||||
elif ! test_body="$(sed -n '/^cmd_test()/,/^}/p' "$panama")" || [[ -z "$test_body" ]]; then
|
||||
note 'cmd_test could not be found in bin/panama'
|
||||
else
|
||||
grep -q -- '--safe' <<<"$test_body" \
|
||||
|| note 'cmd_test does not handle --safe'
|
||||
grep -qE 'DESKTOP_HIJACKING_LEDGER|desktop-hijacking' <<<"$test_body" \
|
||||
|| note 'cmd_test never consults the desktop-hijacking ledger, so --safe skips nothing'
|
||||
grep -qF 'desktop-hijacking contract(s)' <<<"$test_body" \
|
||||
|| note '--safe no longer reports how many contracts it skipped'
|
||||
fi
|
||||
|
||||
grep -qF 'tests/desktop-hijacking' "$panama" \
|
||||
|| note 'bin/panama never names tests/desktop-hijacking'
|
||||
|
||||
# Then for real. The pattern is the first ledger entry with its 'tests/' prefix
|
||||
# removed, which cmd_test matches against the full path -- so it selects that
|
||||
# one contract, --safe removes it, and nothing is left to run. A --safe that
|
||||
# ignored the ledger would run it instead, which is the failure this catches.
|
||||
if (( ${#entries[@]} > 0 )) && [[ -x "$panama" ]]; then
|
||||
probe="${entries[0]#tests/}"
|
||||
output="$("$panama" test --safe "$probe" 2>&1)"
|
||||
status=$?
|
||||
if (( status == 0 )); then
|
||||
note "'panama test --safe $probe' ran a ledger-listed contract instead of skipping it"
|
||||
elif ! grep -qF 'desktop-hijacking' <<<"$output"; then
|
||||
note "'panama test --safe $probe' refused without mentioning the ledger: $output"
|
||||
fi
|
||||
fi
|
||||
|
||||
if (( ${#findings[@]} > 0 )); then
|
||||
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
|
||||
printf 'desktop hijacking contract: %d finding(s)\n' "${#findings[@]}" >&2
|
||||
printf ' - %s\n' "${findings[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'desktop hijacking contract: PASS (%d contracts listed; the sweep found none unlisted)\n' "${#entries[@]}"
|
||||
Reference in New Issue
Block a user