- `woc` now emits `.wob` that `wovm` runs: emit.ml lowers the typed, owner-annotated AST (scope-stack registers with a >64 WO-E401 diagnostic, Lua-style call windows, ICALL by slot, dedup const pool, drop maps, line tables, implicit terminators); disasm.ml backs `--dump-bc` goldens. - Ownership lowering consumes the four owner tables verbatim; RESIDUAL is the only source of borrow ops, coalesced per operand. Review caught the emitter consuming only 2 of owner.ml's 4 residual producers — an assignment-anchored aliasing violation ran to exit 0 instead of trapping; fixed, plus a backstop raising WO-E404 for any residual region left unconsumed. - Conformance harness `scripts/oop-e2e.sh` (`just oop-e2e`): four fixture kinds with exact outcomes — byte-exact stdout, one WO-E### anchored on `error CODE:`, numeric trap code, gc trace. 25 fixtures incl. pricing-demo logic, the ownership suite, and DB_STUB's parse-but-trap. `tests/` un-ignored so the corpus is actually tracked. - `woc build` produces a self-contained binary: wovm copy + appended image + 20-byte trailer, self-exec via /proc/self/exe. Verified relocated outside the repo, argless, and against adversarial trailer corruption. - Milestone 1's five spec criteria all MET (`just oop-accept`). Criterion 3 closed by WO-E405 — the entry must return `Int`, since program mode already says its return value is the exit code — which deletes the leak class without adding return-type metadata to the format. `gc/held-cycle` retired: an externally-held cycle is not expressible in a post-exit pump. - New spec: inferred GC + incremental per-shard tri-color mark-sweep, retiring `@gc` and reference counting. Story gains iterations 7b (that work) and 9b (`@table`, relations, compiler-checked query); `.dev/reference` gains a sparse System.Linq checkout. Priority: 5→6→7 (log-watcher) then 7b, 8, 9, 9b.
334 lines
11 KiB
Bash
Executable file
334 lines
11 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# scripts/oop-e2e.sh — the conformance harness (plan 3, Task 2).
|
|
#
|
|
# Walks tests/corpus/{run,compile-fail,trap,gc}/*/ and enforces one exact
|
|
# outcome per fixture kind (docs/plan/oop-vm/02-corpus.md has the
|
|
# contribution contract this script is the enforcement of):
|
|
#
|
|
# run/ fixture.wo compiles with woc and runs with wovm;
|
|
# stdout must equal fixture.out BYTE-FOR-BYTE.
|
|
# compile-fail/ fixture.wo must fail to compile with exactly the
|
|
# WO-E### named in fixture.code.
|
|
# trap/ fixture.wo must compile, then wovm must trap with
|
|
# exactly the code named in fixture.trap, parsed from
|
|
# wovm's fixed stderr line
|
|
# ("trap N in METHOD at line L: MESSAGE").
|
|
# gc/ fixture.wo compiles and runs with wovm under
|
|
# WO_GC_TRACE=1 (and WO_GC_BUDGET from the optional
|
|
# fixture.gc_budget); stdout must equal fixture.out
|
|
# byte-for-byte, and the gc pump's stderr trace must
|
|
# match fixture.trace's step count and total freed
|
|
# count exactly.
|
|
#
|
|
# Any other outcome — wrong code, unexpected success, a loader
|
|
# rejection, a crash, a hang — fails and names the fixture. One line
|
|
# per fixture, a final tally, nonzero exit if anything failed.
|
|
|
|
set -uo pipefail # no -e: a failing fixture is handled explicitly, one at a time
|
|
shopt -s nullglob
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
WOC="$ROOT/compiler/_build/default/bin/woc"
|
|
WOVM="$ROOT/runtime/wovm"
|
|
CORPUS="$ROOT/tests/corpus"
|
|
|
|
# A hang in either binary must not block `just oop-e2e`/CI forever with no
|
|
# diagnostic — every invocation below runs under `timeout`, and a kill
|
|
# (exit 124, timeout(1)'s own signal for "I killed it") is reported as its
|
|
# own named failure, never folded into "exited nonzero". Overridable for a
|
|
# slower box; fixtures in this corpus are small enough that the default is
|
|
# generous, not tight.
|
|
TIMEOUT="${OOP_E2E_TIMEOUT:-10}"
|
|
|
|
if [[ ! -x "$WOC" ]]; then
|
|
echo "oop-e2e: woc is not built ($WOC) — run: just woc-build" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -x "$WOVM" ]]; then
|
|
echo "oop-e2e: wovm is not built ($WOVM) — run: make -C runtime wovm" >&2
|
|
exit 1
|
|
fi
|
|
|
|
WORK="$(mktemp -d "${TMPDIR:-/tmp}/oop-e2e.XXXXXX")"
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
|
|
pass=0
|
|
fail=0
|
|
|
|
ok() {
|
|
echo "ok $1"
|
|
pass=$((pass + 1))
|
|
}
|
|
|
|
bad() {
|
|
echo "FAIL $1 -- $2"
|
|
fail=$((fail + 1))
|
|
}
|
|
|
|
# One scratch-file prefix per fixture (kind+name is unique across the
|
|
# whole corpus), so no per-file mktemp calls are needed inside the loop.
|
|
tmp_prefix() {
|
|
echo "$WORK/$(echo "$1" | tr '/' '-')"
|
|
}
|
|
|
|
# Every WO-E### a run actually reported, anchored on the diagnostic
|
|
# renderer's own text (diag.ml's `render`: "file:line:col: error CODE:
|
|
# message") -- not a bare substring search. This is what makes matching
|
|
# exact both ways: an expected "WO-E21" cannot match a reported "WO-E215"
|
|
# (Critical 1, review round 1), and an empty/garbled fixture.code cannot
|
|
# match a stray occurrence of the right digits inside a message body.
|
|
extract_error_codes() {
|
|
grep -oE 'error (WO-E[0-9]+):' "$1" | sed -E 's/error (WO-E[0-9]+):/\1/'
|
|
}
|
|
|
|
run_fixture() {
|
|
local dir="$1" name="run/$(basename "$1")"
|
|
local prefix wob out err rc
|
|
|
|
if [[ ! -f "$dir/fixture.wo" ]]; then bad "$name" "missing fixture.wo"; return; fi
|
|
if [[ ! -f "$dir/fixture.out" ]]; then bad "$name" "missing fixture.out"; return; fi
|
|
|
|
prefix="$(tmp_prefix "$name")"
|
|
wob="$prefix.wob" out="$prefix.out" err="$prefix.err"
|
|
|
|
timeout "$TIMEOUT" "$WOC" --emit "$dir/fixture.wo" -o "$wob" >/dev/null 2>"$err"
|
|
rc=$?
|
|
if [[ $rc -eq 124 ]]; then
|
|
bad "$name" "woc timed out after ${TIMEOUT}s"
|
|
return
|
|
elif [[ $rc -gt 128 ]]; then
|
|
bad "$name" "woc crashed (signal $((rc - 128)))"
|
|
return
|
|
elif [[ $rc -ne 0 ]]; then
|
|
bad "$name" "compile failed (exit $rc): $(head -1 "$err")"
|
|
return
|
|
fi
|
|
|
|
timeout "$TIMEOUT" "$WOVM" "$wob" >"$out" 2>"$err"
|
|
rc=$?
|
|
if [[ $rc -eq 124 ]]; then
|
|
bad "$name" "wovm timed out after ${TIMEOUT}s"
|
|
return
|
|
elif [[ $rc -eq 1 ]]; then
|
|
bad "$name" "unexpected trap: $(head -1 "$err")"
|
|
return
|
|
elif [[ $rc -eq 2 ]]; then
|
|
bad "$name" "loader rejection: $(head -1 "$err")"
|
|
return
|
|
elif [[ $rc -gt 128 ]]; then
|
|
bad "$name" "wovm crashed (signal $((rc - 128)))"
|
|
return
|
|
elif [[ $rc -ne 0 ]]; then
|
|
bad "$name" "wovm exited $rc: $(head -1 "$err")"
|
|
return
|
|
fi
|
|
|
|
if ! diff -q "$dir/fixture.out" "$out" >/dev/null 2>&1; then
|
|
bad "$name" "stdout mismatch"
|
|
diff -u "$dir/fixture.out" "$out" | sed 's/^/ /'
|
|
return
|
|
fi
|
|
ok "$name"
|
|
}
|
|
|
|
gc_fixture() {
|
|
local dir="$1" name="gc/$(basename "$1")"
|
|
local prefix wob out err rc exp_steps exp_freed got_steps got_freed budget
|
|
|
|
if [[ ! -f "$dir/fixture.wo" ]]; then bad "$name" "missing fixture.wo"; return; fi
|
|
if [[ ! -f "$dir/fixture.out" ]]; then bad "$name" "missing fixture.out"; return; fi
|
|
if [[ ! -f "$dir/fixture.trace" ]]; then bad "$name" "missing fixture.trace"; return; fi
|
|
|
|
prefix="$(tmp_prefix "$name")"
|
|
wob="$prefix.wob" out="$prefix.out" err="$prefix.err"
|
|
|
|
timeout "$TIMEOUT" "$WOC" --emit "$dir/fixture.wo" -o "$wob" >/dev/null 2>"$err"
|
|
rc=$?
|
|
if [[ $rc -eq 124 ]]; then
|
|
bad "$name" "woc timed out after ${TIMEOUT}s"
|
|
return
|
|
elif [[ $rc -gt 128 ]]; then
|
|
bad "$name" "woc crashed (signal $((rc - 128)))"
|
|
return
|
|
elif [[ $rc -ne 0 ]]; then
|
|
bad "$name" "compile failed (exit $rc): $(head -1 "$err")"
|
|
return
|
|
fi
|
|
|
|
exp_steps="$(grep -oE 'steps=[0-9]+' "$dir/fixture.trace" | head -1 | cut -d= -f2)"
|
|
exp_freed="$(grep -oE 'freed=[0-9]+' "$dir/fixture.trace" | head -1 | cut -d= -f2)"
|
|
if [[ -z "$exp_steps" || -z "$exp_freed" ]]; then
|
|
bad "$name" "fixture.trace missing steps=/freed="
|
|
return
|
|
fi
|
|
|
|
# WO_GC_TRACE is always on so the pump's stderr trace can be checked;
|
|
# WO_GC_BUDGET is only set when the fixture names one (fixture.gc_budget
|
|
# is optional — absent means "use the pump's own default").
|
|
if [[ -f "$dir/fixture.gc_budget" ]]; then
|
|
budget="$(tr -d '[:space:]' <"$dir/fixture.gc_budget")"
|
|
timeout "$TIMEOUT" env WO_GC_TRACE=1 WO_GC_BUDGET="$budget" "$WOVM" "$wob" >"$out" 2>"$err"
|
|
else
|
|
timeout "$TIMEOUT" env WO_GC_TRACE=1 "$WOVM" "$wob" >"$out" 2>"$err"
|
|
fi
|
|
rc=$?
|
|
if [[ $rc -eq 124 ]]; then
|
|
bad "$name" "wovm timed out after ${TIMEOUT}s"
|
|
return
|
|
elif [[ $rc -eq 1 ]]; then
|
|
bad "$name" "unexpected trap: $(head -1 "$err")"
|
|
return
|
|
elif [[ $rc -eq 2 ]]; then
|
|
bad "$name" "loader rejection: $(head -1 "$err")"
|
|
return
|
|
elif [[ $rc -gt 128 ]]; then
|
|
bad "$name" "wovm crashed (signal $((rc - 128)))"
|
|
return
|
|
elif [[ $rc -ne 0 ]]; then
|
|
bad "$name" "wovm exited $rc: $(head -1 "$err")"
|
|
return
|
|
fi
|
|
|
|
if ! diff -q "$dir/fixture.out" "$out" >/dev/null 2>&1; then
|
|
bad "$name" "stdout mismatch"
|
|
diff -u "$dir/fixture.out" "$out" | sed 's/^/ /'
|
|
return
|
|
fi
|
|
|
|
got_steps="$(grep -c '^gc: step ' "$err")"
|
|
got_freed="$(grep -oE 'freed=[0-9]+' "$err" | cut -d= -f2 | awk '{s += $1} END {print s + 0}')"
|
|
if [[ "$got_steps" != "$exp_steps" || "$got_freed" != "$exp_freed" ]]; then
|
|
bad "$name" "gc trace mismatch: expected steps=$exp_steps freed=$exp_freed, got steps=$got_steps freed=$got_freed"
|
|
return
|
|
fi
|
|
|
|
ok "$name"
|
|
}
|
|
|
|
compile_fail_fixture() {
|
|
local dir="$1" name="compile-fail/$(basename "$1")"
|
|
local prefix wob err rc expected
|
|
|
|
if [[ ! -f "$dir/fixture.wo" ]]; then bad "$name" "missing fixture.wo"; return; fi
|
|
if [[ ! -f "$dir/fixture.code" ]]; then bad "$name" "missing fixture.code"; return; fi
|
|
|
|
prefix="$(tmp_prefix "$name")"
|
|
wob="$prefix.wob" err="$prefix.err"
|
|
expected="$(tr -d '[:space:]' <"$dir/fixture.code")"
|
|
|
|
if [[ -z "$expected" ]]; then
|
|
bad "$name" "fixture.code is empty"
|
|
return
|
|
fi
|
|
|
|
timeout "$TIMEOUT" "$WOC" --emit "$dir/fixture.wo" -o "$wob" >/dev/null 2>"$err"
|
|
rc=$?
|
|
|
|
if [[ $rc -eq 124 ]]; then
|
|
bad "$name" "expected $expected, woc timed out after ${TIMEOUT}s"
|
|
elif [[ $rc -eq 0 ]]; then
|
|
bad "$name" "expected $expected, compiled clean (unexpected success)"
|
|
elif [[ $rc -gt 128 ]]; then
|
|
bad "$name" "expected $expected, woc crashed (signal $((rc - 128)))"
|
|
elif [[ $rc -eq 2 ]]; then
|
|
bad "$name" "expected $expected, got a usage/IO error: $(head -1 "$err")"
|
|
elif [[ $rc -ne 1 ]]; then
|
|
bad "$name" "expected $expected, woc exited $rc"
|
|
elif ! extract_error_codes "$err" | grep -qxF "$expected"; then
|
|
bad "$name" "expected $expected, got: $(head -1 "$err")"
|
|
else
|
|
ok "$name"
|
|
fi
|
|
}
|
|
|
|
trap_fixture() {
|
|
local dir="$1" name="trap/$(basename "$1")"
|
|
local prefix wob out err rc expected got
|
|
|
|
if [[ ! -f "$dir/fixture.wo" ]]; then bad "$name" "missing fixture.wo"; return; fi
|
|
if [[ ! -f "$dir/fixture.trap" ]]; then bad "$name" "missing fixture.trap"; return; fi
|
|
|
|
prefix="$(tmp_prefix "$name")"
|
|
wob="$prefix.wob" out="$prefix.out" err="$prefix.err"
|
|
expected="$(tr -d '[:space:]' <"$dir/fixture.trap")"
|
|
|
|
if [[ -z "$expected" ]]; then
|
|
bad "$name" "fixture.trap is empty"
|
|
return
|
|
fi
|
|
|
|
timeout "$TIMEOUT" "$WOC" --emit "$dir/fixture.wo" -o "$wob" >/dev/null 2>"$err"
|
|
rc=$?
|
|
if [[ $rc -eq 124 ]]; then
|
|
bad "$name" "expected trap $expected, woc timed out after ${TIMEOUT}s"
|
|
return
|
|
elif [[ $rc -gt 128 ]]; then
|
|
bad "$name" "expected trap $expected, woc crashed (signal $((rc - 128)))"
|
|
return
|
|
elif [[ $rc -ne 0 ]]; then
|
|
bad "$name" "expected trap $expected, compile failed: $(head -1 "$err")"
|
|
return
|
|
fi
|
|
|
|
timeout "$TIMEOUT" "$WOVM" "$wob" >"$out" 2>"$err"
|
|
rc=$?
|
|
if [[ $rc -eq 124 ]]; then
|
|
bad "$name" "expected trap $expected, wovm timed out after ${TIMEOUT}s"
|
|
return
|
|
elif [[ $rc -eq 0 ]]; then
|
|
bad "$name" "expected trap $expected, ran to completion"
|
|
return
|
|
elif [[ $rc -eq 2 ]]; then
|
|
bad "$name" "expected trap $expected, got a loader rejection: $(head -1 "$err")"
|
|
return
|
|
elif [[ $rc -gt 128 ]]; then
|
|
bad "$name" "expected trap $expected, wovm crashed (signal $((rc - 128)))"
|
|
return
|
|
elif [[ $rc -ne 1 ]]; then
|
|
bad "$name" "expected trap $expected, wovm exited $rc"
|
|
return
|
|
fi
|
|
|
|
# wovm's fixed stderr line: "trap N in METHOD at line L: MESSAGE"
|
|
got="$(sed -n 's/^trap \([0-9][0-9]*\) in .*/\1/p' "$err" | head -1)"
|
|
if [[ -z "$got" ]]; then
|
|
bad "$name" "exit 1 but no parseable trap line: $(head -1 "$err")"
|
|
elif [[ "$got" != "$expected" ]]; then
|
|
bad "$name" "expected trap $expected, got trap $got"
|
|
else
|
|
ok "$name"
|
|
fi
|
|
}
|
|
|
|
walk() {
|
|
local kind="$1" fn="$2" dir
|
|
for dir in "$CORPUS/$kind"/*/; do
|
|
[[ -d "$dir" ]] || continue
|
|
"$fn" "${dir%/}"
|
|
done
|
|
}
|
|
|
|
walk run run_fixture
|
|
walk compile-fail compile_fail_fixture
|
|
walk trap trap_fixture
|
|
walk gc gc_fixture
|
|
|
|
# ---- single-binary smoke (Task 6, plan 3) -----------------------------
|
|
# `woc build` end to end (relocate outside the repo, run, diff; corrupt
|
|
# the trailer, confirm a clear failure) doesn't fit the fixture-walk
|
|
# shape above, so it's a dedicated script -- its ok/FAIL lines fold into
|
|
# this harness's own tally the same way a fixture's would.
|
|
SB_LOG="$WORK/single-binary-smoke.log"
|
|
"$ROOT/scripts/single-binary-smoke.sh" | tee "$SB_LOG"
|
|
pass=$((pass + $(grep -c '^ok ' "$SB_LOG")))
|
|
fail=$((fail + $(grep -c '^FAIL ' "$SB_LOG")))
|
|
|
|
echo
|
|
total=$((pass + fail))
|
|
printf 'oop-e2e: %d checks, %d failures\n' "$total" "$fail"
|
|
|
|
if [[ $total -eq 0 ]]; then
|
|
echo "oop-e2e: no fixtures found under $CORPUS/{run,compile-fail,trap,gc} — harness misconfigured?" >&2
|
|
exit 1
|
|
fi
|
|
[[ $fail -eq 0 ]]
|