#!/bin/sh
# script/test: run the test suite.
#
# -timeout is applied by `go test` per package, not to the run as a whole, so
# it only has to clear the slowest single package. When this budget was set
# that was internal/handlers, measured in a cache-defeated builder stage on the
# 48-core shared build host (2026-08-18); load- and host-dependent, not
# invariants:
#
#   16.9s                  host load 5-20, GOMAXPROCS 48
#   45.9s / 47.3s / 49.0s  three runs at deliberate host load 31-73
#   30.6s / 39.7s          host load 5-20, GOMAXPROCS 6 / 4
#   67.3s / 97.5s          host load 5-20, GOMAXPROCS 2 / 1
#   67.3s                  GOMAXPROCS 4 at deliberate host load 52-68
#
# The old 30s budget was breached by every loaded run and by every GOMAXPROCS
# at or below 6; at GOMAXPROCS 4 it failed outright ("panic: test timed out
# after 30s"), reproduced on 33e4fa4 with no other change.
#
# 90s matches the org-wide backstop in REPO_POLICIES.md and is sized here
# against the figures above: the worst case under native parallelism is 49.0s,
# and the compound GOMAXPROCS-4-under-load case at 67.3s sits at 75% of it.
# The one figure above 90s is GOMAXPROCS 1, a synthetic core floor rather than
# a condition CI runs under. If a CPU-limited runner ever puts a real run near
# 67s, that is the datum to revisit the org figure with.
#
# Those figures predate tests hashing the admin password at 1 MB instead of
# 64 MB (https://git.eeqj.de/sneak/webhooker/pulls/404). After that change, in
# a cache-defeated build at host load 44-109 (2026-10-02), internal/handlers
# took 8.5s and the slowest package was internal/database at 15.8s. Once its
# retention tests seeded 50 rows per insert instead of 500
# (https://git.eeqj.de/sneak/webhooker/issues/198), internal/database took
# 7.3s and the slowest package was internal/handlers at 8.1s to 10.0s, at host
# load 25-48 (2026-10-02).
#
# -p 4 -parallel 8 keep the run under 2 GB of memory: at most four test
# binaries build or run at once, each with at most eight parallel tests. Under
# -race every test binary and every link costs a few hundred MB, so the
# defaults (one per core) add up to several GB on a many-core host.
#
# The first run has no -v: go test then prints one result line per package,
# with its coverage, and for a package that fails, everything its tests wrote,
# application log lines included. Verbose output from the whole suite passes
# the 2 MiB at which the Docker build cuts off each step's log, so on a failure
# only the tests that failed run again, with -v. The script exits 1 after that
# rerun whatever its result: the first run already showed the suite is broken.
set -eu

ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"

main() {
    cd "$ROOT"
    "$ROOT/script/assets"

    log="$(mktemp -t webhooker-test.XXXXXXXX)"
    rcfile="$(mktemp -t webhooker-test-rc.XXXXXXXX)"
    trap 'rm -f "$log" "$rcfile"' EXIT INT TERM

    # The pipeline's status is tee's, and POSIX sh has no pipefail, so go
    # test's status travels via a file. Output still streams live.
    {
        go test -race -cover -p 4 -parallel 8 -timeout 90s ./... 2>&1 \
            && echo 0 >"$rcfile" || echo $? >"$rcfile"
    } | tee "$log"
    if [ "$(cat "$rcfile")" -eq 0 ]; then
        return
    fi

    # go test reports a failed test as a line starting "--- FAIL: TestName"
    # (a failed subtest's line is indented, and reruns with its parent), and
    # a failed package as "FAIL<tab>package/path<tab>...". A failure that
    # names no test, such as a build error or a timeout, is already shown in
    # full above, so there is nothing to rerun.
    tests="$(awk '/^--- FAIL: / { print $3 }' "$log" | paste -s -d '|' -)"
    packages="$(awk '/^FAIL\t/ { print $2 }' "$log")"
    if [ -n "$tests" ]; then
        echo "--- Rerunning the failed tests with -v for details ---"
        go test -race -v -p 4 -parallel 8 -timeout 90s \
            -run "^($tests)\$" $packages || true
    fi
    exit 1
}

main "$@"
