From 803362ebad7aa37489f2b999d8678fe27a28f9dc Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Wed, 26 Aug 2026 22:46:46 -0400 Subject: [PATCH] Test: Exercise updates through the real command --- tests/setup/update-command-contract | 201 +++++++++++++++++++--------- 1 file changed, 139 insertions(+), 62 deletions(-) diff --git a/tests/setup/update-command-contract b/tests/setup/update-command-contract index 8b6272e..689685c 100755 --- a/tests/setup/update-command-contract +++ b/tests/setup/update-command-contract @@ -37,7 +37,8 @@ tmp="$(mktemp -d -t panama-update-contract.XXXXXX)" trap 'rm -rf "$tmp"' EXIT STAGE_NAMES=(install-packages link-dotfiles link-skills link-user change-settings - link-vicinae-scripts setup-identity install-hardware) + link-vicinae-scripts setup-server link-server setup-identity + install-hardware) # A PANAMA_PATH that looks enough like the real one for install to run, and # records what it was asked to do instead of doing it. @@ -108,14 +109,13 @@ EOF # Run the fixture installer and echo what ran, one stage per line. run_install() { local root="$1"; shift + local status=0 : >"$root/ran" - PATH="$root/shim:$PATH" \ - PANAMA_PATH="$root" \ - PANAMA_RAN="$root/ran" \ - XDG_STATE_HOME="$root/state" \ - bash "$root/install" "$@" >"$root/out" 2>&1 - printf '%s' "$?" >"$root/rc" + PATH="$root/shim:$PATH" PANAMA_PATH="$root" PANAMA_RAN="$root/ran" \ + XDG_STATE_HOME="$root/state" bash "$root/install" "$@" \ + >"$root/out" 2>&1 || status=$? cat "$root/ran" + return "$status" } # ── 1. The interview never runs on an upgrade ──────────────────────────────── @@ -146,12 +146,28 @@ done # A stage added to STAGES without a decision about which path owns it shows up # here, because this list is written down twice on purpose. -mapfile -t declared < <(python3 - "$installer" <<'PY' -import re, sys -line = next(l for l in open(sys.argv[1], encoding="utf-8") if l.startswith("STAGES=")) -print("\n".join(re.findall(r"[\w-]+", line)[1:])) +declared=() +if declared_output="$(python3 - "$installer" <<'PY' +import re +import sys + +found = False +for line in open(sys.argv[1], encoding="utf-8"): + match = re.match(r'^\s*STAGES=\((.*)\)\s*$', line) + if match and "$" not in match.group(1): + found = True + print("\n".join(re.findall(r"[\w-]+", match.group(1)))) +if not found: + print("install has no STAGES assignment", file=sys.stderr) + sys.exit(1) PY -) +)"; then + while IFS= read -r stage; do + [[ -n "$stage" ]] && declared+=("$stage") + done <<<"$declared_output" +else + note 'could not read the install STAGES assignment' +fi for stage in "${declared[@]}"; do printf '%s\n' "${STAGE_NAMES[@]}" | grep -qx "$stage" \ || note "install declares a stage this contract has never heard of: $stage" @@ -177,7 +193,10 @@ grep -qx 'install-packages' <<<"$ran_changed" \ # A failing stage must not record the hash, or the failure is hidden forever. build_fixture "$tmp/c" 1 -run_install "$tmp/c" --upgrade >/dev/null +install_status=0 +run_install "$tmp/c" --upgrade >/dev/null || install_status=$? +[[ "$install_status" -eq 1 ]] \ + || note "install --upgrade returned $install_status instead of the failing stage status" if [[ -r "$tmp/c/state/panama/packages-hash" ]]; then note 'install-packages failed but its hash was recorded, so it will never be retried' fi @@ -191,62 +210,120 @@ grep -qx 'install-packages' <<<"$ran_full" \ # ── 4. A conflicted pop never leaves markers in a live config ──────────────── # -# Two halves. The first checks that git still behaves the way the design -# depends on; the second checks that panama acts on it. Neither is worth much -# without the other. +# The fixture also covers a clean fast-forward, installer status propagation, +# and the boundary between update and sync before forcing the conflict below. -conflict="$tmp/conflict" -mkdir -p "$conflict" -( +# Each fixture has the same three repositories as a real update: a bare remote, +# a clone that publishes upstream changes, and the machine clone being updated. +build_cli_fixture() ( set -e - cd "$conflict" - git init -q up && cd up - git config user.email contract@panama && git config user.name contract - printf 'one\n' >f; git add -A; git commit -qm one - cd "$conflict"; git clone -q up work; cd work - git config user.email contract@panama && git config user.name contract - cd "$conflict/up"; printf 'upstream\n' >f; git commit -qam two - cd "$conflict/work"; printf 'local\n' >f - git stash push --include-untracked -m contract >/dev/null - git pull -q --ff-only - git stash pop >/dev/null 2>&1 && exit 3 # a conflict was the point - git reset -q --hard HEAD - [[ -n "$(git stash list)" ]] || exit 4 # the stash must survive - grep -q '<<<<<<<' f && exit 5 # and no markers may remain - exit 0 -) >/dev/null 2>&1 -case $? in - 0) ;; - 3) note 'the conflict fixture did not conflict, so this check proves nothing' ;; - 4) note 'git no longer keeps the stash after a conflicted pop; panama update would lose work' ;; - 5) note 'git reset --hard left conflict markers behind' ;; - *) note 'the stash conflict fixture could not be built' ;; -esac + local root="$1" + rm -rf "$root" + mkdir -p "$root" + git init -q --bare "$root/origin.git" + git clone -q "$root/origin.git" "$root/upstream" 2>/dev/null + git -C "$root/upstream" config user.email contract@panama + git -C "$root/upstream" config user.name contract -# panama update must act on that: reset the tree rather than leave the markers. -body="$(sed -n '/^cmd_update()/,/^}/p' "$panama")" -if [[ -z "$body" ]]; then - note 'bin/panama has no cmd_update to check' + mkdir -p "$root/upstream/bin" + cp "$panama" "$root/upstream/bin/panama" + cat >"$root/upstream/install" <<'EOF' +#!/usr/bin/env bash +printf '%s\n' "$*" >>"${PANAMA_UPDATE_FIXTURE_LOG:?}" +exit "${PANAMA_UPDATE_INSTALL_RC:-0}" +EOF + chmod +x "$root/upstream/bin/panama" "$root/upstream/install" + printf 'one\n' >"$root/upstream/f" + git -C "$root/upstream" add -A + git -C "$root/upstream" commit -qm initial + git -C "$root/upstream" push -qu origin HEAD + + git clone -q "$root/origin.git" "$root/machine" + git -C "$root/machine" config user.email contract@panama + git -C "$root/machine" config user.name contract +) + +advance_upstream() ( + set -e + local root="$1" file="$2" contents="$3" + printf '%s\n' "$contents" >"$root/upstream/$file" + git -C "$root/upstream" add "$file" + git -C "$root/upstream" commit -qm "update $file" + git -C "$root/upstream" push -q +) + +clean="$tmp/clean-update" +if build_cli_fixture "$clean" && advance_upstream "$clean" release new; then + machine_before="$(git -C "$clean/machine" rev-parse HEAD)" + upstream_after="$(git -C "$clean/upstream" rev-parse HEAD)" + [[ "$machine_before" != "$upstream_after" ]] \ + || note 'the clean update fixture started current, so it cannot prove a fast-forward' + + : >"$clean/install.log" + update_status=0 + PANAMA_UPDATE_FIXTURE_LOG="$clean/install.log" \ + "$clean/machine/bin/panama" update >"$clean/update.out" 2>&1 \ + || update_status=$? + [[ "$update_status" -eq 0 ]] \ + || note "panama update failed on a clean clone with status $update_status" + [[ "$(git -C "$clean/machine" rev-parse HEAD)" == "$upstream_after" ]] \ + || note 'panama update did not fast-forward the clean machine clone' + grep -qx -- '--upgrade' "$clean/install.log" \ + || note 'panama update did not invoke the installer with --upgrade' + + : >"$clean/install.log" + update_status=0 + PANAMA_UPDATE_FIXTURE_LOG="$clean/install.log" PANAMA_UPDATE_INSTALL_RC=23 \ + "$clean/machine/bin/panama" update >"$clean/failing-update.out" 2>&1 \ + || update_status=$? + [[ "$update_status" -eq 23 ]] \ + || note "panama update returned $update_status instead of installer status 23" + grep -qx -- '--upgrade' "$clean/install.log" \ + || note 'the failing update did not reach the fixture installer' + + : >"$clean/install.log" + printf 'local sync\n' >"$clean/machine/synced" + sync_status=0 + printf 'y\ncontract sync\n' \ + | PANAMA_UPDATE_FIXTURE_LOG="$clean/install.log" \ + "$clean/machine/bin/panama" sync >"$clean/sync.out" 2>&1 \ + || sync_status=$? + [[ "$sync_status" -eq 0 ]] \ + || note "panama sync failed in the local fixture with status $sync_status" + [[ ! -s "$clean/install.log" ]] \ + || note 'panama sync invoked the installer; sync and update are separate jobs' else - grep -q 'git stash push' <<<"$body" \ - || note 'cmd_update does not stash local changes, so a pull can fail on a dirty tree' - grep -q 'git reset --hard' <<<"$body" \ - || note 'cmd_update does not reset after a failed pop, so conflict markers reach ~/.config' - grep -q 'git pull --ff-only' <<<"$body" \ - || note 'cmd_update does not pull with --ff-only' - grep -q -- '--upgrade' <<<"$body" \ - || note 'cmd_update does not hand off to install --upgrade, so it would ask the interview' + note 'the clean update fixture could not be built' fi -# The two verbs stay separate: sync must never run the installer. -sync_body="$(sed -n '/^cmd_sync()/,/^}/p' "$panama")" -if [[ -z "$sync_body" ]]; then - note 'bin/panama has no cmd_sync, so the git workflow lost its home' +# A conflicted stash pop must not leave markers in the checkout. The failed pop +# keeps the stash, so the local version remains recoverable after the reset. +conflict="$tmp/conflict-update" +if build_cli_fixture "$conflict"; then + printf 'local\n' >"$conflict/machine/f" + if advance_upstream "$conflict" f upstream; then + : >"$conflict/install.log" + conflict_status=0 + PANAMA_UPDATE_FIXTURE_LOG="$conflict/install.log" \ + "$conflict/machine/bin/panama" update >"$conflict/update.out" 2>&1 \ + || conflict_status=$? + [[ "$conflict_status" -eq 0 ]] \ + || note "panama update failed while recovering a stash conflict with status $conflict_status" + [[ "$(<"$conflict/machine/f")" == upstream ]] \ + || note 'panama update did not reset the conflicted file to the upstream version' + if git -C "$conflict/machine" grep -qE '^(<<<<<<<|=======|>>>>>>>)' -- .; then + note 'panama update left conflict markers in the machine checkout' + fi + [[ -n "$(git -C "$conflict/machine" stash list)" ]] \ + || note 'panama update dropped the stash after its conflicted pop' + recovered="$(git -C "$conflict/machine" show 'stash@{0}:f' 2>/dev/null)" + [[ "$recovered" == local ]] \ + || note 'the stash left by panama update does not contain the local version' + else + note 'the conflict fixture could not publish its upstream edit' + fi else - grep -q 'install' <<<"$sync_body" \ - && note 'cmd_sync runs the installer; committing and updating are separate jobs' - grep -q 'git stash' <<<"$sync_body" \ - && note 'cmd_sync stashes, which the commit-before-pull order exists to avoid' + note 'the conflict update fixture could not be built' fi if (( ${#findings[@]} > 0 )); then