Files
rose-ash/lib/js/conformance.sh
giles 9e568ad886 js-on-sx: baseline commit (278/280 unit, 148/148 slice, runner stub)
Initial commit of the lib/js/ tree and plans/ directory. A previous
session left template-string work in progress — 278/280 unit tests pass
(2 failing: tpl part-count off-by-one, escaped-backtick ident lookup).
test262-runner.py and scoreboard are placeholders (0/8 with 7 timeouts);
fixing the runner is the next queue item.
2026-04-23 19:42:16 +00:00

131 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Cherry-picked test262 conformance runner for JS-on-SX.
# Walks lib/js/test262-slice/**/*.js, evaluates each via js-eval,
# and compares against the sibling .expected file (substring match).
#
# Usage:
# bash lib/js/conformance.sh # summary only
# bash lib/js/conformance.sh -v # per-test pass/fail
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: $SX_SERVER not found. Run: cd hosts/ocaml && dune build"
exit 1
fi
VERBOSE="${1:-}"
SLICE_DIR="lib/js/test262-slice"
PASS=0
FAIL=0
ERRORS=""
# Find all .js fixtures (sorted for stable output).
# Skip README.md and similar.
mapfile -t FIXTURES < <(find "$SLICE_DIR" -type f -name '*.js' | sort)
if [ ${#FIXTURES[@]} -eq 0 ]; then
echo "No fixtures found in $SLICE_DIR"
exit 1
fi
# Build one big batch script: load everything once, then one epoch per
# fixture. Avoids the ~200ms boot cost of starting the server for each
# test.
TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" EXIT
{
echo '(epoch 1)'
echo '(load "lib/r7rs.sx")'
echo '(epoch 2)'
echo '(load "lib/js/lexer.sx")'
echo '(epoch 3)'
echo '(load "lib/js/parser.sx")'
echo '(epoch 4)'
echo '(load "lib/js/transpile.sx")'
echo '(epoch 5)'
echo '(load "lib/js/runtime.sx")'
epoch=100
for f in "${FIXTURES[@]}"; do
# Read source, strip trailing newline, then escape for *two*
# nested SX string literals: the outer epoch `(eval "…")` and
# the inner `(js-eval "…")` that it wraps.
#
# Source char → final stream char
# \ → \\\\ (outer: becomes \\ ; inner: becomes \)
# " → \\\" (outer: becomes \" ; inner: becomes ")
# nl → \\n (SX newline escape, survives both levels)
src=$(python3 -c '
import sys
s = open(sys.argv[1], "r", encoding="utf-8").read().rstrip("\n")
# Two nested SX string literals: outer eval wraps inner js-eval.
# Escape once for inner (JS source → SX inner string literal):
inner = s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
# Escape the result again for the outer SX string literal:
outer = inner.replace("\\", "\\\\").replace("\"", "\\\"")
sys.stdout.write(outer)
' "$f")
echo "(epoch $epoch)"
echo "(eval \"(js-eval \\\"$src\\\")\")"
epoch=$((epoch + 1))
done
} > "$TMPFILE"
OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
# Iterate fixtures with the same epoch sequence and check each.
epoch=100
for f in "${FIXTURES[@]}"; do
expected=$(cat "${f%.js}.expected" | sed -e 's/[[:space:]]*$//' | head -n 1)
name="${f#${SLICE_DIR}/}"
name="${name%.js}"
# Actual output lives on the line after "(ok-len $epoch N)" or on
# "(ok $epoch VAL)" for short values. Errors surface as "(error …)".
actual=$(echo "$OUTPUT" | awk -v e="$epoch" '
$0 ~ ("^\\(ok-len "e" ") { getline; print; exit }
$0 ~ ("^\\(ok "e" ") { sub("^\\(ok "e" ", ""); sub(")$", ""); print; exit }
$0 ~ ("^\\(error "e" ") { print; exit }
')
[ -z "$actual" ] && actual="<no output>"
if echo "$actual" | grep -qF -- "$expected"; then
PASS=$((PASS + 1))
[ "$VERBOSE" = "-v" ] && echo "$name"
else
FAIL=$((FAIL + 1))
ERRORS+="$name
expected: $expected
actual: $actual
"
[ "$VERBOSE" = "-v" ] && echo "$name (expected: $expected, actual: $actual)"
fi
epoch=$((epoch + 1))
done
TOTAL=$((PASS + FAIL))
PCT=$(awk "BEGIN{printf \"%.1f\", ($PASS/$TOTAL)*100}")
echo
if [ $FAIL -eq 0 ]; then
echo "$PASS/$TOTAL test262-slice tests passed ($PCT%)"
else
echo "$PASS/$TOTAL passed, $FAIL failed ($PCT%):"
[ "$VERBOSE" != "-v" ] && echo "$ERRORS"
fi
# Phase 5 target: ≥50% pass.
TARGET=50
if (( $(echo "$PCT >= $TARGET" | bc -l 2>/dev/null || python3 -c "print($PCT >= $TARGET)") )); then
exit 0
else
echo "(below target of ${TARGET}%)"
exit 1
fi