#!/bin/sh
# Test runner for PoM's scheduled suites on astra.
#
# pom.service calls this once a target's last run goes stale. Everything it
# touches lives under /var/lib/pom, the one path the hardened unit can write:
# the clones, the rust toolchain, and the build artifacts. That is deliberate.
# The daemon runs as `pom` with ProtectHome, so max's checkouts and max's
# toolchain are out of reach by design, and this script must not depend on them.
#
# The one exception is the git mirrors it fetches from, which are read-only.
#
# Usage: run-ci.sh <target> [filter]
#   target  one of: mnw mt go bb af sk
#   filter  optional cargo test filter, passed through by PoM
#
# Exit status is the suite's, which is what PoM records as pass/fail.

set -eu

STAGING="/var/lib/pom/staging"
MIRRORS="/home/max/git-mirrors"

# The pom user's own toolchain. /usr/bin/cargo is the system rustc, which
# trails what these dependency trees need.
export HOME="/var/lib/pom"
export RUSTUP_HOME="/var/lib/pom/.rustup"
export CARGO_HOME="/var/lib/pom/.cargo"
export PATH="$CARGO_HOME/bin:$PATH"

# Parallel test databases exhaust the default 1024 descriptors.
ulimit -n 65536 2>/dev/null || true

# Peer auth on the socket means postgres wants an OS username, and sqlx cannot
# work one out inside the service sandbox: it falls back to the `whoami` crate's
# placeholder and every connection is refused as user "anonymous". libpq tools
# and sqlx both honour PGUSER, so say it once here.
export PGUSER=pom

# astra has 96 cores, and cargo's default job count is all of them. Ninety-six
# concurrent rustc processes on the MNW server workspace peaked past 16G and 20G
# of swap before the cgroup killed it. This is a memory cap wearing a job-count
# costume; the wall-clock cost is small next to thrashing.
#
# 8 rather than 16 because MemoryMax applies to the whole pom.service cgroup,
# not per invocation, and a sweep that finds several targets stale at once runs
# them concurrently under that one ceiling. Caches are warm in the steady state,
# so few crates actually rebuild and the job count barely shows up in wall clock.
export CARGO_BUILD_JOBS="${CARGO_BUILD_JOBS:-8}"

# The mirrors are max's, this runs as pom, and git refuses a repo it considers
# foreign. Without this every fetch below fails and the clones silently test
# whatever commit they were created at, forever. Scoped to this process rather
# than written into a global gitconfig.
GIT_CONFIG_COUNT=1
GIT_CONFIG_KEY_0=safe.directory
GIT_CONFIG_VALUE_0='*'
export GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0

target="${1:-}"
filter="${2:-}"

# The layout under $STAGING mirrors ~/Code on purpose, Apps/ included. Nothing
# in git records that tree, so a plain clone does not reproduce it, and the
# Tauri apps have a symlink that reaches across it:
#   Apps/<app>/src-tauri/frontend/js/shared-updater.js
#     -> ../../../../../MNW/shared/tauri-updater-ui/updater.js
# Flatten the tree and that dangles, and the app's build.rs panics on a read.
case "$target" in
    mnw) dir="$STAGING/MNW/server" ;;
    mt)  dir="$STAGING/MNW/multithreaded" ;;
    go)  dir="$STAGING/Apps/goingson" ;;
    bb)  dir="$STAGING/Apps/balanced_breakfast" ;;
    af)  dir="$STAGING/Apps/audiofiles" ;;
    sk)  dir="$STAGING/synckit/synckit-client" ;;
    *)
        echo "run-ci.sh: unknown target '${target}' (want: mnw mt go bb af sk)" >&2
        exit 2
        ;;
esac

if [ ! -d "$dir" ]; then
    echo "run-ci.sh: $dir does not exist, clone it from $MIRRORS first" >&2
    exit 2
fi

# Track the mirror so a run tests current main rather than whatever was cloned
# months ago. A failed fetch is not fatal: testing a slightly old tree beats
# reporting nothing, and the staleness clock keeps ticking either way.
repo_root=$(cd "$dir" && git rev-parse --show-toplevel)
if ! (cd "$repo_root" && git fetch --quiet origin && git reset --quiet --hard origin/HEAD); then
    echo "run-ci.sh: warning, could not update $repo_root, testing the existing checkout" >&2
fi

cd "$dir"
echo "run-ci.sh: $target at $(git rev-parse --short HEAD) in $dir"

# Postgres on astra listens on a unix socket only, so the URLs carry no host.
# Both harnesses default to a TCP URL, which fails to connect here and fails
# every test in the suite within seconds rather than failing loudly once.
# Do not add ?host=... : the harnesses split these on the last '/'.
# Auth is peer on the socket, so the `pom` role needs LOGIN and CREATEDB.
case "$target" in
    mnw)
        # Its own database, not max's `makenotwork`: the sqlx macros are
        # checked at compile time against whatever DATABASE_URL points at, so
        # CI needs a schema it owns and can migrate. Compiling against the
        # committed .sqlx cache instead is not an option worth taking, since
        # the cache is hand-maintained by `cargo sqlx prepare` and goes stale
        # silently the moment a migration lands without one.
        DATABASE_URL='postgres:///pom_ci_makenotwork'
        TEST_DATABASE_URL='postgres:///postgres'
        export DATABASE_URL TEST_DATABASE_URL

        psql "$TEST_DATABASE_URL" -tAc \
            "SELECT 1 FROM pg_database WHERE datname = 'pom_ci_makenotwork'" \
            | grep -q 1 || createdb pom_ci_makenotwork
        sqlx migrate run --source migrations --database-url "$DATABASE_URL"
        ;;
    mt)
        # Two harnesses in one suite. Most tests use the hand-rolled `TestDb`,
        # which reads TEST_DATABASE_URL. The `admin` and `bans` modules use
        # `#[sqlx::test]`, which reads DATABASE_URL and manages its own throwaway
        # databases off it. Set only the first and those 29 fail with
        # "DATABASE_URL must be set", which reads like 29 broken tests rather
        # than one missing variable.
        DATABASE_URL='postgres:///pom_ci_mt'
        TEST_DATABASE_URL='postgres:///postgres'
        export DATABASE_URL TEST_DATABASE_URL

        psql "$TEST_DATABASE_URL" -tAc \
            "SELECT 1 FROM pg_database WHERE datname = 'pom_ci_mt'" \
            | grep -q 1 || createdb pom_ci_mt
        ;;
esac

# --test-threads=4 rather than the default: at full parallelism the suites
# intermittently exhaust postgres connection slots on this box.
if [ -n "$filter" ]; then
    exec cargo test --workspace "$filter" -- --test-threads=4
else
    exec cargo test --workspace -- --test-threads=4
fi
