AtlatestRepositoryfolio

folio / tree / testgate-sabotage-coverage.sh

1#!/usr/bin/env bash
2#
3# Every module in `src/folio/doc/` is either sabotaged by a leg in one of this
4# repository's sabotage drivers, or named here with a reason.
5#
6# WHY THIS IS A STANDING CHECK AND NOT A ONE-TIME SWEEP.
7#
8# Sabotage targets in this family were picked by looking at the files that
9# already had legs. That is a search scoped to the previous change, and it
10# cannot find the class: every leg got added next to another leg, so
11# `traversal.sgl`, `render/plain.sgl` and `render/debug.sgl` went from F1a's
12# first commit to 2026-08-22 with no leg in ANY driver -- while `gate-f1a.sh`
13# arm 8 READ `traversal.sgl` to derive its field-totality check. A review
14# planted a real defect there (`update-id` replaced by a constant `#f`) and the
15# gate returned green from a broken product.
17# A sweep fixes those three files. This is for the eleventh module.
19# WHAT THIS CHECK CAN AND CANNOT SHOW -- stated because a coverage banner is
20# itself an assertion, and an overstated one stops the next person looking.
22# IT CAN show that no module is unattended: that some driver MUTATES the file,
23# so somebody has pointed a leg at it.
25# IT CANNOT show that the leg is any good. Whether a leg actually reddens the
26# gate is proved by the drivers themselves -- each `expect_red` demands a
27# named failure signature and a non-zero exit, and a driver whose legs did not
28# all go red exits non-zero. This check is the denominator; those are the
29# numerator. Run both.
31# NAMED `gate-` AND NOT `sabotage-`, AND THAT IS LOAD-BEARING. The first version
32# was `test/sabotage-coverage.sh`, so the driver glob below matched IT, and the
33# control module named in its own executable code read as covered. Its own
34# control caught that on the first run. A checker that is a member of the
35# population it enumerates will find itself.
36set -euo pipefail
38repo_dir="$(cd "$(dirname "$0")/.." && pwd)"
39cd "$repo_dir"
41command -v find >/dev/null || { echo "find is required by this check" >&2; exit 1; }
42command -v awk >/dev/null || { echo "awk is required by this check" >&2; exit 1; }
44work=$(mktemp -d)
45trap 'rm -rf "$work"' EXIT INT TERM
47drivers=$(find test -maxdepth 1 -name 'sabotage-*.sh' | sort)
48driver_count=$(printf '%s\n' "$drivers" | grep -c . || true)
49test "$driver_count" -ge 1 || { echo "found no sabotage drivers; the derivation is broken" >&2; exit 1; }
51# A MENTION IS NOT A LEG, AND THIS IS THE DEFECT THAT NEARLY SHIPPED IN THIS
52# FILE. The first version counted any non-comment mention. Every driver keeps a
53# `targets=()` backup array naming the files it will RESTORE, so a module listed
54# there only -- backed up, never sabotaged -- read as covered. An adversarial
55# review deleted legs S8, S9 and S10 outright, leaving just the array entries,
56# and this gate still reported 10/10 GREEN. It could not fail for the one thing
57# it exists to detect.
59# It now requires a POSITIVE DISCRIMINATOR: the path must appear on a logical
60# line that also invokes something which MUTATES a file. An allowlist, not a
61# denylist -- a driver that grows a new mutation helper reads as UNCOVERED and
62# says so loudly, which is the safe direction and a one-line fix. `save_file`
63# and the array literals are deliberately absent: backing a file up is not
64# sabotaging it.
65mutators='sed -i|replace_once|patch_file|python3|>>'
67# code_of <file> -- non-comment lines, with `\`-continuations JOINED into one
68# logical line. The joining is load-bearing: the drivers write
70# sed -i 's/.../.../' \
71# src/folio/doc/render/plain.sgl
73# so the verb and the path land on different physical lines, and a line-local
74# test would miss every continued leg.
76# Comment lines are dropped, and only comment lines: driver prose names these
77# files constantly -- including prose confessing that a file is UNCOVERED -- and
78# a check satisfied by a comment confirms coverage by reading the confession.
79code_of() {
80 awk '
81 /^[[:space:]]*#/ { next }
82 { line = $0
83 while (line ~ /\\$/ && (getline nxt) > 0) {
84 sub(/\\$/, "", line)
85 sub(/^[[:space:]]+/, " ", nxt)
86 line = line nxt
87 }
88 print line }
89 ' "$1"
92# regex_escape <literal> -- the ERE metacharacters only. `/` is NOT one, and
93# escaping it earns a `stray \ before /` warning on every single grep, which is
94# exactly the kind of noise a reader learns to scroll past.
95regex_escape() { printf '%s' "$1" | sed 's/[.[\*^$+?(){}|]/\\&/g'; }
97# covered_by <module> -- one line per MUTATING mention, prefixed with the driver.
98# The path is bounded on both sides, so `model.sgl` cannot be satisfied by
99# `model.sgl.bak` nor by a longer path that merely contains it.
100covered_by() {
101 local module="$1" pat d
102 pat="(^|[^A-Za-z0-9_./-])$(regex_escape "$module")([^A-Za-z0-9_.-]|\$)"
103 for d in $drivers; do
104 code_of "$d" | grep -E -- "$mutators" | grep -E -- "$pat" | sed "s|^|$d: |" || true
105 done
108# --------------------------------------------------------------- EXEMPTIONS
109# A module may be left without a leg, but the reason has to be written down and
110# it has to be an ARGUMENT. "Nobody got to it" is not one, and neither is "the
111# file is small" -- `render/debug.sgl` is eleven lines and has leg S10, because
112# what it guards against is somebody growing the alias into a second
113# serialization.
115# THIS LIST IS EMPTY, AND THAT IS THE RESULT. All ten modules under
116# `src/folio/doc/` can carry a leg and now do. The mechanism is kept so a reader
117# can see the exemption path exists and was not needed.
118exempt_modules=()
119exempt_reasons=()
121is_exempt() {
122 local m="$1" i=0
123 while [ "$i" -lt "${#exempt_modules[@]}" ]; do
124 if [ "${exempt_modules[$i]}" = "$m" ]; then printf '%s' "${exempt_reasons[$i]}"; return 0; fi
125 i=$((i + 1))
126 done
127 return 1
130# --------------------------------------------------------------- CONTROLS
131# Planted into a throwaway directory; the working tree is never touched, so no
132# restore step has to be reached. Five directions, and the third is the one that
133# would have caught the defect this file shipped with.
134ctl="$work/controls"
135mkdir -p "$ctl"
137ctl_module=src/folio/doc/control-module.sgl
138{ printf 'sed -i %ss/a/b/%s %s\n' "'" "'" "$ctl_module"; } >"$ctl/sabotage-ctl-mutated.sh"
139{ printf 'targets=(\n %s\n)\n' "$ctl_module"
140 printf 'save_file %s\n' "$ctl_module"; } >"$ctl/sabotage-ctl-array-only.sh"
141{ printf '# %s is not covered by anything and this comment says so\n' "$ctl_module"; } >"$ctl/sabotage-ctl-comment-only.sh"
142{ printf 'frobnicate %s\n' "$ctl_module"; } >"$ctl/sabotage-ctl-unknown-verb.sh"
143{ printf 'sed -i %ss/a/b/%s \\\n %s\n' "'" "'" "$ctl_module"; } >"$ctl/sabotage-ctl-continued.sh"
145ctl_hits() { # ctl_hits <fixture-basename>
146 local saved="$drivers" out
147 drivers="$ctl/$1"
148 out=$(covered_by "$ctl_module")
149 drivers="$saved"
150 printf '%s' "$out" | grep -c . || true
153test "$(ctl_hits sabotage-ctl-mutated.sh)" -ge 1 || {
154 echo "CONTROL FAILED: a plain 'sed -i <path>' mutation was not seen as coverage" >&2; exit 1; }
155test "$(ctl_hits sabotage-ctl-continued.sh)" -ge 1 || {
156 echo "CONTROL FAILED: a backslash-continued mutation was not seen; the line joiner is broken" >&2; exit 1; }
157test "$(ctl_hits sabotage-ctl-array-only.sh)" = "0" || {
158 echo "CONTROL FAILED: a module named ONLY in a targets=() backup array read as covered." >&2
159 echo " That is precisely the false green this check was rewritten to close." >&2; exit 1; }
160test "$(ctl_hits sabotage-ctl-comment-only.sh)" = "0" || {
161 echo "CONTROL FAILED: a module named only in a comment read as covered" >&2; exit 1; }
162test "$(ctl_hits sabotage-ctl-unknown-verb.sh)" = "0" || {
163 echo "CONTROL FAILED: an unrecognised verb read as coverage; the allowlist is not an allowlist" >&2; exit 1; }
165# ...and against the REAL drivers: a module that exists nowhere must read
166# uncovered, and one with real legs must read covered.
167test -z "$(covered_by src/folio/doc/this-module-cannot-exist.sgl)" || {
168 echo "CONTROL FAILED: a module that exists nowhere was reported as covered" >&2; exit 1; }
169test -n "$(covered_by src/folio/doc/model.sgl)" || {
170 echo "CONTROL FAILED: the detector cannot see the coverage of src/folio/doc/model.sgl," >&2
171 echo " which legs S1-S4 and S6 of test/sabotage-f1a.sh sabotage. An empty uncovered" >&2
172 echo " list would therefore mean nothing." >&2; exit 1; }
173echo "detector armed: mutation seen (plain and continued), backup array / comment / unknown verb all refused, model.sgl covered, an absent module uncovered"
175# --------------------------------------------------------------- POPULATION
176# ENUMERATED FROM THE SOURCE TREE. Not from the drivers, not from a list.
177modules=$(find src/folio/doc -type f -name '*.sgl' | sort)
178module_count=$(printf '%s\n' "$modules" | grep -c . || true)
179test "$module_count" -ge 1 || { echo "found no modules under src/folio/doc" >&2; exit 1; }
181# The bound this check does NOT claim, stated so the green cannot be read wider
182# than it is.
183all_sgl=$(find src -type f -name '*.sgl' | wc -l)
184echo "population: $module_count module(s) under src/folio/doc, checked against $driver_count sabotage driver(s)"
185echo "out of scope: $((all_sgl - module_count)) further .sgl file(s) under src/ that this check does not cover"
187uncovered=""
188for m in $modules; do
189 hits=$(covered_by "$m")
190 if [ -n "$hits" ]; then
191 printf ' covered %-38s %s\n' "$m" "$(printf '%s\n' "$hits" | cut -d: -f1 | sort -u | tr '\n' ' ')"
192 elif reason=$(is_exempt "$m"); then
193 printf ' exempt %-38s %s\n' "$m" "$reason"
194 else
195 printf ' UNCOVERED %-38s no sabotage driver mutates it\n' "$m"
196 uncovered="$uncovered $m"
197 fi
198done
200if [ -n "$uncovered" ]; then
201 echo >&2
202 echo "MODULE(S) WITH NO SABOTAGE LEG:$uncovered" >&2
203 echo >&2
204 echo "VERIFY THIS CHECK'S OWN PRECONDITIONS BEFORE ACTING ON THIS MESSAGE:" >&2
205 echo " - the controls above passed, so the detector sees coverage where it exists;" >&2
206 echo " - the module is MUTATED by a driver, not merely listed in its backup array" >&2
207 echo " and not merely named in a comment;" >&2
208 echo " - the mutation uses one of the recognised verbs ($mutators). A new helper" >&2
209 echo " is the likeliest cause of a surprising result here, and adding it to" >&2
210 echo " \$mutators is the fix;" >&2
211 echo " - the path spelling matches the one the driver uses." >&2
212 echo "Then: add a leg that plants the defect the module's own gate arm exists to" >&2
213 echo "catch and prove it reddens, or add the module to exempt_modules WITH AN" >&2
214 echo "ARGUMENT for why it cannot carry one." >&2
215 exit 1
216fi
218echo "F1A_SABOTAGE_COVERAGE_GREEN $module_count/$module_count module(s) attended, ${#exempt_modules[@]} exempt"