#!/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)"