Skip to content

Commit 7cbe02a

Browse files
committed
Keep the checksum lint's regression corpus
The checksum lint took nineteen rounds of review. Every round was a definition shape that slipped past it, and every fix was verified once, by hand, against a scratch file that was then deleted — so the corpus survives only in dfcd595's commit message. Nothing stops the next edit reintroducing any of them. test/lint is that corpus, kept. Each case writes a fixture definition, calls lint_checksums, and asserts on the `failures` array — the same array bin/ci's summary reads, so a test can't pass on output that wouldn't fail the run. Both directions are covered, because they aren't symmetric. A false negative ships an unverified download silently, which is what the lint exists to prevent; a false positive blocks CI over a URL nothing fetches, which is what gets checks deleted. Twenty undigested forms must fail — every quoting style, continuations, split mid-scheme, after if/&&/;/ANSI-C quoting/a multi-line string/a heredoc/a quoted <<HIDE, attached ;echo/&&/|/), ?download=1, uppercase and wrong-length digests. Reference links in comments and the savannah gitweb URLs must pass. And a file with no archives at all must fail rather than pass over nothing. Three things nothing else exercises: the archive extensions (.tar.xz, .tbz2, .tar.Z, .zip are in the pattern but no definition uses them), join_continuations itself (no definition has a line continuation), and the vacuous-pass guard. bin/ci changes only enough to be sourceable: the dispatch moves into main(), called only under `[ "${BASH_SOURCE[0]}" = "$0" ]`. Sourcing it currently cds, prints a banner, runs all three lints over the sourcing script's arguments, and can exit 1 out of the harness. The header comment is deliberately not renumbered — --help re-reads lines 2-15 of the file. bin/ci runs test/lint as well as syntax-checking and shellchecking it. Beyond what the plan called for, and easy to drop, but a regression suite CI never executes rots unnoticed, which is the failure mode this file exists to document. Verified by mutation, since a suite that can't fail is worth nothing: dropping .tar.xz from the pattern, joining continuations with a space, removing the vacuous-pass guard, accepting uppercase digests, and classifying every URL as an archive each fail the cases they should and no others.
1 parent dfcd595 commit 7cbe02a

2 files changed

Lines changed: 381 additions & 34 deletions

File tree

‎bin/ci‎

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@
1515
# subset is worse than not signing off at all.
1616
set -euo pipefail
1717

18-
cd "$(dirname "$0")/.."
19-
20-
# Before anything else, so --help doesn't sit through a lint pass first.
21-
case "${1:-}" in
22-
-h|--help) sed -n '2,15p' "$0" | sed 's/^#\{1,\} \{0,1\}//'; exit 0 ;;
23-
esac
24-
2518
BANNER=$'\033[1;32m'; TITLE=$'\033[1;35m'; SUBTITLE=$'\033[1;90m'
2619
ERROR=$'\033[1;31m'; SUCCESS=$'\033[1;32m'; RESET=$'\033[0m'
2720

@@ -49,8 +42,6 @@ trim_url() {
4942
printf '%s' "${u%%[;\)\(\&\|\<\>]*}"
5043
}
5144

