#!/bin/bash # m3te logic-test gate. Mirrors sx/tests/run_examples.sh: run each sx logic # test, diff its stdout + exit code against committed expectations. Exits 0 iff # ALL tests pass, NON-ZERO otherwise (so a broken assertion fails the build gate). # # Usage: bash tools/run_tests.sh [--update] # --update: regenerate the expected .stdout/.exit from current output. # # Layout (a test is any tests/.sx with a tests/expected/.exit marker; # helper modules such as tests/test.sx have no marker, so they are not run): # tests/.sx # tests/expected/.stdout # normalized stdout # tests/expected/.exit # process exit code # # SX binary: override with the SX env var; defaults to the dev build. set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" SX="${SX:-/Users/agra/projects/sx/zig-out/bin/sx}" TESTS_DIR="$ROOT_DIR/tests" EXPECTED_DIR="$TESTS_DIR/expected" TIMEOUT=30 PASS=0 FAIL=0 SKIP=0 TIMEOUT_COUNT=0 UPDATE=0 if [[ "${1:-}" == "--update" ]]; then UPDATE=1 fi if [[ ! -x "$SX" ]]; then echo "sx binary not found/executable at: $SX (override with SX=/path/to/sx)" >&2 exit 2 fi # Collapse the absolute test-file path back to its repo-relative form so the # `FAIL :` lines printed by tests/test.sx are checkout-location # independent. Applied identically to expected and actual, so it can only # reconcile location noise, never desync an otherwise-matching pair. normalize() { sed -E -e "s#${ROOT_DIR}/##g" } TMP_ERR="$(mktemp)" trap 'rm -f "$TMP_ERR"' EXIT shopt -s nullglob markers=("$EXPECTED_DIR"/*.exit) if [[ ${#markers[@]} -eq 0 ]]; then echo "no tests found under $EXPECTED_DIR (expected .exit markers)" >&2 exit 2 fi for exit_file in "${markers[@]}"; do name=$(basename "$exit_file" .exit) sx_file="$TESTS_DIR/${name}.sx" out_file="$EXPECTED_DIR/${name}.stdout" if [[ ! -f "$sx_file" ]]; then printf " %-40s SKIP (missing %s)\n" "$name" "tests/${name}.sx" SKIP=$((SKIP + 1)) continue fi printf " %-40s" "$name" actual_out=$(timeout "$TIMEOUT" "$SX" run "$sx_file" 2>"$TMP_ERR" | normalize) actual_exit=${PIPESTATUS[0]} if [[ $actual_exit -eq 124 ]]; then TIMEOUT_COUNT=$((TIMEOUT_COUNT + 1)) echo "TIMEOUT (>${TIMEOUT}s)" continue fi if [[ $UPDATE -eq 1 ]]; then printf '%s\n' "$actual_out" > "$out_file" printf '%s\n' "$actual_exit" > "$exit_file" echo "updated (exit=$actual_exit)" continue fi expected_out=$(normalize < "$out_file" 2>/dev/null) expected_exit=$(cat "$exit_file") out_ok=true; exit_ok=true [[ "$actual_out" == "$expected_out" ]] || out_ok=false [[ "$actual_exit" == "$expected_exit" ]] || exit_ok=false if $out_ok && $exit_ok; then PASS=$((PASS + 1)) echo "ok" else FAIL=$((FAIL + 1)) echo "FAIL" $out_ok || { echo " --- stdout diff (expected vs actual) ---"; diff <(printf '%s\n' "$expected_out") <(printf '%s\n' "$actual_out") || true; } $exit_ok || echo " exit code: expected=$expected_exit actual=$actual_exit" [[ -s "$TMP_ERR" ]] && { echo " --- stderr ---"; cat "$TMP_ERR"; } fi done if [[ $UPDATE -eq 1 ]]; then echo "Updated all expected output files." exit 0 fi echo "$PASS passed, $FAIL failed, $SKIP skipped, $TIMEOUT_COUNT timed out" [[ $FAIL -eq 0 && $TIMEOUT_COUNT -eq 0 ]]