#!/usr/bin/env bash # ci/tests/touched-requirements-base.test.sh — regression tests for BASE-REF RESOLUTION # in ci/touched-requirements.sh. # # THE BUG THESE LOCK DOWN (2026-08-31, local/CI divergence): # `make -C ci gate` on a feature branch printed # "no changed files vs base; empty touched-requirement set" # and then "MERGE GATE: PASS" — while CI, with a correct base, computed 17 touched # requirements and BLOCKED on real coverage debt. Root cause: EVERY way of failing to # establish a diff base degraded silently to an EMPTY touched set and exit 0. An empty # set means conformance/run.sh is invoked without `--requirements`, which means its # coverage `--require` gate never runs — so the gate reported PASS having checked # nothing. A false green is worse than a red. # # The fix: an undeterminable base is a LOUD exit 3 (ci/README.md's SKIPPED code) with # EMPTY stdout and a machine-readable reason code, never exit 0. These tests assert # that for each way the base can be undeterminable, and assert the good paths still work. # # The tests point the real script at THROWAWAY git repos via the REPO_ROOT override # (ci/lib.sh honours a pre-set REPO_ROOT); the script runs from its true location so it # still sources the real lib.sh. No network, no cluster, and the working repo is never # touched. # # Run: ci/tests/touched-requirements-base.test.sh (or `make -C ci test`) set -uo pipefail # NOT -e: these tests deliberately run a script that exits non-zero. CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SCRIPT="${CI_DIR}/touched-requirements.sh" PASS=0 FAIL=0 ok() { printf ' ok %s\n' "$*"; PASS=$((PASS+1)); } bad() { printf ' FAIL %s\n' "$*" >&2; FAIL=$((FAIL+1)); } TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT # newrepo — a throwaway repo with one baseline commit on `main`. newrepo() { local d="$1" mkdir -p "$d/notes" git -C "$d" init -q -b main git -C "$d" config user.email t@t.t git -C "$d" config user.name t git -C "$d" config commit.gpgsign false # throwaway: never sign (ignore host gpg config) echo baseline > "$d/notes/base.txt" git -C "$d" add -A && git -C "$d" commit -qm baseline } # commit_touching — a commit whose content declares an R-ID. commit_touching() { local d="$1" text="$2" printf 'name: %s\n' "$text" > "$d/notes/decl.yaml" git -C "$d" add -A && git -C "$d" commit -qm change } # Runs the script in ; sets RC, OUT (stdout), ERRTXT (stderr), REASON. run_in() { local d="$1"; shift local reason_file="${TMP}/reason.$$" : > "$reason_file" OUT="$(REPO_ROOT="$d" TOUCHED_REASON_FILE="$reason_file" "$@" "$SCRIPT" 2>"${TMP}/err.$$")" RC=$? ERRTXT="$(cat "${TMP}/err.$$")" REASON="$(cat "$reason_file")" } # assert_refusal