From 38c4e4f2910bf66794540542676fd7d6005658ba Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:27:45 +0100 Subject: [PATCH] =?UTF-8?q?test(faces):=20prove=20a=20Python-face=20progra?= =?UTF-8?q?m=20runs=20end-to-end=20(face=20=E2=86=92=20wasm=20=E2=86=92=20?= =?UTF-8?q?result)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README's headline is "faces are the product" — a program in a familiar surface, elaborated to the affine core, compiled to wasm. That path had never been exercised as *running code*, only parsed. This proves it: - test/e2e/fixtures/python_face_runnable.pyaff — Python-shaped source (`def`, `if/else`, recursion), `# face: python` pragma. - test/e2e/python_face_e2e.sh — compiles it THROUGH the Python face to core-WASM and asserts main() === 5170 (fac(5)=120 + sum_to(100)=5050). Skips loudly (exit 0) without the compiler or node; AFFINESCRIPT_BIN overrides the binary path. Observed (node 26, dune 3.24 build): Compiled fixtures/python_face_runnable.pyaff -> pf.wasm (WASM) main() = 5170 (expected 5170) PASS: Python-face program ran end-to-end (face -> wasm -> correct result) Recursion, not a loop, on purpose: proving the face path surfaced two real bugs, filed with minimal repros: - #683 — the Python face drops the trailing `;` on the last statement of a while/for body (tail-position detection doesn't distinguish loop bodies from value bodies), so face-authored loops don't yet compile. Recursion's tail line is a value expression, which the face handles correctly. - #682 — `total` is rejected as a variable name in let-binding / assignment position (the `TOTAL` soft-keyword's ident-recovery is incomplete). The fixture avoids that name. So this is an honest end-to-end proof of the face→wasm path today, and a regression lock, with the known loop gap tracked in #683. Gates: dune build 0; face e2e PASS; doc-truthing OK; soundness ledger all-5 OK. Co-Authored-By: Claude Opus 4.8 (1M context) --- test/e2e/fixtures/python_face_runnable.pyaff | 30 ++++++++++++++++ test/e2e/python_face_e2e.sh | 37 ++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 test/e2e/fixtures/python_face_runnable.pyaff create mode 100755 test/e2e/python_face_e2e.sh diff --git a/test/e2e/fixtures/python_face_runnable.pyaff b/test/e2e/fixtures/python_face_runnable.pyaff new file mode 100644 index 00000000..a7f32f81 --- /dev/null +++ b/test/e2e/fixtures/python_face_runnable.pyaff @@ -0,0 +1,30 @@ +# face: python +# SPDX-License-Identifier: MPL-2.0 +# +# End-to-end proof (2026-07-07) that a *face-authored* program compiles +# through the Python face → canonical → typecheck → wasm and RUNS with the +# right answer. This is the README's headline claim ("faces are the product") +# exercised for real, not just parsed. +# +# Recursion, not a loop: the Python face currently drops the trailing `;` on +# the last statement of a while/for body (issue #683), so face-authored loops +# don't yet compile. Recursion's tail line is a value expression, which the +# face handles correctly — so this proves the face→wasm path without waiting +# on #683. (Variable names also avoid the `total` soft-keyword collision, +# issue #682.) + +def fac(n: Int) -> Int: + if n == 0: + 1 + else: + n * fac(n - 1) + +def sum_to(n: Int) -> Int: + if n == 0: + 0 + else: + n + sum_to(n - 1) + +def main() -> Int: + # fac(5)=120, sum_to(100)=5050 -> 120 + 5050 = 5170 + fac(5) + sum_to(100) diff --git a/test/e2e/python_face_e2e.sh b/test/e2e/python_face_e2e.sh new file mode 100755 index 00000000..b01cd96d --- /dev/null +++ b/test/e2e/python_face_e2e.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# End-to-end proof that a Python-face-authored program runs: compile +# fixtures/python_face_runnable.pyaff *through the Python face* to core-WASM +# and assert main() === 5170 (fac(5)=120 + sum_to(100)=5050). Exercises the +# README's "faces are the product" claim as running code, not just a parse. +# +# Requires the compiler built (dune build → _build/default/bin/main.exe; +# override with AFFINESCRIPT_BIN) and node on PATH. Skips loudly (exit 0) +# if either is absent. +set -uo pipefail +cd "$(dirname "$0")" +REPO="$(cd ../.. && pwd)" +BIN="${AFFINESCRIPT_BIN:-$REPO/_build/default/bin/main.exe}" +SRC="fixtures/python_face_runnable.pyaff" +EXPECT=5170 + +[ -x "$BIN" ] || { echo "SKIP: compiler not built ($BIN) — run dune build"; exit 0; } +command -v node >/dev/null 2>&1 || { echo "SKIP: node not on PATH"; exit 0; } + +TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT +WASM="$TMP/pf.wasm" + +# The `# face: python` pragma on line 1 selects the face; no --face needed. +"$BIN" compile "$SRC" -o "$WASM" || { echo "FAIL: compile through Python face"; exit 1; } + +node - "$WASM" "$EXPECT" <<'JS' +import { readFile } from "node:fs/promises"; +const [wasm, expect] = [process.argv[2], Number(process.argv[3])]; +const { instance } = await WebAssembly.instantiate( + await readFile(wasm), { wasi_snapshot_preview1: { fd_write: () => 0 } }); +const got = instance.exports.main(); +console.log(`main() = ${got} (expected ${expect})`); +if (got !== expect) { console.error("FAIL: wrong result"); process.exit(1); } +console.log("PASS: Python-face program ran end-to-end (face → wasm → correct result)"); +JS