lumbda/tests/integration/test-dlq-runner-classify.sh
russell@unturf.com 1665893321
factory + quantum + sweep-doctrine: AGPLv3 share-back from foxhop ecdsa
29 new files publish factory infra (V2 autoscaler with live VRAM
sampling + EWMA peak tracking, HUGE solo-dispatch, two-tier DLQ/rDLQ
classifier + retry), general quantum circuit primitives (Cuccaro
ripple-carry adder, Clifford gate library, Clifford tableau simulator,
mod-arith family, dialog GCD reversible inverse, Karatsuba multiplier,
Solinas fast reduction), and a TCRAUDT reducer harness. Originally
developed in ~/git/www.foxhop.net/ecdsa/ for secp256k1 attack-surface
research; published upstream as obligated by AGPLv3.

Parametrization contract at factory/CONTRACT.md. Consumers export
LUMBDA_REPO_DIR + LUMBDA_QUEUE_DIR + LUMBDA_BACKEND_CMD + LUMBDA_EMITTER_CMD
then exec factory scripts. No fork-and-modify; single source of truth
upstream.

Integration tests gate 7 V2 defect classes that wedged a live factory
on 2026-06-12 (skewed-demand starve, zero-floor reservation,
multi-tier greedy, +-25%% damping, cold-start ramp, DLQ surge halve,
post-damp CPU ceiling) + 28 DLQ classifier cases (auto-retry vs
escalate partition) + bash -n syntax lint across every script.

GPU backend stays in consumer trees; rationale in
factory/GPU-BACKEND-NOTE.md. Bend wire protocol + gpu-worker.lsp
already upstream at examples/cuda-fanout/.

