#!/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//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 ]