Files
stack/scripts/test-case2.sh
jason.woltje dca9bc8691 Row 227 R3 (#1728): restore the executable bit on scripts/test-case2.sh
R3 (abc66a598) changed its mode from 100755 to 100644. Every other scripts/test-*.sh is 100755.
The landing gate runs it through bash, so no run broke; this restores the R2 mode.
2026-11-02 16:35:54 -06:00

129 lines
6.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# C2-14 (#1728, row 227): one acceptance run on a scratch root, no skipped steps.
# Entry point for the real launcher/runner, C2-06 scripted adapter, fake TLS Gitea and Console DOM.
# Stands alone: the landing gate runs it as `bash scripts/test-case2.sh` with nothing else set up.
# The only human stand-in is bindHuman in-process; a development agent cannot prove human CLI ancestry.
set -uo pipefail
fatal() { printf 'test-case2: %s\n' "$*" >&2; exit 2; }
cd "$(dirname "$0")/.." || fatal 'cannot enter the repository root; nothing was started'
. scripts/case2/sandbox.sh || fatal 'cannot load scripts/case2/sandbox.sh; nothing was started'
# The sandbox root. Every later path, the kill sweep and the removal hang off it, so nothing is
# derived from it, no trap is set and nothing starts until it is this run's own directory: mktemp
# exited 0 under an absolute TMPDIR, and the result is absolute, not a symlink, an empty directory
# this user owns. It is kept as its physical path, the form a /proc/<pid>/cwd link reads, and its
# device:inode goes into SANDBOX_ID. sweep_sandbox and remove_sandbox check both again themselves.
case "${TMPDIR:-/tmp}" in
/*) ;;
*) fatal "TMPDIR is not an absolute path: $TMPDIR; nothing was started" ;;
esac
made="$(mktemp -d)" || fatal "mktemp -d failed under TMPDIR=${TMPDIR:-/tmp}; nothing was started"
case "$made" in
/?*) ;;
*) fatal "mktemp -d gave '$made', not an absolute path; nothing was started" ;;
esac
[ -d "$made" ] && [ ! -L "$made" ] && [ -O "$made" ] && dir_empty "$made" \
|| fatal "mktemp -d gave $made, not a new empty directory of this user; nothing was started"
SANDBOX="$(cd -P -- "$made" && pwd -P)" && SANDBOX_ID="$(sandbox_id "$SANDBOX")" \
&& sandbox_ok "$SANDBOX" "$SANDBOX_ID" \
|| fatal "cannot resolve the sandbox $made; nothing was started"
readonly SANDBOX SANDBOX_ID
DRIVER_PID=''
alive() {
local stat
read -r stat 2>/dev/null <"/proc/$1/stat" || return 1
stat="${stat##*) }"
[ "${stat%% *}" != Z ]
}
# The driver leads its own process group (setsid below). On a signal it gets SIGTERM and 3 s by the
# clock to close, inside the landing gate's 5 s grace, then its group gets SIGKILL. The launcher
# starts each bwrap session in a group of its own, with its working directory in the sandbox; any
# such process still alive then gets SIGKILL too, before the sandbox goes. sweep_sandbox reads every
# cwd link with one find and compares targets as strings, so the sweep stays in milliseconds however
# many processes the host runs.
now_us() { echo "${EPOCHREALTIME/[.,]/}"; }
stop_driver() {
local deadline
if [ -n "$DRIVER_PID" ]; then
kill -TERM "$DRIVER_PID" 2>/dev/null
deadline=$(( $(now_us) + 3000000 ))
while alive "$DRIVER_PID" && [ "$(now_us)" -lt "$deadline" ]; do sleep 0.1; done
kill -KILL -- "-$DRIVER_PID" 2>/dev/null
wait "$DRIVER_PID" 2>/dev/null
DRIVER_PID=''
fi
sweep_sandbox "$SANDBOX" "$SANDBOX_ID"
}
# A second INT or TERM during cleanup is ignored, so it cannot cut the sweep short; the gate's
# SIGKILL after its grace still ends everything. A refused sweep or removal turns a pass red.
cleanup() {
local result=$?
trap '' INT TERM
trap - EXIT
stop_driver || result=$(( result ? result : 1 ))
remove_sandbox "$SANDBOX" "$SANDBOX_ID" || result=$(( result ? result : 1 ))
exit "$result"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
# The tools socket is the longest socket path the run builds (scripts/case2/sandbox.sh). Refuse a
# TMPDIR that would push it past the product's limit before anything starts.
SOCKET_BYTES=$(byte_length "$(socket_shape "$SANDBOX")")
if [ "$SOCKET_BYTES" -gt "$SOCKET_LIMIT" ]; then
tmpdir_bytes=$(byte_length "${TMPDIR:-/tmp}")
fatal "TMPDIR is too long: the run's tools socket path would be $SOCKET_BYTES bytes, over the" \
"$SOCKET_LIMIT-byte limit in packages/seat/src/session.mjs; TMPDIR is $tmpdir_bytes bytes," \
"at most $(( tmpdir_bytes - SOCKET_BYTES + SOCKET_LIMIT )) fit; nothing was started"
fi
CASE2_HOME="$SANDBOX/home"
CASE2_TMP="$SANDBOX/t"
CASE2_CONFIG="$SANDBOX/root/config/config.json"
mkdir -p -- "$CASE2_HOME" "$CASE2_TMP" || fatal "cannot create $CASE2_HOME and $CASE2_TMP"
# The driver runs from an explicit environment: PATH and LANG, a sandbox HOME, XDG dirs and TMPDIR,
# and the variables it needs. Nothing else from the caller, a credential variable included, reaches
# the driver or the launcher it starts. TMPDIR in the sandbox takes the Chromium profile and the
# plug-in tree with it when the sandbox goes.
CLEAN_ENV=(env -i PATH="$PATH" LANG=C.UTF-8 HOME="$CASE2_HOME"
XDG_CONFIG_HOME="$CASE2_HOME/.config" XDG_CACHE_HOME="$CASE2_HOME/.cache"
XDG_DATA_HOME="$CASE2_HOME/.local/share" XDG_STATE_HOME="$CASE2_HOME/.local/state"
TMPDIR="$CASE2_TMP" MOSAIC_CONFIG="$CASE2_CONFIG" GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1)
PASS=0 FAIL=0
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
C_OK=$'\033[0;32m'; C_FAIL=$'\033[0;31m'; C_RESET=$'\033[0m'
else
C_OK=''; C_FAIL=''; C_RESET=''
fi
check() {
if [ "$2" = 0 ]; then PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} $1"
else FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} $1"; fi
}
DRIVER=scripts/case2/e2e.mjs
STEPS='S1a S1b S2a S2b S2c S2d S3a S3b S3c S4 S5a S5b S6 S7 S8 S9 S10'
"${CLEAN_ENV[@]}" node --check "$DRIVER" >/dev/null 2>&1
check "syntax: $DRIVER" "$?"
OUT="$SANDBOX/steps.txt" ERR="$SANDBOX/stderr.txt"
: >"$OUT" && : >"$ERR" || fatal "cannot create the driver output files in $SANDBOX"
"${CLEAN_ENV[@]}" setsid node "$DRIVER" "$SANDBOX/root" >"$OUT" 2>"$ERR" &
DRIVER_PID=$!
wait "$DRIVER_PID"
RC=$?
DRIVER_PID=''
check "driver ran to the end (exit $RC)" "$RC"
if [ "$RC" -ne 0 ]; then grep '^e2e: ' "$ERR" | head -5; fi
seen=''
while IFS= read -r line; do
case "$line" in
'ok '*) id="${line#ok }"; id="${id%% *}"; check "${line#ok }" 0 ;;
'not ok '*) id="${line#not ok }"; id="${id%% *}"; check "${line#not ok }" 1 ;;
*) id=''; check 'driver line not understood (a skip cannot pass acceptance)' 1 ;;
esac
[ -n "$id" ] && seen="$seen $id"
done <"$OUT"
[ "${seen# }" = "$STEPS" ]
check 'every acceptance step ran once, in order, with nothing skipped' "$?"
# The values come from scripts/case2/fixture-credentials.mjs and are never printed.
"${CLEAN_ENV[@]}" node scripts/case2/leak-check.mjs "$OUT" "$ERR"
check 'fixture credentials are in no driver output' "$?"
printf '\nselftest: %s passed, %s failed, 0 skipped\n' "$PASS" "$FAIL"
[ "$FAIL" -eq 0 ]