The denominator artifact's site coordinates assume a backslash-continuation helper call reports at its FIRST physical line. That convention was measured on a developer bash (5.3.x, Fedora); CI runs the bash baked into ci-base (node:24-alpine), which rebuilds only on push-to-main or tag — so the CI bash version floats silently between rebuilds, and an artifact validated only against a developer bash is unverified in the place the gate actually runs. wake_assert_init now runs a probe under the SAME bash binary executing the suite (subshell + continuation, the real call shape) and aborts loudly if the convention does not hold, instead of letting coordinates skew by one line under everyone. Microtest C10 proves the pin's abort arm fires, via a test-only WAKE_ASSERT_PIN_BASH interpreter override ($BASH itself cannot be spoofed — bash resets it at startup): a pin whose failure arm was never seen firing is an undertaking, not a control. Prompted by mos-dt's CI-bash-float finding (prereg addendum 4, ddea1bc7b). Written-by: pepper (sb-it-1-dt) Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NsKce8iZuSuRnu3gVMCBKB
256 lines
9.8 KiB
Bash
Executable File
256 lines
9.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# microtest-wake-assert.sh — #973 instrument self-test. Run BEFORE trusting any
|
|
# validate-run evidence: it proves the counted ledger and the abort mechanics on
|
|
# two generated mini-suites, so a defect in the instrument cannot silently wear
|
|
# the colour of a clean validation.
|
|
#
|
|
# What it proves (each check named C1..C8 below):
|
|
# C1 green run: ledger set EQUALS a text-derived expected set spanning TWO
|
|
# files (file-field discrimination), row count > 1, both sentinels emitted,
|
|
# exit 0. Also pins the BASH_LINENO convention for backslash-continuation
|
|
# call sites against the first-physical-line convention the denominator
|
|
# artifact uses.
|
|
# C2 early-exit truncation: a suite that exits before its later site yields a
|
|
# SHORT ledger, and the expected-set comparison catches it — a counted
|
|
# ledger must report its own truncation, never a smaller total.
|
|
# C3 abort from inside a `( ... )` test subshell kills the WHOLE suite: no
|
|
# sentinel, non-zero exit, loud named reason (file:line + raw rc).
|
|
# C4 abort stays loud at a call site that appends 2>/dev/null (the preimage
|
|
# canary shape) — the saved-fd path.
|
|
# C5 abort escapes a `$( count_lines ... )` substitution (A6 shape): the
|
|
# count from a failed measurement is never compared and the suite dies.
|
|
# C6 abort escapes a pipeline tail (`printf | has_match`).
|
|
# C7 count_lines prints 0 on grep rc 1 (zero matches is a measurement, not an
|
|
# error) — implicit in C1's green run via the delta-count site.
|
|
# C8 an env-prefix on the helper (`LC_ALL=C has_match ...`) reaches the grep
|
|
# child — pins the conversion shape for the digest-hmac LC_ALL site.
|
|
# C9 an arm that matches NO site is loud about it by omission: green run,
|
|
# sentinel present, and NO "WAKE-ASSERT ARMED" line — so "did not abort"
|
|
# is separable into arm-never-matched (no ARMED line) vs error-path-
|
|
# broken (ARMED line, no abort). C3..C6 require the ARMED line AND the
|
|
# aborting site's ledger row (append lands BEFORE the grep runs, so an
|
|
# abort can never shorten the count it is part of).
|
|
# C10 the BASH_LINENO pin's abort arm fires: under a probe interpreter that
|
|
# misreports the continuation line, wake_assert_init aborts loudly and
|
|
# nothing past init executes — a pin whose failure arm was never seen
|
|
# firing is an undertaking, not a control.
|
|
set -uo pipefail
|
|
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
export WAKE_COMMON="$HERE/../_wake-common.sh"
|
|
[ -f "$WAKE_COMMON" ] || {
|
|
echo "microtest: _wake-common.sh not found at $WAKE_COMMON" >&2
|
|
exit 1
|
|
}
|
|
|
|
TMP="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
fails=0
|
|
check() { # check NAME COND-DESCRIPTION (pass/fail already decided by caller: $1=name $2=0|1 $3=detail)
|
|
if [ "$2" -eq 0 ]; then
|
|
echo " PASS $1"
|
|
else
|
|
echo " FAIL $1 — $3"
|
|
fails=$((fails + 1))
|
|
fi
|
|
}
|
|
|
|
# --- fixture data ----------------------------------------------------------
|
|
printf 'alpha\nbeta\nbeta\ngamma-unused\n' >"$TMP/data.txt"
|
|
|
|
# --- mini-suite A: six helper sites across every converted form ------------
|
|
cat >"$TMP/mini-a.sh" <<'MINI_A'
|
|
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
. "$WAKE_COMMON"
|
|
wake_assert_init
|
|
TMP="$1"
|
|
FAILFILE="$TMP/failures-a"
|
|
: >"$FAILFILE"
|
|
fail_msg() { echo " FAIL: $*" >&2; echo x >>"$FAILFILE"; }
|
|
ok() { :; }
|
|
(
|
|
has_match -q alpha "$TMP/data.txt" || fail_msg "alpha missing" # SITE:or-subshell
|
|
) && ok
|
|
(
|
|
has_match -q FORBIDDEN "$TMP/data.txt" 2>/dev/null && fail_msg "forbidden present" # SITE:and-swallow
|
|
) && ok
|
|
(
|
|
[ "$(count_lines beta "$TMP/data.txt")" = "2" ] || fail_msg "beta count" # SITE:count-capture
|
|
) && ok
|
|
(
|
|
printf 'gamma\n' | has_match -q gamma || fail_msg "gamma pipeline" # SITE:pipeline
|
|
) && ok
|
|
(
|
|
has_match -q \
|
|
alpha "$TMP/data.txt" || fail_msg "continuation" # SITE:continuation
|
|
) && ok
|
|
(
|
|
[ "$(count_lines delta "$TMP/data.txt")" = "0" ] || fail_msg "delta zero" # SITE:count-zero
|
|
) && ok
|
|
if [ -s "$FAILFILE" ]; then
|
|
echo "mini-a: FAILED" >&2
|
|
exit 1
|
|
fi
|
|
echo "mini-a: OK" >&2
|
|
MINI_A
|
|
|
|
# --- mini-suite B: second file, one site behind an early exit --------------
|
|
cat >"$TMP/mini-b.sh" <<'MINI_B'
|
|
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
. "$WAKE_COMMON"
|
|
wake_assert_init
|
|
TMP="$1"
|
|
(
|
|
has_match -q alpha "$TMP/data.txt" || echo "b1 missing" >&2 # SITE:b-first
|
|
)
|
|
if [ "${MINI_B_EARLY_EXIT:-}" = "1" ]; then
|
|
exit 0
|
|
fi
|
|
(
|
|
has_match -q beta "$TMP/data.txt" || echo "b2 missing" >&2 # SITE:b-second
|
|
)
|
|
echo "mini-b: OK" >&2
|
|
MINI_B
|
|
chmod +x "$TMP/mini-a.sh" "$TMP/mini-b.sh"
|
|
|
|
# Text-derived expected set: helper-name + basename:line for every SITE-marked
|
|
# call, taken from the generated files' TEXT (independent of BASH_LINENO), with
|
|
# the continuation site expected at its FIRST physical line — the denominator
|
|
# artifact's convention.
|
|
expected_set() { # expected_set FILE
|
|
local f="$1" base
|
|
base="$(basename "$f")"
|
|
awk '
|
|
/# SITE:/ {
|
|
line = NR
|
|
if ($0 !~ /has_match|count_lines/) line = NR - 1 # marker on the continuation tail
|
|
print line
|
|
}
|
|
' "$f" | while read -r ln; do
|
|
txt="$(sed -n "${ln}p" "$f")"
|
|
case "$txt" in
|
|
*count_lines*) printf 'count_lines %s:%s\n' "$base" "$ln" ;;
|
|
*) printf 'has_match %s:%s\n' "$base" "$ln" ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
site_line() { # site_line FILE MARKER -> first physical line of that call
|
|
local f="$1" marker="$2" ln
|
|
ln="$(grep -n "# SITE:${marker}\$" "$f" | cut -d: -f1)"
|
|
# continuation marker sits on the tail line; the call starts one line up
|
|
if ! sed -n "${ln}p" "$f" | grep -Eq 'has_match|count_lines'; then
|
|
ln=$((ln - 1))
|
|
fi
|
|
printf '%s' "$ln"
|
|
}
|
|
|
|
# --- C1: green run, two files, set equality --------------------------------
|
|
LEDGER="$TMP/ledger-c1"
|
|
: >"$LEDGER"
|
|
outA="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
|
|
rcA=$?
|
|
outB="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-b.sh" "$TMP" 2>&1)"
|
|
rcB=$?
|
|
{ expected_set "$TMP/mini-a.sh"; expected_set "$TMP/mini-b.sh"; } | sort >"$TMP/expected-c1"
|
|
sort "$LEDGER" >"$TMP/got-c1"
|
|
n_expected="$(grep -c . "$TMP/expected-c1")"
|
|
if [ "$rcA" -eq 0 ] && [ "$rcB" -eq 0 ] &&
|
|
printf '%s' "$outA" | grep -q 'mini-a: OK' &&
|
|
printf '%s' "$outB" | grep -q 'mini-b: OK' &&
|
|
[ "$n_expected" -gt 1 ] &&
|
|
cmp -s "$TMP/expected-c1" "$TMP/got-c1"; then
|
|
check C1 0 ""
|
|
else
|
|
check C1 1 "rcA=$rcA rcB=$rcB expected($n_expected)/got diff: $(diff "$TMP/expected-c1" "$TMP/got-c1" 2>&1 | head -n 10 | tr '\n' ' ')"
|
|
fi
|
|
|
|
# --- C2: early exit -> short ledger, comparison catches it -----------------
|
|
LEDGER="$TMP/ledger-c2"
|
|
: >"$LEDGER"
|
|
WAKE_ASSERT_LEDGER="$LEDGER" MINI_B_EARLY_EXIT=1 bash "$TMP/mini-b.sh" "$TMP" >/dev/null 2>&1
|
|
expected_set "$TMP/mini-b.sh" | sort >"$TMP/expected-c2"
|
|
sort "$LEDGER" >"$TMP/got-c2"
|
|
if ! cmp -s "$TMP/expected-c2" "$TMP/got-c2" &&
|
|
grep -q "has_match mini-b.sh:$(site_line "$TMP/mini-b.sh" b-first)" "$TMP/got-c2" &&
|
|
! grep -q "mini-b.sh:$(site_line "$TMP/mini-b.sh" b-second)" "$TMP/got-c2"; then
|
|
check C2 0 ""
|
|
else
|
|
check C2 1 "truncated ledger was not detected as short"
|
|
fi
|
|
|
|
# --- C3..C6: per-shape abort proofs ----------------------------------------
|
|
abort_case() { # abort_case NAME MARKER HELPER
|
|
local name="$1" marker="$2" helper="$3" ln site out rc ledger
|
|
ln="$(site_line "$TMP/mini-a.sh" "$marker")"
|
|
site="mini-a.sh:${ln}"
|
|
ledger="$TMP/ledger-${name}"
|
|
: >"$ledger"
|
|
out="$(WAKE_ASSERT_LEDGER="$ledger" WAKE_ASSERT_FORCE_GREP_ERROR_AT="$site" \
|
|
bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
|
|
rc=$?
|
|
if [ "$rc" -ne 0 ] &&
|
|
! printf '%s' "$out" | grep -q 'mini-a: OK' &&
|
|
! printf '%s' "$out" | grep -q 'mini-a: FAILED' &&
|
|
printf '%s' "$out" | grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" &&
|
|
printf '%s' "$out" | grep -q "WAKE-ASSERT ABORT" &&
|
|
printf '%s' "$out" | grep -q "$site" &&
|
|
printf '%s' "$out" | grep -q "grep exit 2" &&
|
|
grep -q "^${helper} ${site}\$" "$ledger"; then
|
|
check "$name" 0 ""
|
|
else
|
|
check "$name" 1 "rc=$rc site=$site ledger=$(grep -c . "$ledger") out=$(printf '%s' "$out" | tail -n 3 | tr '\n' ' ')"
|
|
fi
|
|
}
|
|
abort_case C3 or-subshell has_match
|
|
abort_case C4 and-swallow has_match
|
|
abort_case C5 count-capture count_lines
|
|
abort_case C6 pipeline has_match
|
|
|
|
# --- C7: covered by C1 (delta-count site prints 0 on grep rc 1) ------------
|
|
check C7 0 ""
|
|
|
|
# --- C8: env-prefix on a function reaches the grep child -------------------
|
|
envprobe() { command env | command grep -c '^LC_ALL=xx_wake_test$'; }
|
|
got="$(LC_ALL=xx_wake_test envprobe 2>/dev/null)" # bash's setlocale warning about the fake locale is itself proof the prefix landed
|
|
if [ "$got" = "1" ]; then check C8 0 ""; else check C8 1 "env-prefix did not reach child (got=$got)"; fi
|
|
|
|
# --- C9: arm matching NO site -> green run, no ARMED line ------------------
|
|
out="$(WAKE_ASSERT_FORCE_GREP_ERROR_AT="mini-a.sh:9999" bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
|
|
rc=$?
|
|
if [ "$rc" -eq 0 ] &&
|
|
printf '%s' "$out" | grep -q 'mini-a: OK' &&
|
|
! printf '%s' "$out" | grep -q 'WAKE-ASSERT ARMED'; then
|
|
check C9 0 ""
|
|
else
|
|
check C9 1 "rc=$rc out=$(printf '%s' "$out" | tail -n 3 | tr '\n' ' ')"
|
|
fi
|
|
|
|
# --- C10: lineno pin aborts under an interpreter that breaks the convention -
|
|
cat >"$TMP/fake-bash" <<'FAKE'
|
|
#!/usr/bin/env bash
|
|
# stand-in for a bash whose BASH_LINENO convention differs: misreports the
|
|
# continuation call one line low (the exact skew the pin exists to catch)
|
|
printf '3\n5\n'
|
|
FAKE
|
|
chmod +x "$TMP/fake-bash"
|
|
out="$(WAKE_ASSERT_PIN_BASH="$TMP/fake-bash" bash -c '. "$WAKE_COMMON" && wake_assert_init && echo REACHED-PAST-INIT' 2>&1)"
|
|
rc=$?
|
|
if [ "$rc" -ne 0 ] &&
|
|
! printf '%s' "$out" | grep -q 'REACHED-PAST-INIT' &&
|
|
printf '%s' "$out" | grep -q 'WAKE-ASSERT INIT ABORT: BASH_LINENO convention violated'; then
|
|
check C10 0 ""
|
|
else
|
|
check C10 1 "rc=$rc out=$(printf '%s' "$out" | tail -n 2 | tr '\n' ' ')"
|
|
fi
|
|
|
|
echo
|
|
if [ "$fails" -gt 0 ]; then
|
|
echo "microtest-wake-assert: FAILED ($fails check(s))" >&2
|
|
exit 1
|
|
fi
|
|
echo "microtest-wake-assert: OK (all checks passed)"
|