make factory-lint                bash -n on every factory/*.sh
make test-integration            V2 reducer + DLQ classifier + syntax gate
make sweep-doctrine              TCRAUDT reducer gate (serial)
make sweep-doctrine-parallel     xargs -P fan-out

Verified on neoblanka: factory-lint 12 scripts PASS; test-integration
14 V2 cases + 28 DLQ classifier cases + 12 syntax cases all PASS.
2026-06-14 10:37:35 -04:00

178 lines
6.5 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# test-dlq-runner-classify - unit-test bend-supervisor-dlq-runner.sh's
# classify_reason() function. Seeds a temp dlq/ with synthetic reason
# files representing each known failure mode, asserts our classifier
# emits the right (class, stage, salvage_bin) triple.
#
# Independent of live LUMBDA_QUEUE_DIR; runs in mktemp -d. Safe on any
# host.
#
# Env contract per ~/git/lumbda/factory/CONTRACT.md:
# LUMBDA_FACTORY_DIR where bend-supervisor-dlq-runner.sh lives
# (default $HOME/git/lumbda/factory).
#
# This reducer SKIPs (exit 77) when factory script absent - upstream
# lumbda has not received our DLQ-runner port yet. After our port lands,
# this gate fires.
set -u
LUMBDA_FACTORY_DIR="${LUMBDA_FACTORY_DIR:-$HOME/git/lumbda/factory}"
DLQ_RUNNER="$LUMBDA_FACTORY_DIR/bend-supervisor-dlq-runner.sh"
if [ ! -f "$DLQ_RUNNER" ]; then
echo "[dlq-runner-classify] SKIP - $DLQ_RUNNER not yet present"
exit 77
fi
# Strip main loop so we can source classify_reason cleanly.
STUB=$(mktemp /tmp/dlq-runner-stub-XXXXXX.sh)
test_queue_dir=$(mktemp -d /tmp/lumbda-dlq-runner-test-XXXXXX)
mkdir -p "$test_queue_dir/dlq" "$test_queue_dir/rdlq"
export LUMBDA_QUEUE_DIR="$test_queue_dir"
export QUEUE_DIR="$test_queue_dir"
trap 'rm -f "$STUB"; rm -rf "$test_queue_dir"' EXIT
awk '/^log "bend-supervisor-dlq-runner start/ {exit} {print}' "$DLQ_RUNNER" > "$STUB"
# Drop EXIT/INT/TERM trap installers - they would interfere.
sed -i '/^trap on_exit EXIT$/d; /^trap .EXIT_REASON=signal-int/d; /^trap .EXIT_REASON=signal-term/d' "$STUB"
# shellcheck source=/dev/null
. "$STUB" 2>/dev/null
fails=0
pass() { echo "PASS $*"; }
fail() { echo "FAIL $*"; fails=$((fails + 1)); }
# assert_classify <label> <reason-text> <expected-class> <expected-stage> <expected-salvage>
assert_classify() {
local label="$1" content="$2" exp_class="$3" exp_stage="$4" exp_salv="$5"
local f="$test_queue_dir/dlq/__test_$$_$RANDOM.reason"
printf '%s' "$content" > "$f"
local got
got=$(classify_reason "$f")
local got_class got_stage got_salv
got_class=$(echo "$got" | awk '{print $1}')
got_stage=$(echo "$got" | awk '{print $2}')
got_salv=$(echo "$got" | awk '{print $3}')
if [ "$got_class" = "$exp_class" ] && [ "$got_stage" = "$exp_stage" ] && [ "$got_salv" = "$exp_salv" ]; then
pass "$label -> $got_class/$got_stage/$got_salv"
else
fail "$label expected=$exp_class/$exp_stage/$exp_salv got=$got_class/$got_stage/$got_salv"
fi
rm -f "$f"
}
echo "── classifier tests ──"
assert_classify "cuda-oom (typed Phase 7 portal)" \
'(cuda-sim-error (code cuda-oom) (reason "out of memory at sim_gpu_packed.cu:179"))' \
"cuda-oom" "sim" "yes"
assert_classify "cuda-oom (legacy stderr leak)" \
'# dispatch foo.bin (0.5 GB) × 141 batches
wall_s=42
raw_resp=(error (no-portal "/tmp/bend-sim-ops-X.portal"))
--- bend worker log context ---
CUDA error cudaMalloc(&c->d_bits, ...) at sim_gpu_packed.cu:179: out of memory' \
"cuda-oom" "sim" "yes"
assert_classify "cuda-illegal-addr (typed)" \
'(cuda-sim-error (code cuda-illegal-addr) (reason "..."))' \
"cuda-illegal-addr" "sim" "yes"
assert_classify "memory-cap-refused" \
'(cuda-sim-error (code memory-cap-refused) (reason "peak host+device memory ~22.4 GB exceeds 16 GB cap"))' \
"memory-cap-refused" "sim" "yes"
assert_classify "vram-budget-refused (Phase 8 fast-fail)" \
'(cuda-sim-error (code vram-budget-refused) (reason "predicted 5.4 GB > free 1.2 GB at sim_gpu_packed_alloc; reduce n_batches"))' \
"vram-budget-refused" "sim" "yes"
assert_classify "bin-load-fail" \
'(cuda-sim-error (code bin-load-fail) (reason "load_ops_bin returned non-zero"))' \
"bin-load-fail" "emit" "no"
assert_classify "no-portal (pre-Phase-7)" \
'raw_resp=(error (no-portal "/tmp/bend-sim-ops-X.portal"))' \
"no-portal" "sim" "yes"
# Note: queue path inside reason body deliberately reads /tmp/lumbda-queue
# (CONTRACT default) - classifier matches on `missing:` prefix not on path
# substring, so this reads cleanly under upstream's generic env.
assert_classify "missing-bin" \
'missing: /tmp/lumbda-queue/foo-cell.bin' \
"missing-bin" "dispatch" "yes"
assert_classify "tier=zero-magic (emit interrupted)" \
'tier=zero-magic bin_bytes=146800656
reason: emit-interrupted' \
"emit-broken" "emit" "no"
assert_classify "tier=zero-n_ops (emit-finalize never fired)" \
'tier=zero-n_ops bin_bytes=146800656
reason: emit-finalize-never-fired' \
"emit-broken" "emit" "no"
assert_classify "tier=above-max" \
'tier=above-max bin_bytes=8144699544
reason: bin-too-large (over tier-huge max ...)' \
"tier-classify" "emit" "no"
assert_classify "BISECT-DIAG (pool finalize race)" \
'BISECT-DIAG: ipmul step6 swap target<->tmp
BISECT-DIAG: ipmul step7 free tmp
BISECT-DIAG: exit dgcd-raw-pa! dispatch arm' \
"bisect-pool-race" "emit" "no"
assert_classify "transient cuda-error (catchall typed)" \
'(cuda-sim-error (code cuda-error) (reason "transient driver"))' \
"cuda-error-transient" "sim" "yes"
assert_classify "unknown (catchall)" \
'completely unrelated text' \
"unknown" "unknown" "no"
# 2026-06-13: empty / whitespace-only .reason files used to land in
# "unknown" class + rDLQ. Now classified as transient-empty-reason
# (auto-retry up to AUTO_RETRY_MAX before escalating). Catches our
# dispatcher socket-disconnect / minimal-output failure mode. Detection
# stays whitespace-aware so short-but-meaningful reasons (like
# "missing: ...foo.bin") never get swept into transient class.
assert_classify "empty .reason (0 bytes)" \
'' \
"transient-empty-reason" "sim" "yes"
assert_classify "whitespace-only .reason" \
'
' \
"transient-empty-reason" "sim" "yes"
# Class-set checks - partitioning by is_auto_class / is_escalate_class
# must cover every named class exactly once.
echo ""
echo "── class-partition tests ──"
for c in missing-bin bisect-pool-race cuda-error-transient no-portal transient-empty-reason; do
if is_auto_class "$c" && ! is_escalate_class "$c"; then
pass "$c is auto (not escalate)"
else
fail "$c partitioning wrong"
fi
done
for c in cuda-oom cuda-illegal-addr memory-cap-refused vram-budget-refused bin-load-fail tier-classify emit-broken unknown; do
if is_escalate_class "$c" && ! is_auto_class "$c"; then
pass "$c is escalate (not auto)"
else
fail "$c partitioning wrong"
fi
done
echo ""
if [ "$fails" -gt 0 ]; then
echo "[dlq-runner-classify] FAIL - $fails assertion(s) failed"
exit 1
fi
echo "[dlq-runner-classify] PASS - all assertions OK"
exit 0