pre no longer builds a bare serve.git mirror, moves refs or sets allowAnySHA1InWant. The repo-local insteadOf now rewrites the official URLs directly to --source, so updates follow the fork's main (force-push it to the branch under test). post still undoes the redirect via the clone/snapshot restore. --ref is removed: neither hermes update nor the tester's pre-branch desktop can follow another branch through config the kit could set without the mirror. pre now ls-remotes --source's main before writing anything, so a bad source aborts with nothing done. The stale global-redirect cleanup is dropped: it only matched serve.git paths.
186 lines
10 KiB
Bash
Executable File
186 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Smoke test for hermes-update-rehearsal.sh (this directory).
|
|
# Builds a synthetic install in a temp tree and drives pre/post/status for real.
|
|
# Runs on macOS/Linux directly, and under git-bash on Windows (native paths).
|
|
set -uo pipefail
|
|
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
SCRIPT="$HERE/hermes-update-rehearsal.sh"
|
|
[ -f "$SCRIPT" ] || { echo "missing $SCRIPT"; exit 1; }
|
|
|
|
echo "--- bash -n ---"
|
|
bash -n "$SCRIPT" && echo "syntax OK" || exit 1
|
|
|
|
# Native paths, so native git.exe/python.exe can reach the same files bash can.
|
|
if command -v cygpath >/dev/null 2>&1; then ROOT="$(cygpath -m "$(mktemp -d)")"; else ROOT="$(mktemp -d)"; fi
|
|
export HOME="$ROOT/home"; mkdir -p "$HOME"
|
|
export GIT_CONFIG_GLOBAL="$ROOT/gitconfig-test"; : > "$GIT_CONFIG_GLOBAL"
|
|
export HERMES_HOME="$ROOT/home/.hermes"
|
|
export HERMES_DESKTOP_USER_DATA_DIR="$ROOT/electron-user-data"
|
|
export HERMES_STOP_LOG="$ROOT/gateway-stop.log"
|
|
|
|
H="$HERMES_HOME"; INSTALL="$H/hermes-agent"
|
|
|
|
echo "--- fixture under $ROOT ---"
|
|
mkdir -p "$H/plugins/mnemosyne-wrapper" "$H/photon/sidecar/node_modules" "$H/memories"
|
|
mkdir -p "$H/skills/foo" "$H/cron" "$H/logs" "$ROOT/external-mnemosyne"
|
|
printf 'name: mnemosyne-wrapper\n' > "$H/plugins/mnemosyne-wrapper/plugin.yaml"
|
|
printf '{"wrapper":true}\n' > "$H/plugins/mnemosyne-wrapper/mnemosyne-wrapper.json"
|
|
printf 'witness\n' > "$ROOT/external-mnemosyne/witness.txt"
|
|
ln -sfn "$ROOT/external-mnemosyne" "$H/plugins/mnemosyne-wrapper/runtime"
|
|
printf 'timezone: utc\n' > "$H/config.yaml"
|
|
printf 'NOUS_API_KEY=xxx\n' > "$H/.env"
|
|
printf '{"tokens":{}}\n' > "$H/auth.json"
|
|
printf 'recall\n' > "$H/memories/note.md"
|
|
printf 'jobs\n' > "$H/cron/jobs.json"
|
|
printf 'console.log(1)\n' > "$H/photon/sidecar/index.mjs"
|
|
printf '{"name":"sidecar"}\n' > "$H/photon/sidecar/package.json"
|
|
printf '{"lockfileVersion":3}\n' > "$H/photon/sidecar/package-lock.json"
|
|
printf '{"lockfileVersion":3}\n' > "$H/photon/sidecar/node_modules/.package-lock.json"
|
|
mkdir -p "$H/cache/scratch"
|
|
printf 'throwaway\n' > "$H/cache/scratch/keep.txt"
|
|
# Old directory times, so a clone that stamps directories with "now" is caught.
|
|
touch -t 202001010000 "$H/memories" "$H/skills/foo" "$H/skills"
|
|
|
|
python - "$H/state.db" <<'PY'
|
|
import sqlite3, sys
|
|
con = sqlite3.connect(sys.argv[1])
|
|
con.execute("CREATE TABLE sessions (id TEXT PRIMARY KEY)")
|
|
con.execute("CREATE TABLE messages (id TEXT)")
|
|
con.executemany("INSERT INTO sessions VALUES (?)", [("s1",), ("s2",)])
|
|
con.commit(); con.close()
|
|
PY
|
|
|
|
# the install checkout: a real repo on branch `main`, plus untracked runtime
|
|
git init -q -b main "$INSTALL"
|
|
git -C "$INSTALL" config user.email t@example.com
|
|
git -C "$INSTALL" config user.name test
|
|
printf 'print(1)\n' > "$INSTALL/module_name.py"
|
|
git -C "$INSTALL" add -A
|
|
git -C "$INSTALL" -c commit.gpgsign=false commit -qm initial
|
|
git -C "$INSTALL" remote add origin https://github.com/NousResearch/hermes-agent.git
|
|
HEAD_SHA="$(git -C "$INSTALL" rev-parse HEAD)"
|
|
mkdir -p "$INSTALL/.hermes/bin" "$INSTALL/.hermes-runtime/python"
|
|
# A fake launcher for the backup and profile-scoped gateway stop.
|
|
cat > "$INSTALL/.hermes/bin/hermes" <<'SH'
|
|
#!/bin/sh
|
|
if [ "$1" = backup ] && [ "$2" = -o ]; then printf 'fake-zip\n' > "$3"; exit 0; fi
|
|
if [ "$1" = gateway ] && [ "$2" = stop ]; then printf '%s\n' "$HERMES_HOME" > "$HERMES_STOP_LOG"; exit 0; fi
|
|
exit 1
|
|
SH
|
|
chmod +x "$INSTALL/.hermes/bin/hermes"
|
|
printf 'big' > "$INSTALL/.hermes-runtime/python/interpreter.bin"
|
|
mkdir -p "$HOME/.local/bin"
|
|
ln -sfn "$INSTALL/.hermes/bin/hermes" "$HOME/.local/bin/hermes"
|
|
SYMLINKS_OK=0
|
|
[ -L "$HOME/.local/bin/hermes" ] && SYMLINKS_OK=1
|
|
echo "host symlink support: $SYMLINKS_OK"
|
|
|
|
mkdir -p "$HERMES_DESKTOP_USER_DATA_DIR/Local Storage/leveldb" "$HERMES_DESKTOP_USER_DATA_DIR/Cache"
|
|
printf '{"window":{}}\n' > "$HERMES_DESKTOP_USER_DATA_DIR/Preferences"
|
|
printf '{"session":"t"}\n' > "$HERMES_DESKTOP_USER_DATA_DIR/connection.json"
|
|
printf 'x' > "$HERMES_DESKTOP_USER_DATA_DIR/Local Storage/leveldb/000001.ldb"
|
|
printf 'junk' > "$HERMES_DESKTOP_USER_DATA_DIR/Cache/data.bin"
|
|
|
|
BACKUPS="$ROOT/backups"
|
|
RUN=("$SCRIPT")
|
|
pass=0; fail=0
|
|
check() { if [ "$1" = 0 ]; then echo " PASS $2"; pass=$((pass+1)); else echo " FAIL $2"; fail=$((fail+1)); fi; }
|
|
# `cmd | grep -q` loses to SIGPIPE under pipefail: capture, then grep.
|
|
check_out() { local n="$1" m="$2"; shift 2; local out; out="$("$@" 2>&1 || true)"; grep -q "$n" <<< "$out"; check $? "$m"; }
|
|
|
|
echo
|
|
echo "--- pre (source = the fixture repo, so no network) ---"
|
|
STATUS_BEFORE="$(git -C "$INSTALL" status --porcelain)"
|
|
# A pristine copy of both trees: post is checked against it for exactness.
|
|
mkdir -p "$ROOT/pristine/home" "$ROOT/pristine/userdata"
|
|
cp -a "$H/." "$ROOT/pristine/home/"
|
|
cp -a "$HERMES_DESKTOP_USER_DATA_DIR/." "$ROOT/pristine/userdata/"
|
|
"${RUN[@]}" pre --source "$INSTALL" --backup-root "$BACKUPS" > "$ROOT/pre.log" 2>&1
|
|
check $? "pre exits 0"
|
|
SNAP="$(ls -1d "$BACKUPS"/*/ | head -1)"; SNAP="${SNAP%/}"
|
|
for f in hermes-backup.zip home manifest.json hermes-home.txt target-sha; do
|
|
[ -e "$SNAP/$f" ]; check $? "backup artifact $f"
|
|
done
|
|
[ -f "$SNAP/home/hermes-agent/.git/config" ]; check $? "whole home: checkout .git in the clone"
|
|
[ -f "$SNAP/home/hermes-agent/.hermes-runtime/python/interpreter.bin" ]; check $? "whole home: PM store in the clone"
|
|
[ -f "$SNAP/home/config.yaml" ]; check $? "whole home: config.yaml in the clone"
|
|
[ ! -e "$SNAP/home/cache" ]; check $? "cache/ left out of the clone"
|
|
[ -f "$SNAP/userdata/Cache/data.bin" ]; check $? "whole userData: nothing filtered out of the clone"
|
|
grep -q 'clone_mode' "$SNAP/manifest.json"; check $? "manifest records the clone mode"
|
|
echo " clone mode: $(sed -n 's/.*"clone_mode": "\(.*\)",/\1/p' "$SNAP/manifest.json")"
|
|
same_mtime() { ! [ "$1" -nt "$2" ] && ! [ "$2" -nt "$1" ]; }
|
|
same_mtime "$SNAP/home/memories" "$H/memories" && same_mtime "$SNAP/home/skills/foo" "$H/skills/foo"
|
|
check $? "clone keeps directory mtimes"
|
|
! grep -q 'fast clone .* failed' "$ROOT/pre.log"; check $? "no clone fell back to the slow path"
|
|
|
|
echo
|
|
echo "--- pre points the install at the rehearsal copy ---"
|
|
[ "$(git -C "$INSTALL" config --local --get-regexp 'insteadOf' | wc -l | tr -d ' ')" = 2 ]; check $? "two insteadOf entries written (repo-local)"
|
|
[ -f "$H/.skip_upstream_prompt" ]; check $? "upstream-prompt marker created"
|
|
[ "$(git -C "$INSTALL" remote get-url origin)" = "$INSTALL" ]; check $? "remote get-url resolves to --source"
|
|
git -C "$INSTALL" config --get remote.origin.url | grep -q 'NousResearch'; check $? "config --get remote.origin.url stays official"
|
|
|
|
echo
|
|
echo "--- pre changed nothing else ---"
|
|
[ "$(git -C "$INSTALL" rev-parse HEAD)" = "$HEAD_SHA" ]; check $? "checkout untouched by pre"
|
|
[ "$(git -C "$INSTALL" status --porcelain)" = "$STATUS_BEFORE" ]; check $? "pre changed no files in the checkout"
|
|
grep -q 'nothing has been updated yet' "$ROOT/pre.log"; check $? "pre says it did not update anything"
|
|
|
|
echo
|
|
echo "--- status (read-only) ---"
|
|
check_out "$HEAD_SHA" "status reports what it prepared" "${RUN[@]}" status --backup-root "$BACKUPS"
|
|
check_out "clone *present" "status reports the clone present" "${RUN[@]}" status --backup-root "$BACKUPS"
|
|
|
|
echo
|
|
echo "--- simulate an update: modify, add and delete in both trees ---"
|
|
printf 'timezone: changed-by-update\n' > "$H/config.yaml"
|
|
rm "$H/memories/note.md"
|
|
printf 'print(2)\n' > "$INSTALL/added_by_update.py"
|
|
mkdir -p "$H/photon/sidecar/node_modules/newdep"; printf 'x' > "$H/photon/sidecar/node_modules/newdep/index.js"
|
|
printf '{"window":{"changed":true}}\n' > "$HERMES_DESKTOP_USER_DATA_DIR/Preferences"
|
|
printf '{}\n' > "$HERMES_DESKTOP_USER_DATA_DIR/new-after-update.json"
|
|
! diff -r --no-dereference "$ROOT/pristine/home" "$H" >/dev/null 2>&1; check $? "the simulated update changed HERMES_HOME"
|
|
|
|
echo
|
|
echo "--- post ---"
|
|
"${RUN[@]}" post --backup-root "$BACKUPS" --yes > "$ROOT/post.log" 2>&1
|
|
check $? "post exits 0"
|
|
[ "$(cat "$HERMES_STOP_LOG" 2>/dev/null)" = "$H" ]; check $? "post stops only this home's gateway"
|
|
[ -f "$H/config.yaml" ]; check $? "config.yaml restored"
|
|
[ -f "$H/.env" ]; check $? ".env restored"
|
|
[ -f "$H/memories/note.md" ]; check $? "memories restored"
|
|
[ -f "$H/plugins/mnemosyne-wrapper/mnemosyne-wrapper.json" ]; check $? "plugin marker restored"
|
|
[ -f "$H/photon/sidecar/node_modules/.package-lock.json" ]; check $? "photon sidecar marker restored"
|
|
[ -f "$INSTALL/.hermes-runtime/python/interpreter.bin" ]; check $? "PM store restored"
|
|
[ -d "$INSTALL/.git" ]; check $? "checkout restored"
|
|
[ "$(git -C "$INSTALL" rev-parse HEAD)" = "$HEAD_SHA" ]; check $? "checkout HEAD restored"
|
|
[ "$(git -C "$INSTALL" config --get remote.origin.url)" = "https://github.com/NousResearch/hermes-agent.git" ]; check $? "origin remote restored"
|
|
[ -f "$HERMES_DESKTOP_USER_DATA_DIR/connection.json" ]; check $? "userData connection.json restored"
|
|
[ "$(git -C "$INSTALL" config --local --get-regexp 'insteadOf' 2>/dev/null | wc -l | tr -d ' ')" = 0 ]; check $? "no stale insteadOf left in the checkout"
|
|
[ ! -f "$H/.skip_upstream_prompt" ]; check $? "upstream-prompt marker removed"
|
|
git -C "$INSTALL" remote get-url origin | grep -q 'NousResearch'; check $? "origin resolves officially again"
|
|
[ -f "$H/cache/scratch/keep.txt" ]; check $? "cache/ left in place"
|
|
[ -f "$SNAP/hermes-backup.zip" ]; check $? "post keeps hermes-backup.zip"
|
|
[ ! -e "$SNAP/home" ] && [ ! -e "$SNAP/userdata" ]; check $? "post consumed the clones"
|
|
[ ! -e "$SNAP/replaced" ]; check $? "post deleted the post-update trees"
|
|
if [ "$SYMLINKS_OK" = 1 ]; then
|
|
[ -L "$HOME/.local/bin/hermes" ]; check $? "shim outside the two trees left untouched"
|
|
else
|
|
echo " SKIP user-bin shim symlink (host cannot create symlinks)"
|
|
fi
|
|
|
|
echo
|
|
echo "--- the acceptance criterion: every file identical before/after ---"
|
|
diff -r --no-dereference "$ROOT/pristine/home" "$H" > "$ROOT/home.diff" 2>&1
|
|
check $? "HERMES_HOME identical to before pre"
|
|
[ -s "$ROOT/home.diff" ] && sed 's/^/ /' "$ROOT/home.diff" | head -30
|
|
diff -r --no-dereference "$ROOT/pristine/userdata" "$HERMES_DESKTOP_USER_DATA_DIR" > "$ROOT/userdata.diff" 2>&1
|
|
check $? "userData identical to before pre"
|
|
[ -s "$ROOT/userdata.diff" ] && sed 's/^/ /' "$ROOT/userdata.diff" | head -30
|
|
|
|
echo
|
|
echo "=== smoke: $pass passed, $fail failed ==="
|
|
if [ "${SMOKE_KEEP:-0}" = 1 ]; then echo "root kept: $ROOT"; else rm -rf "$ROOT"; fi
|
|
exit $(( fail > 0 ))
|