Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
Three composing pieces that make the blog SPA correct and resilient.
Content-addressed module cache (lib/host/static.sx, serve.sh, blog.sx shell,
conformance.sh): index each web-stack .sxbc by the content hash in its head,
serve GET /sx/h/{hash} immutable text/sx, and emit <script data-sx-manifest>
{file->hash} so the WASM client loads modules content-addressed (localStorage +
immutable) instead of path + max-age. serve.sh builds the index at boot;
conformance.sh now loads static.sx before blog.sx (the shell calls
host/static-manifest-json).
Declarative relate picker (lib/host/blog.sx, lib/dream/form.sx): replace the
inline /relate-picker.js blob — which never ran on swapped-in content, so the
candidate list was empty after a boosted nav to /<slug>/edit — with a declarative
SX-htmx form: sx-get relate-options on "load" + debounced "input", innerHTML-swap
the results ul; infinite scroll via a server-emitted "load more" sentinel
(sx-trigger revealed, sx-swap outerHTML) that pages the rest, q preserved via a
new symmetric dr/url-encode. The engine re-binds these triggers on swapped
content, so the picker populates on full load AND boosted SPA nav. Candidate
relate forms get :sx-disable (plain POST->303->reload, their original behavior;
the engine would otherwise boost them and swap the redirect unreliably).
sx-retry "exponential:1000:30000" on the form+sentinel retries a dropped/offline
fetch forever (the cap bounds the interval, not the attempts).
SIGPIPE hardening (hosts/ocaml/bin/sx_server.ml): the native http-listen server
had no SIGPIPE handler, so a client aborting an in-flight fetch (the engine
cancels superseded requests on a debounced filter/fast nav) closed the socket
mid-write and killed the whole process (exit 141). Ignore SIGPIPE so the failed
write becomes a catchable Sys_error the per-connection handler already swallows.
Tests: host conformance 272/272; relate-picker.spec.js 5/5 incl. a boosted-nav
populate regression; spa-check 4/4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
172 lines
5.6 KiB
Bash
Executable File
172 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# host-on-sx conformance runner — loads the kernel stdlib, the subsystem
|
|
# libraries the host wires to, the host modules, and the host test suites in one
|
|
# sx_server process, then reports pass/fail per suite. Mirrors lib/dream's runner.
|
|
#
|
|
# Usage:
|
|
# bash lib/host/conformance.sh # run all suites
|
|
# bash lib/host/conformance.sh -v # verbose (list each suite)
|
|
|
|
set -uo pipefail
|
|
cd "$(git rev-parse --show-toplevel)"
|
|
|
|
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
|
if [ ! -x "$SX_SERVER" ]; then
|
|
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
|
fi
|
|
if [ ! -x "$SX_SERVER" ]; then
|
|
echo "ERROR: sx_server.exe not found." >&2
|
|
exit 1
|
|
fi
|
|
|
|
VERBOSE="${1:-}"
|
|
|
|
# Kernel + subsystem dependencies, then the host modules. Order matters:
|
|
# stdlib/r7rs first; the Datalog engine + ACL subsystem (authorisation); the feed
|
|
# subsystem (the first migrated domain); Dream (types/json/auth/error/router) the
|
|
# host builds on; then the host layer itself.
|
|
MODULES=(
|
|
"spec/stdlib.sx"
|
|
"lib/r7rs.sx"
|
|
"lib/apl/runtime.sx"
|
|
"lib/datalog/tokenizer.sx"
|
|
"lib/datalog/parser.sx"
|
|
"lib/datalog/unify.sx"
|
|
"lib/datalog/db.sx"
|
|
"lib/datalog/builtins.sx"
|
|
"lib/datalog/aggregates.sx"
|
|
"lib/datalog/strata.sx"
|
|
"lib/datalog/eval.sx"
|
|
"lib/datalog/api.sx"
|
|
"lib/datalog/magic.sx"
|
|
"lib/acl/schema.sx"
|
|
"lib/acl/facts.sx"
|
|
"lib/acl/engine.sx"
|
|
"lib/acl/explain.sx"
|
|
"lib/acl/audit.sx"
|
|
"lib/acl/federation.sx"
|
|
"lib/acl/api.sx"
|
|
"lib/relations/schema.sx"
|
|
"lib/relations/engine.sx"
|
|
"lib/relations/api.sx"
|
|
"lib/relations/explain.sx"
|
|
"lib/relations/federation.sx"
|
|
"lib/relations/tree.sx"
|
|
"lib/feed/normalize.sx"
|
|
"lib/feed/stream.sx"
|
|
"lib/feed/api.sx"
|
|
"lib/persist/event.sx"
|
|
"lib/persist/backend.sx"
|
|
"lib/persist/log.sx"
|
|
"lib/persist/kv.sx"
|
|
"lib/persist/api.sx"
|
|
"lib/persist/durable.sx"
|
|
"spec/render.sx"
|
|
"web/adapter-html.sx"
|
|
"lib/dream/types.sx"
|
|
"lib/dream/json.sx"
|
|
"lib/dream/auth.sx"
|
|
"lib/dream/error.sx"
|
|
"lib/dream/form.sx"
|
|
"lib/dream/session.sx"
|
|
"lib/dream/router.sx"
|
|
"lib/host/handler.sx"
|
|
"lib/host/middleware.sx"
|
|
"lib/host/session.sx"
|
|
"lib/host/auth.sx"
|
|
"lib/host/sxtp.sx"
|
|
"lib/host/router.sx"
|
|
"lib/host/static.sx"
|
|
"lib/host/feed.sx"
|
|
"lib/host/relations.sx"
|
|
"lib/host/blog.sx"
|
|
"lib/host/page.sx"
|
|
"lib/host/server.sx"
|
|
"lib/host/ledger.sx"
|
|
)
|
|
|
|
# Suites: NAME RUNNER-FN PATH
|
|
SUITES=(
|
|
"handler host-hd-tests-run! lib/host/tests/handler.sx"
|
|
"middleware host-mw-tests-run! lib/host/tests/middleware.sx"
|
|
"sxtp host-sx-tests-run! lib/host/tests/sxtp.sx"
|
|
"router host-rt-tests-run! lib/host/tests/router.sx"
|
|
"feed host-fd-tests-run! lib/host/tests/feed.sx"
|
|
"relations host-rl-tests-run! lib/host/tests/relations.sx"
|
|
"blog host-bl-tests-run! lib/host/tests/blog.sx"
|
|
"session host-se-tests-run! lib/host/tests/session.sx"
|
|
"page host-pg-tests-run! lib/host/tests/page.sx"
|
|
"server host-sv-tests-run! lib/host/tests/server.sx"
|
|
"ledger host-lg-tests-run! lib/host/tests/ledger.sx"
|
|
)
|
|
|
|
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
|
EPOCH=1
|
|
emit_load () { echo "(epoch $EPOCH)"; echo "(load \"$1\")"; EPOCH=$((EPOCH+1)); }
|
|
emit_eval () { echo "(epoch $EPOCH)"; echo "(eval \"$1\")"; EPOCH=$((EPOCH+1)); }
|
|
|
|
{
|
|
for M in "${MODULES[@]}"; do emit_load "$M"; done
|
|
for SUITE in "${SUITES[@]}"; do
|
|
read -r _NAME _RUNNER FILE <<< "$SUITE"
|
|
emit_load "$FILE"
|
|
emit_eval "($_RUNNER)"
|
|
done
|
|
} > "$TMPFILE"
|
|
|
|
# 600s: the blog suite drives the relations graph hard (every is-a/types-of/
|
|
# instances-of query re-saturates the Datalog db), so it's CPU-bound and slower
|
|
# under shared-box contention. 300s was tripping a false truncation.
|
|
OUTPUT=$(timeout 600 "$SX_SERVER" < "$TMPFILE" 2>&1 || true)
|
|
|
|
# Fail LOUD on any load/eval error. A test file that errors mid-load silently
|
|
# truncates its suite — the runner returns only the tests that ran before the
|
|
# error, so the suite reports a false green (e.g. "blog 13 passed, 0 failed"
|
|
# when 16 CRUD tests never ran). Catch the error markers and abort before the
|
|
# pass/fail tally can hide them.
|
|
if echo "$OUTPUT" | grep -qE 'Undefined symbol|Unhandled exception|\[load\][^|]*[Ee]rror|expected list, got|: error '; then
|
|
echo "FAIL: load/eval error detected — a suite may be silently truncated:" >&2
|
|
echo "$OUTPUT" | grep -nE 'Undefined symbol|Unhandled exception|\[load\]|expected list, got|: error ' | head -20 >&2
|
|
exit 1
|
|
fi
|
|
|
|
TOTAL_PASS=0
|
|
TOTAL_FAIL=0
|
|
FAILED_SUITES=()
|
|
LAST_DICT_LINES=$(echo "$OUTPUT" | grep -E '^\{:' || true)
|
|
|
|
I=0
|
|
while read -r LINE; do
|
|
[ -z "$LINE" ] && continue
|
|
P=$(echo "$LINE" | grep -oE ':passed [0-9]+' | awk '{print $2}')
|
|
F=$(echo "$LINE" | grep -oE ':failed [0-9]+' | awk '{print $2}')
|
|
[ -z "$P" ] && P=0
|
|
[ -z "$F" ] && F=0
|
|
SUITE_INFO="${SUITES[$I]}"
|
|
SUITE_NAME=$(echo "$SUITE_INFO" | awk '{print $1}')
|
|
TOTAL_PASS=$((TOTAL_PASS + P))
|
|
TOTAL_FAIL=$((TOTAL_FAIL + F))
|
|
if [ "$F" -gt 0 ]; then
|
|
FAILED_SUITES+=("$SUITE_NAME: $P/$((P+F))")
|
|
printf 'X %-12s %d/%d\n' "$SUITE_NAME" "$P" "$((P+F))"
|
|
echo "$LINE" | grep -oE ':name "[^"]*"' | sed 's/:name / fail: /'
|
|
elif [ "$VERBOSE" = "-v" ]; then
|
|
printf 'ok %-12s %d passed\n' "$SUITE_NAME" "$P"
|
|
fi
|
|
I=$((I+1))
|
|
done <<< "$LAST_DICT_LINES"
|
|
|
|
TOTAL=$((TOTAL_PASS + TOTAL_FAIL))
|
|
if [ "$TOTAL" -eq 0 ]; then
|
|
echo "ERROR: no suite results parsed. Raw output:" >&2
|
|
echo "$OUTPUT" >&2
|
|
exit 1
|
|
fi
|
|
if [ $TOTAL_FAIL -eq 0 ]; then
|
|
echo "ok $TOTAL_PASS/$TOTAL host-on-sx tests passed (${#SUITES[@]} suites)"
|
|
else
|
|
echo "FAIL $TOTAL_PASS/$TOTAL passed, $TOTAL_FAIL failed:"
|
|
for S in "${FAILED_SUITES[@]}"; do echo " $S"; done
|
|
exit 1
|
|
fi
|