52-
echo "${BANNER}🚀 Local CI for ruby-dev${RESET}"
53-
5445
heading() { printf '\n%s%s%s\n' "$TITLE" "$1" "$RESET"; [ $# -gt 1 ] && printf '%s%s%s\n' "$SUBTITLE" "$2" "$RESET"; return 0; }
5546
pass() { printf '%s ✓ %s%s\n' "$SUCCESS" "$1" "$RESET"; }
5647
fail() { printf '%s ✗ %s%s\n' "$ERROR" "$1" "$RESET"; failures+=("$1"); }
@@ -77,7 +68,7 @@ require_definitions() {
7768
lint_syntax() {
7869
heading "Syntax" "bash -n over scripts and definitions"
7970
local f
80-
for f in bin/ci test/build $(definitions); do
71+
for f in bin/ci test/build test/lint $(definitions); do
8172
if bash -n "$f" 2>/dev/null; then pass "$f"; else fail "$f has a syntax error"; bash -n "$f" || true; fi
8273
done
8374
}
@@ -176,6 +167,25 @@ lint_checksums() {
176167
done
177168
}
178169

170+
# --- Lint tests -------------------------------------------------------------
171+
# The checksum lint's own regression corpus. Run here, not just syntax-checked, because a
172+
# regression suite CI never executes is one that rots unnoticed — which is the same
173+
# inspected-nothing failure the lint it covers kept producing.
174+
#
175+
# test/lint sources this file, which is why everything above is definitions and only a
176+
# direct run does anything. Output is swallowed unless it fails; its per-case lines would
177+
# drown this summary.
178+
lint_tests() {
179+
heading "Lint tests" "test/lint over the checksum lint"
180+
local out
181+
if out=$(test/lint 2>&1); then
182+
pass "$(printf '%s' "$out" | tail -1)"
183+
else
184+
fail "test/lint"
185+
printf '%s\n' "$out"
186+
fi
187+
}
188+
179189
# --- Shellcheck -------------------------------------------------------------
180190
# Optional: not everywhere, and not worth blocking a build matrix over. Report
181191
# the skip out loud rather than passing silently, so nobody reads a green run as
@@ -194,7 +204,7 @@ lint_shellcheck() {
194204
# find on version-numbered filenames, deliberate word splitting). Gating on info
195205
# would mean either noisy failures or a scattering of disable comments, and both
196206
# train people to ignore the step.
197-
if shellcheck -s bash --severity=warning bin/ci test/build; then
207+
if shellcheck -s bash --severity=warning bin/ci test/build test/lint; then
198208
pass "scripts"
199209
else
200210
fail "shellcheck"
@@ -222,33 +232,54 @@ signoff() {
222232
gh signoff
223233
}
224234

225-
started=$SECONDS
235+
main() {
236+
cd "$(dirname "$0")/.."
226237

227-
require_definitions
228-
lint_syntax
229-
lint_checksums
230-
lint_shellcheck
238+
# Before anything else, so --help doesn't sit through a lint pass first.
239+
case "${1:-}" in
240+
-h|--help) sed -n '2,15p' "$0" | sed 's/^#\{1,\} \{0,1\}//'; exit 0 ;;
241+
esac
231242

232-
partial=false
233-
case "${1:-}" in
234-
--lint) partial=true ;;
235-
"") build_matrix ;;
236-
*) partial=true; build_matrix "$@" ;;
237-
esac
243+
echo "${BANNER}🚀 Local CI for ruby-dev${RESET}"
238244

239-
elapsed=$(( SECONDS - started ))
245+
local started=$SECONDS
240246

241-
if [ ${#failures[@]} -eq 0 ]; then
242-
printf '\n%s✅ CI passed in %ds%s\n' "$SUCCESS" "$elapsed" "$RESET"
243-
if $partial; then
244-
printf '%s📋 Partial run — not signing off. Run bin/ci with no arguments to sign off.%s\n' \
245-
"$SUBTITLE" "$RESET"
247+
require_definitions
248+
lint_syntax
249+
lint_checksums
250+
lint_tests
251+
lint_shellcheck
252+
253+
local partial=false
254+
case "${1:-}" in
255+
--lint) partial=true ;;
256+
"") build_matrix ;;
257+
*) partial=true; build_matrix "$@" ;;
258+
esac
259+
260+
local elapsed=$(( SECONDS - started ))
261+
262+
if [ ${#failures[@]} -eq 0 ]; then
263+
printf '\n%s✅ CI passed in %ds%s\n' "$SUCCESS" "$elapsed" "$RESET"
264+
if $partial; then
265+
printf '%s📋 Partial run — not signing off. Run bin/ci with no arguments to sign off.%s\n' \
266+
"$SUBTITLE" "$RESET"
267+
else
268+
signoff
269+
fi
246270
else
247-
signoff
271+
printf '\n%s❌ CI failed in %ds%s\n' "$ERROR" "$elapsed" "$RESET"
272+
local f
273+
for f in "${failures[@]}"; do printf '%s • %s%s\n' "$ERROR" "$f" "$RESET"; done
274+
printf '%s📋 No sign-off. Fix the issues and try again.%s\n' "$SUBTITLE" "$RESET"
275+
exit 1
248276
fi
249-
else
250-
printf '\n%s❌ CI failed in %ds%s\n' "$ERROR" "$elapsed" "$RESET"
251-
for f in "${failures[@]}"; do printf '%s • %s%s\n' "$ERROR" "$f" "$RESET"; done
252-
printf '%s📋 No sign-off. Fix the issues and try again.%s\n' "$SUBTITLE" "$RESET"
253-
exit 1
277+
}
278+
279+
# Everything above is definitions, so test/lint can source this file and call the lints
280+
# directly. Only a direct run does anything: sourced, this would cd elsewhere, print a
281+
# banner, run all three lints over the sourcing script's arguments, and exit 1 out of the
282+
# harness that sourced it.
283+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
284+
main "$@"
254285
fi

0 commit comments

Comments
 (0)