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