Files
stack/packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh
T
Mos 4fb44f6345
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
fix(wake): three-valued grep verdicts — has_match/count_lines across all ten suites (closes #973) (#983)
2026-07-31 08:39:04 +00:00

300 lines
12 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.
# C11 the FAILED-summary template executes on the red path: a mini-suite
# driven deterministically red emits the exact converted summary shape
# (`FAILED ($(count_lines . "$FAILFILE") assertion(s))`) with the right
# count, exits 1, and the summary site's ledger row lands. The nine
# real-suite summary sites are structurally unreachable in a green run
# (guarded by [ -s "$FAILFILE" ]); their dispositions cite THIS check as
# the measured execution of the same template, so "unexecuted in the
# green run" never silently means "never executed anywhere".
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
# --- C11: red path executes the converted FAILED-summary template -----------
cat >"$TMP/mini-c.sh" <<'MINI_C'
#!/usr/bin/env bash
set -uo pipefail
. "$WAKE_COMMON"
wake_assert_init
TMP="$1"
FAILFILE="$TMP/failures-c"
: >"$FAILFILE"
fail_msg() { echo " FAIL: $*" >&2; echo x >>"$FAILFILE"; }
ok() { :; }
(
has_match -q alpha "$TMP/data.txt" && fail_msg "alpha present" # SITE:c-inverted (deterministically red: alpha IS in the fixture)
) && ok
echo
if [ -s "$FAILFILE" ]; then
echo "wake mini-c harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 # SITE:c-summary
exit 1
fi
echo "wake mini-c harness: all invariants passed (1 group)"
MINI_C
chmod +x "$TMP/mini-c.sh"
LEDGER="$TMP/ledger-c11"
: >"$LEDGER"
out="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-c.sh" "$TMP" 2>&1)"
rc=$?
summary_ln="$(site_line "$TMP/mini-c.sh" c-summary)"
if [ "$rc" -eq 1 ] &&
printf '%s' "$out" | grep -q 'wake mini-c harness: FAILED (1 assertion(s))' &&
! printf '%s' "$out" | grep -q 'all invariants passed' &&
grep -q "^count_lines mini-c.sh:${summary_ln}\$" "$LEDGER"; then
check C11 0 ""
else
check C11 1 "rc=$rc summary_ln=$summary_ln ledger=$(tr '\n' ' ' <"$LEDGER") 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)"