HAND-OFF 1 (make -C ci gate parity). touched-requirements.sh gained a third scoping rule: after stripping the diff marker, whole-line YAML/shell `#` comments are dropped BEFORE the R-ID grep. A requirement cited only in a code-file comment is prose (a REFERENCE), not a declaration -- the same logic rule 2 already applies to `*.md`. This unblocks the self-referential trap where editing gate.yml's header (which cites R-TEST-06, a known-uncovered ID enforced by the spec-immutability STAGE, not a Chainsaw test) re-attributed R-TEST-06 so the coverage `--require` check false-blocked the merge. Conservative: only whole-line comments are dropped; an R-ID on a real code/spec line is still detected, and authoritative declarations never live in a bare comment. gate.yml now invokes the aggregate `make -C ci gate` (ci/gate.sh) instead of the individual stage scripts, so CI runs byte-for-byte the same gate a human runs in the devcontainer (R-TEST-02) and applies the R-TEST-03 decision. A BLOCKED gate exits non-zero and fails the job. The cluster-gated L2/L3 stage SKIPS honestly (no CLUSTER_AVAILABLE). spec-immutability is retained as an explicit early UI step. Adds ci/tests/touched-requirements.test.sh (+ `make -C ci test`) asserting rule 3: comment-only R-IDs (added or removed) are not attributed while real-line R-IDs are. ci/lib.sh REPO_ROOT is now env-overridable so tests can run the real script against a throwaway git repo (default unchanged). Co-Authored-By: Claude Opus 4.8 <[email protected]>
99 lines
4.3 KiB
Bash
Executable File
99 lines
4.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ci/tests/touched-requirements.test.sh — regression tests for the content scan in
|
|
# ci/touched-requirements.sh, focused on rule 3: R-IDs cited only in a whole-line
|
|
# YAML/shell `#` comment are PROSE (a reference), not a declaration, and must NOT be
|
|
# attributed — while R-IDs on real code/spec lines still ARE.
|
|
#
|
|
# The tests point the real script at a THROWAWAY git repo via the REPO_ROOT override
|
|
# (ci/lib.sh honours a pre-set REPO_ROOT); the script itself is run from its true
|
|
# location so it still sources the real lib.sh. No network, no cluster.
|
|
#
|
|
# Run: ci/tests/touched-requirements.test.sh (or `make -C ci test`)
|
|
set -euo pipefail
|
|
|
|
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' "$*"; FAIL=$((FAIL+1)); }
|
|
|
|
# assert_absent <needle> <haystack> <msg>
|
|
assert_absent() {
|
|
if grep -qxF "$1" <<<"$2"; then bad "$3 (found unexpected '$1' in: $(tr '\n' ' ' <<<"$2"))";
|
|
else ok "$3"; fi
|
|
}
|
|
# assert_present <needle> <haystack> <msg>
|
|
assert_present() {
|
|
if grep -qxF "$1" <<<"$2"; then ok "$3";
|
|
else bad "$3 (missing '$1' from: $(tr '\n' ' ' <<<"$2"))"; fi
|
|
}
|
|
|
|
TMP="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
git -C "$TMP" init -q -b main
|
|
git -C "$TMP" config user.email [email protected]
|
|
git -C "$TMP" config user.name t
|
|
git -C "$TMP" config commit.gpgsign false # throwaway repo: never sign (ignore host gpg config)
|
|
mkdir -p "$TMP/notes" # a path OUTSIDE path_requirements() so we isolate the content scan
|
|
echo "baseline" > "$TMP/notes/base.txt"
|
|
git -C "$TMP" add -A && git -C "$TMP" commit -qm baseline
|
|
|
|
git -C "$TMP" checkout -q -b feature
|
|
|
|
# A YAML file: an R-ID in a whole-line comment (must be IGNORED) and an R-ID on a real
|
|
# value line (must be DETECTED). Also an indented comment, to prove leading space is fine.
|
|
cat > "$TMP/notes/example.yaml" <<'YAML'
|
|
# This header explains R-DOC-01 and must NOT be attributed.
|
|
kind: Thing
|
|
metadata:
|
|
name: implements-R-REAL-01
|
|
spec:
|
|
note: value # trailing inline comment mentioning R-INLINE-01 (conservatively still seen)
|
|
# deeply indented comment about R-DOC-02 — also NOT attributed
|
|
YAML
|
|
|
|
# A shell file: a `#` comment R-ID (IGNORED) plus a real command line R-ID (DETECTED).
|
|
cat > "$TMP/notes/example.sh" <<'SH'
|
|
#!/usr/bin/env bash
|
|
# doc: this script relates to R-SHDOC-01 (comment only — ignored)
|
|
echo "runs check for R-SHREAL-01"
|
|
SH
|
|
|
|
git -C "$TMP" add -A && git -C "$TMP" commit -qm feature
|
|
|
|
OUT="$(REPO_ROOT="$TMP" BASE_REF=main "$SCRIPT" 2>/dev/null)"
|
|
printf 'touched set:\n%s\n---\n' "$OUT"
|
|
|
|
# Comment-only citations are dropped (rule 3):
|
|
assert_absent "R-DOC-01" "$OUT" "whole-line YAML comment R-ID is NOT attributed"
|
|
assert_absent "R-DOC-02" "$OUT" "indented YAML comment R-ID is NOT attributed"
|
|
assert_absent "R-SHDOC-01" "$OUT" "shell '#' comment R-ID is NOT attributed"
|
|
# Real code/spec lines still count:
|
|
assert_present "R-REAL-01" "$OUT" "R-ID on a real YAML value line IS attributed"
|
|
assert_present "R-SHREAL-01" "$OUT" "R-ID on a real shell command line IS attributed"
|
|
# Conservative: a trailing inline comment on a REAL line is still seen (only whole-line
|
|
# comments are dropped) — documents the intentional boundary of the fix.
|
|
assert_present "R-INLINE-01" "$OUT" "inline comment on a real line is (conservatively) still seen"
|
|
|
|
# Removed comment lines are also excluded, while removed real lines still count. The file
|
|
# must EXIST at the base, so we diff the removal against the commit that added it.
|
|
git -C "$TMP" checkout -q -b feature2 main
|
|
cat > "$TMP/notes/todelete.yaml" <<'YAML'
|
|
# will delete a comment citing R-DELDOC-01 and a real line citing R-DELREAL-01
|
|
kind: Thing
|
|
name: implements-R-DELREAL-01
|
|
YAML
|
|
git -C "$TMP" add -A && git -C "$TMP" commit -qm add
|
|
ADD_SHA="$(git -C "$TMP" rev-parse HEAD)"
|
|
git -C "$TMP" rm -q "$TMP/notes/todelete.yaml" && git -C "$TMP" commit -qm remove
|
|
OUT2="$(REPO_ROOT="$TMP" BASE_REF="$ADD_SHA" "$SCRIPT" 2>/dev/null)"
|
|
printf 'touched set (removal):\n%s\n---\n' "$OUT2"
|
|
assert_absent "R-DELDOC-01" "$OUT2" "REMOVED whole-line comment R-ID is NOT attributed"
|
|
assert_present "R-DELREAL-01" "$OUT2" "REMOVED real-line R-ID IS attributed"
|
|
|
|
echo
|
|
if [ "$FAIL" -eq 0 ]; then echo "touched-requirements tests: PASS ($PASS assertions)"; exit 0;
|
|
else echo "touched-requirements tests: FAIL ($FAIL failed, $PASS passed)"; exit 1; fi
|