From 43d1e158585a729cda0efdc917f7fb478b0c0dcc Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Wed, 26 Aug 2026 22:00:52 -0400 Subject: [PATCH] Test: Harden manifest validation --- tests/contracts.manifest | 88 +++++++++++++++++--------- tests/setup/contract-manifest-contract | 34 ++++++++-- 2 files changed, 87 insertions(+), 35 deletions(-) diff --git a/tests/contracts.manifest b/tests/contracts.manifest index 8167f65..4e512c4 100644 --- a/tests/contracts.manifest +++ b/tests/contracts.manifest @@ -17,66 +17,83 @@ 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. +# Pushes a fixture privacy state into the running shell and opens the activity +# panel 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. +# Pushes fixture privacy states and Signal Glass events into the running shell, +# and 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. +# Reads the host GNOME accent enum through gsettings to verify Panama's accent +# mapping. 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. +# Reads the host PipeWire application-volume state through the live Quickshell +# service. 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. +# Opens the live agenda popover and notification centre, and starts a real focus +# session 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. +# Queries the host calendar helper for real configured sources and upcoming +# events. 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. +# Reads the host Quickshell networking type metadata to verify the live +# NetworkManager 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. +# Maps the Control Center and reads its live compositor layer to verify the +# window 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. +# Maps the Control Center service surface and reads the live compositor while +# exercising 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. +# Reads the host xdg-mime default handlers for the configured application +# families. 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. +# Changes the real default browser through the default-apps helper -- xdg-mime +# and 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. +# Reads the host disk and filesystem snapshot through the production disks +# helper. 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. +# Changes the real monitor's mode, position, scale and rotation through the live +# compositor, 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. +# Reads Hyprland option descriptions from the live compositor to verify enum +# mappings. 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. +# Starts, pauses, reveals and ends a real focus session, and opens Mission +# Control 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. +# 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. 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. +# Restarts panama-quickshell.service (or `qs kill`s the shell when unsupervised) +# mid-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 @@ -106,7 +123,8 @@ 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. +# Maps the My Home Settings surface and reads the live compositor during the +# settings flow. live-compositor,live-desktop tests/quickshell/my-home-settings-contract hermetic tests/quickshell/network-tools-contract hermetic tests/quickshell/notification-app-rules-contract @@ -115,12 +133,14 @@ 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. +# Spawns a real kitty window, moves it between your workspaces and into the +# scratchpad, 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. +# Queries the live Quickshell overview-actions IPC surface for window action +# availability. live-host tests/quickshell/overview-window-actions-contract hermetic tests/quickshell/palette-contract hermetic tests/quickshell/panama-action-contract @@ -133,7 +153,8 @@ 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. +# Maps the Phone page test-shell surface and reads the live compositor during +# the 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 @@ -151,7 +172,8 @@ hermetic tests/quickshell/qmldir-registration-contract 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. +# Opens the live screen-intelligence and capture overlays and runs an analysis +# through them. live-desktop tests/quickshell/screen-intelligence-contract hermetic tests/quickshell/screen-intelligence-helper-contract hermetic tests/quickshell/search-routing-contract @@ -162,24 +184,30 @@ 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. +# Flips real compositor policy -- gaps, blur, inactive opacity, keyboard layout +# -- 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. +# Maps Settings pages through a test shell and reads the live compositor during +# page 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). +# Applies compositor policy through the production write path against the live +# compositor, and writes preferences to the real settings store (no isolated +# config 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. +# Opens the real Settings window, routes it between pages, and closes it through +# the 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. +# Flips every compositor-backed setting to a value it does not hold, one at a +# time, 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 @@ -192,7 +220,8 @@ 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. +# Reads the host SSH-agent and key-management availability while keeping test +# keys 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 @@ -223,7 +252,8 @@ 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. +# Contacts the configured package sources through dnf and Flathub availability +# checks. network tests/setup/extras-contract hermetic tests/setup/firefox-chrome-contract hermetic tests/setup/hardware-contract diff --git a/tests/setup/contract-manifest-contract b/tests/setup/contract-manifest-contract index 1106f9b..eb26af0 100755 --- a/tests/setup/contract-manifest-contract +++ b/tests/setup/contract-manifest-contract @@ -21,7 +21,7 @@ discover_contracts() { validate_manifest() { local candidate="$1" local -n expected_contracts="$2" - local line capabilities path extra previous_was_comment=0 + local line capabilities path extra previous_comment="" previous_was_comment=0 local -a capability_list=() local -A manifest_paths=() capability_counts=() local previous_path="" @@ -31,11 +31,15 @@ validate_manifest() { while IFS= read -r line || [[ -n "$line" ]]; do if [[ "$line" =~ ^[[:space:]]*# ]]; then + previous_comment="${line#*#}" + previous_comment="${previous_comment#"${previous_comment%%[![:space:]]*}"}" + previous_comment="${previous_comment%"${previous_comment##*[![:space:]]}"}" previous_was_comment=1 continue fi if [[ "$line" =~ ^[[:space:]]*$ ]]; then + previous_comment="" previous_was_comment=0 continue fi @@ -43,6 +47,7 @@ validate_manifest() { 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_comment="" previous_was_comment=0 continue fi @@ -81,9 +86,14 @@ validate_manifest() { 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" + if [[ "$capabilities" != hermetic ]]; then + if [[ "$previous_was_comment" -ne 1 ]]; then + validation_note "$path is non-hermetic but lacks a directly preceding comment" + elif [[ -z "$previous_comment" ]]; then + validation_note "$path is non-hermetic but lacks a non-empty directly preceding comment" + fi fi + previous_comment="" previous_was_comment=0 done < "$candidate" @@ -108,8 +118,12 @@ validate_manifest() { return 0 } +cleanup_fixture() { + [[ -n "${fixture:-}" ]] && rm -f -- "$fixture" +} + run_parser_fixture() { - local label="$1" expected_message="$2" contents="$3" output fixture + local label="$1" expected_message="$2" contents="$3" output fixture="" shift 3 local -A fixture_paths=() local fixture_path @@ -118,13 +132,18 @@ run_parser_fixture() { done fixture="$(mktemp)" + trap cleanup_fixture EXIT + trap 'cleanup_fixture; exit 130' INT + trap 'cleanup_fixture; exit 143' TERM 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" + cleanup_fixture + trap - EXIT INT TERM return 1 fi - rm -f "$fixture" + cleanup_fixture + trap - EXIT INT TERM if ! grep -Fq "$expected_message" <<<"$output"; then printf 'contract manifest: parser fixture %s did not name %q: %s\n' \ @@ -149,6 +168,9 @@ run_parser_fixtures() { 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 + run_parser_fixture blank-comment \ + 'tests/a is non-hermetic but lacks a non-empty directly preceding comment' \ + $'#\nnetwork tests/a\n' tests/a || return 1 } [[ -r "$manifest" ]] || {