Files
stack/packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh
T
Jason WoltjeandClaude Fable 5 6109f81769 wake/#973: three-valued grep assertion helpers + instrument micro-test
has_match/count_lines in _wake-common.sh: rc 0 -> match, rc 1 -> no-match,
anything else -> loud abort naming the call site, the raw exit code, and the
arguments — an error never becomes a verdict. The abort escapes subshells,
pipelines, and $() substitutions by signalling the suite's main shell, so
the suite dies verdict-less; loudness survives call-site 2>/dev/null via a
wake_assert_init-saved stderr fd (dynamically allocated >= 10, no collision
with the wake lock fd 8 or detector run-loop fd 9).

Validate instrumentation (evidence only, inert in production):
WAKE_ASSERT_LEDGER appends "<helper> <file>:<line>" BEFORE the grep runs, so
an aborting site's row has already landed and the counted ledger can never
go quiet in the one condition it exists to report.
WAKE_ASSERT_FORCE_GREP_ERROR_AT=<file>:<line> routes exactly that call site
through a REAL grep driven onto its real error path (rc 2) and emits a
positive ARMED confirmation first, so arm-never-matched (no ARMED line) and
error-path-broken (ARMED line, no abort) are separable on stderr alone.

validate-973/microtest-wake-assert.sh: nine checks (C1..C9) proving the
instrument before any conversion trusts it — ledger set equality across two
files, truncation detection behind an early exit, per-shape abort proofs
(subshell, 2>/dev/null canary shape, $() count capture, pipeline tail),
count-of-zero on grep rc 1, env-prefix reaching the grep child, and a
no-match arm loud by omission. All nine pass; C1 pins BASH_LINENO's
first-physical-line convention for backslash continuations, matching the
denominator artifact's coordinates.

Production tools (store.sh, ack.sh) source this file but call none of the
new helpers.

Written-by: pepper (sb-it-1-dt)
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NsKce8iZuSuRnu3gVMCBKB
2026-07-31 01:52:56 -05:00

234 lines
8.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).
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
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)"