From 1228a0edc8098483a0d6412695a7b5fd7a822378 Mon Sep 17 00:00:00 2001 From: Ilja Heitlager Date: Fri, 6 Mar 2026 15:36:11 +0100 Subject: [PATCH 1/4] refactor: move shared lib to experiments/shared/, inline rust source - Move lib/bench.sh from 001_hello_world to experiments/shared/lib/ - Update symlinks in 001, 002, 003 to point to ../shared/lib - Copy 001_hello_world/leg3_wasmtime into 003_wasm_compile/rust-hello/ (removes cross-experiment symlink dependency) - Update Makefile, benchmark.sh, .gitignore for rust -> rust-hello rename Co-Authored-By: Claude Opus 4.6 --- experiments/001_hello_world/lib | 1 + experiments/002_chromium_sandbox/lib | 2 +- experiments/003_wasm_compile/.gitignore | 2 +- experiments/003_wasm_compile/Makefile | 4 +- experiments/003_wasm_compile/benchmark.sh | 2 +- experiments/003_wasm_compile/lib | 2 +- experiments/003_wasm_compile/rust | 1 - .../003_wasm_compile/rust-hello/Cargo.lock | 43 ++++++++++++++++ .../003_wasm_compile/rust-hello/Cargo.toml | 10 ++++ .../003_wasm_compile/rust-hello/run.sh | 49 +++++++++++++++++++ .../003_wasm_compile/rust-hello/src/lib.rs | 33 +++++++++++++ .../{001_hello_world => shared}/lib/bench.sh | 0 12 files changed, 142 insertions(+), 7 deletions(-) create mode 120000 experiments/001_hello_world/lib delete mode 120000 experiments/003_wasm_compile/rust create mode 100644 experiments/003_wasm_compile/rust-hello/Cargo.lock create mode 100644 experiments/003_wasm_compile/rust-hello/Cargo.toml create mode 100755 experiments/003_wasm_compile/rust-hello/run.sh create mode 100644 experiments/003_wasm_compile/rust-hello/src/lib.rs rename experiments/{001_hello_world => shared}/lib/bench.sh (100%) diff --git a/experiments/001_hello_world/lib b/experiments/001_hello_world/lib new file mode 120000 index 0000000..565e254 --- /dev/null +++ b/experiments/001_hello_world/lib @@ -0,0 +1 @@ +../shared/lib \ No newline at end of file diff --git a/experiments/002_chromium_sandbox/lib b/experiments/002_chromium_sandbox/lib index 207813e..565e254 120000 --- a/experiments/002_chromium_sandbox/lib +++ b/experiments/002_chromium_sandbox/lib @@ -1 +1 @@ -../001_hello_world/lib \ No newline at end of file +../shared/lib \ No newline at end of file diff --git a/experiments/003_wasm_compile/.gitignore b/experiments/003_wasm_compile/.gitignore index 43dc8bc..4c5a0db 100644 --- a/experiments/003_wasm_compile/.gitignore +++ b/experiments/003_wasm_compile/.gitignore @@ -10,7 +10,7 @@ python-raw/wit_world/ python-raw/componentize_py_types.py python-raw/componentize_py_async_support/ python-raw/poll_loop.py -rust/target/ +rust-hello/target/ as-hello/node_modules/ as-hello/build/ __pycache__/ diff --git a/experiments/003_wasm_compile/Makefile b/experiments/003_wasm_compile/Makefile index ef4ac17..a230c05 100644 --- a/experiments/003_wasm_compile/Makefile +++ b/experiments/003_wasm_compile/Makefile @@ -24,7 +24,7 @@ build: $(VENV)/bin/activate ## Compile all sources to .wasm @echo "→ Building Python (Spin)..." cd python-spin && spin build @echo "→ Building Rust baseline..." - cd rust && cargo build --target wasm32-wasip2 --release --quiet + cd rust-hello && cargo build --target wasm32-wasip2 --release --quiet @echo "→ Building AssemblyScript (asc + wasm-tools)..." cd as-hello && npm ci --silent 2>/dev/null && ./build.sh @echo "✓ All .wasm artifacts built" @@ -43,7 +43,7 @@ clean: ## Remove build artifacts rm -rf js-spin/node_modules js-spin/build js-spin/dist rm -f python-spin/app.wasm rm -f python-raw/hello-py-raw.wasm - rm -rf rust/target + rm -rf rust-hello/target rm -rf as-hello/node_modules as-hello/build $(CONTAINER_CMD) rmi -f hello-js-spin hello-py-spin 2>/dev/null || true diff --git a/experiments/003_wasm_compile/benchmark.sh b/experiments/003_wasm_compile/benchmark.sh index 19a5b22..9534bda 100755 --- a/experiments/003_wasm_compile/benchmark.sh +++ b/experiments/003_wasm_compile/benchmark.sh @@ -225,7 +225,7 @@ run_leg3() { require_port_free 5035 "Leg 3" command -v cargo &>/dev/null || fail "cargo not found" - pushd "$SCRIPT_DIR/rust" >/dev/null + pushd "$SCRIPT_DIR/rust-hello" >/dev/null APP_3=$(human_size src/lib.rs) BUILD_3=$(timed_build "cargo wasm32-wasip2" \ cargo build --target wasm32-wasip2 --release --quiet 2>/dev/null) diff --git a/experiments/003_wasm_compile/lib b/experiments/003_wasm_compile/lib index 207813e..565e254 120000 --- a/experiments/003_wasm_compile/lib +++ b/experiments/003_wasm_compile/lib @@ -1 +1 @@ -../001_hello_world/lib \ No newline at end of file +../shared/lib \ No newline at end of file diff --git a/experiments/003_wasm_compile/rust b/experiments/003_wasm_compile/rust deleted file mode 120000 index f261943..0000000 --- a/experiments/003_wasm_compile/rust +++ /dev/null @@ -1 +0,0 @@ -../001_hello_world/leg3_wasmtime \ No newline at end of file diff --git a/experiments/003_wasm_compile/rust-hello/Cargo.lock b/experiments/003_wasm_compile/rust-hello/Cargo.lock new file mode 100644 index 0000000..bd8eb65 --- /dev/null +++ b/experiments/003_wasm_compile/rust-hello/Cargo.lock @@ -0,0 +1,43 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "leg3" +version = "0.1.0" +dependencies = [ + "wasi", +] + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "bitflags", +] diff --git a/experiments/003_wasm_compile/rust-hello/Cargo.toml b/experiments/003_wasm_compile/rust-hello/Cargo.toml new file mode 100644 index 0000000..1c0b266 --- /dev/null +++ b/experiments/003_wasm_compile/rust-hello/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "leg3" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasi = "0.14" diff --git a/experiments/003_wasm_compile/rust-hello/run.sh b/experiments/003_wasm_compile/rust-hello/run.sh new file mode 100755 index 0000000..61e16c7 --- /dev/null +++ b/experiments/003_wasm_compile/rust-hello/run.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +PORT=5003 +DIR="$(cd "$(dirname "$0")" && pwd)" + +# Ensure Rust/cargo is on PATH — respects CARGO_HOME (XDG-compliant) +export PATH="${CARGO_HOME:-${XDG_DATA_HOME:-$HOME/.local/share}/cargo}/bin:$PATH" + +command -v cargo &>/dev/null || { echo "✗ cargo not found — install rustup" >&2; exit 1; } +command -v wasmtime &>/dev/null || { echo "✗ wasmtime not found — brew install wasmtime" >&2; exit 1; } + +# Ensure wasm32-wasip2 target is available +if ! rustup target list --installed 2>/dev/null | grep -q "wasm32-wasip2"; then + echo "→ Adding wasm32-wasip2 Rust target..." + rustup target add wasm32-wasip2 +fi + +cd "$DIR" + +echo "→ Building Rust WASM component (wasm32-wasip2)..." +cargo build --target wasm32-wasip2 --release 2>&1 + +WASM=$(find target/wasm32-wasip2/release -maxdepth 1 -name "*.wasm" | head -1) +if [ -z "$WASM" ]; then + echo "✗ No .wasm file found in target/wasm32-wasip2/release/" >&2 + exit 1 +fi +echo "→ Binary: $WASM ($(du -sh "$WASM" | cut -f1))" + +echo "→ Starting wasmtime serve on port $PORT..." +wasmtime serve -S cli --addr "127.0.0.1:$PORT" "$WASM" & +WASMTIME_PID=$! + +# Wait for readiness +echo -n "→ Waiting for HTTP..." +for i in $(seq 1 30); do + if curl -sf "http://127.0.0.1:$PORT/" &>/dev/null; then + echo " ready" + curl -s "http://127.0.0.1:$PORT/" | python3 -m json.tool + wait "$WASMTIME_PID" + exit 0 + fi + sleep 0.2 +done + +echo " timeout" >&2 +kill "$WASMTIME_PID" 2>/dev/null || true +exit 1 diff --git a/experiments/003_wasm_compile/rust-hello/src/lib.rs b/experiments/003_wasm_compile/rust-hello/src/lib.rs new file mode 100644 index 0000000..3f2e2fe --- /dev/null +++ b/experiments/003_wasm_compile/rust-hello/src/lib.rs @@ -0,0 +1,33 @@ +use wasi::clocks::wall_clock; +use wasi::exports::wasi::http::incoming_handler::Guest; +use wasi::http::types::{Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam}; + +struct Component; + +impl Guest for Component { + fn handle(_request: IncomingRequest, response_out: ResponseOutparam) { + let now = wall_clock::now(); + let ts = now.seconds as f64 + (now.nanoseconds as f64 / 1_000_000_000.0); + let body_str = format!(r#"{{"message":"Hello World","timestamp":{ts:.6}}}"#); + + let headers = Fields::new(); + headers + .append(&"content-type".to_string(), &b"application/json".to_vec()) + .unwrap(); + + let response = OutgoingResponse::new(headers); + response.set_status_code(200).unwrap(); + + let out_body = response.body().unwrap(); + { + let stream = out_body.write().unwrap(); + stream + .blocking_write_and_flush(body_str.as_bytes()) + .unwrap(); + } + OutgoingBody::finish(out_body, None).unwrap(); + ResponseOutparam::set(response_out, Ok(response)); + } +} + +wasi::http::proxy::export!(Component with_types_in wasi); diff --git a/experiments/001_hello_world/lib/bench.sh b/experiments/shared/lib/bench.sh similarity index 100% rename from experiments/001_hello_world/lib/bench.sh rename to experiments/shared/lib/bench.sh From 3530ae5b1379ade6260f2ec6882ca6e4a39631be Mon Sep 17 00:00:00 2001 From: Ilja Heitlager Date: Fri, 6 Mar 2026 15:59:00 +0100 Subject: [PATCH 2/4] fix(003): restore Makefile sub-targets and centralized build/ copy steps The refactor/shared-lib commit regressed the Makefile by losing the individual build-js, build-py, build-rust sub-targets and their cp-to-build/ steps. Also switches venv creation from python3 -m venv to uv, and restructures tests into source/build/runtime sections with consistent naming. Co-Authored-By: Claude Opus 4.6 --- experiments/003_wasm_compile/Makefile | 42 +++++--- .../003_wasm_compile/tests/test_003.bats | 96 ++++++++++++------- 2 files changed, 90 insertions(+), 48 deletions(-) diff --git a/experiments/003_wasm_compile/Makefile b/experiments/003_wasm_compile/Makefile index a230c05..f67b706 100644 --- a/experiments/003_wasm_compile/Makefile +++ b/experiments/003_wasm_compile/Makefile @@ -1,12 +1,14 @@ -.PHONY: deps build test bench bench-quick clean help +.PHONY: deps build build-js build-py build-rust build-as test bench bench-quick clean help CONTAINER_CMD ?= $(shell command -v podman 2>/dev/null || echo docker) -VENV := .venv -COMPONENTIZE := $(VENV)/bin/componentize-py +GIT_ROOT := $(shell git rev-parse --show-toplevel) +VENV := $(GIT_ROOT)/.venv +ACTIVATE := . $(VENV)/bin/activate && +BUILD_DIR := build $(VENV)/bin/activate: requirements.txt - python3 -m venv $(VENV) - $(VENV)/bin/pip install --quiet -r requirements.txt + uv venv --clear $(VENV) + uv pip install --quiet -r requirements.txt @touch $(VENV)/bin/activate deps: $(VENV)/bin/activate ## Install toolchain dependencies (spin, componentize-py, hey) @@ -16,29 +18,47 @@ deps: $(VENV)/bin/activate ## Install toolchain dependencies (spin, componentiz brew install hey @echo "✓ deps ready (componentize-py in $(VENV); JS deps fetched at build time by spin build)" -build: $(VENV)/bin/activate ## Compile all sources to .wasm +build: build-js build-py build-rust build-as ## Compile all sources to .wasm → build/ + @echo "✓ All .wasm artifacts in $(BUILD_DIR)/" + +build-js: ## Build JS → .wasm (Spin) @echo "→ Building JS (Spin)..." cd js-spin && spin build + @mkdir -p $(BUILD_DIR) + cp js-spin/dist/hello-js.wasm $(BUILD_DIR)/hello-js-spin.wasm + +build-py: $(VENV)/bin/activate ## Build Python → .wasm (componentize-py + Spin) + @mkdir -p $(BUILD_DIR) @echo "→ Building Python/raw (componentize-py)..." - cd python-raw && ../$(COMPONENTIZE) -d wit -w proxy componentize app -o hello-py-raw.wasm + cd python-raw && VIRTUAL_ENV= $(VENV)/bin/componentize-py -d wit -w example:hello/proxy componentize app -o hello-py-raw.wasm + cp python-raw/hello-py-raw.wasm $(BUILD_DIR)/hello-py-raw.wasm @echo "→ Building Python (Spin)..." - cd python-spin && spin build + cd python-spin && $(ACTIVATE) spin build + cp python-spin/app.wasm $(BUILD_DIR)/hello-py-spin.wasm + +build-rust: ## Build Rust → .wasm (cargo wasm32-wasip2) @echo "→ Building Rust baseline..." cd rust-hello && cargo build --target wasm32-wasip2 --release --quiet + @mkdir -p $(BUILD_DIR) + cp rust-hello/target/wasm32-wasip2/release/leg3.wasm $(BUILD_DIR)/hello-rust.wasm + +build-as: ## Build AssemblyScript → .wasm (asc + wasm-tools) @echo "→ Building AssemblyScript (asc + wasm-tools)..." - cd as-hello && npm ci --silent 2>/dev/null && ./build.sh - @echo "✓ All .wasm artifacts built" + cd as-hello && npm ci --silent && ./build.sh + @mkdir -p $(BUILD_DIR) + cp as-hello/build/hello-as.wasm $(BUILD_DIR)/hello-as.wasm test: ## Run BATS unit tests bats tests/ -bench: ## Run full 6-leg benchmark +bench: ## Run full 7-leg benchmark ./benchmark.sh bench-quick: ## Quick benchmark (10 requests) HEY_N=10 ./benchmark.sh clean: ## Remove build artifacts + rm -rf $(BUILD_DIR) rm -rf $(VENV) rm -rf js-spin/node_modules js-spin/build js-spin/dist rm -f python-spin/app.wasm diff --git a/experiments/003_wasm_compile/tests/test_003.bats b/experiments/003_wasm_compile/tests/test_003.bats index a3ae30c..f2ac573 100644 --- a/experiments/003_wasm_compile/tests/test_003.bats +++ b/experiments/003_wasm_compile/tests/test_003.bats @@ -1,12 +1,18 @@ #!/usr/bin/env bats # Experiment 003 — BATS tests -# Validates that each WASM HTTP leg returns valid JSON with expected shape. +# Validates sources, build artifacts, and runtime legs. # -# Prerequisites: all legs must already be built (make build) and running -# on their designated ports. These tests are run by benchmark.sh inline; -# for standalone use start each leg manually first. +# Build artifacts: +# build/hello-js-spin.wasm — JS compiled via Spin (legs 1a, 1b) +# build/hello-py-raw.wasm — Python compiled via componentize-py (leg 2a) +# build/hello-py-spin.wasm — Python compiled via Spin (legs 2b, 2c) +# build/hello-rust.wasm — Rust compiled via cargo (leg 3) +# build/hello-as.wasm — AssemblyScript via asc + wasm-tools (leg 4) SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)" +GIT_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)" +PYTHON="uv run --directory $GIT_ROOT python" +BUILD_DIR="$SCRIPT_DIR/build" # ── Helper: fetch JSON from a running leg ───────────────────────────────────── json_get() { @@ -17,7 +23,7 @@ json_get() { # ── Source validation helper ────────────────────────────────────────────────── valid_hello_json() { local json=$1 - echo "$json" | python3 -c " + echo "$json" | $PYTHON -c " import sys, json d = json.load(sys.stdin) assert 'message' in d, 'missing message field' @@ -28,90 +34,106 @@ print('ok') " } -# ── Python source file smoke tests (no runtime required) ───────────────────── - -@test "python-spin/app.py is syntactically valid Python" { - python3 -m py_compile "$SCRIPT_DIR/python-spin/app.py" +# ── Helper: skip if port not listening ──────────────────────────────────────── +skip_if_no_port() { + local port=$1 + if ! curl -sf --max-time 1 "http://127.0.0.1:${port}/" >/dev/null 2>&1; then + skip "port ${port} not listening — start the leg first" + fi } -@test "python-raw/app.py is syntactically valid Python" { - python3 -m py_compile "$SCRIPT_DIR/python-raw/app.py" +# ── Source checks (no build required) ───────────────────────────────────────── + +@test "source: python-spin/app.py is valid Python" { + $PYTHON -m py_compile "$SCRIPT_DIR/python-spin/app.py" } -# ── spin.toml presence ─────────────────────────────────────────────────────── +@test "source: python-raw/app.py is valid Python" { + $PYTHON -m py_compile "$SCRIPT_DIR/python-raw/app.py" +} -@test "js-spin/spin.toml exists" { +@test "source: js-spin/spin.toml exists" { [ -f "$SCRIPT_DIR/js-spin/spin.toml" ] } -@test "python-spin/spin.toml exists" { +@test "source: python-spin/spin.toml exists" { [ -f "$SCRIPT_DIR/python-spin/spin.toml" ] } -# ── WIT interface ───────────────────────────────────────────────────────────── - -@test "python-raw/wit/proxy.wit exists" { +@test "source: python-raw/wit/proxy.wit exists" { [ -f "$SCRIPT_DIR/python-raw/wit/proxy.wit" ] } -# ── AssemblyScript source ──────────────────────────────────────────────────── - -@test "as-hello/assembly/index.ts exists" { +@test "source: as-hello/assembly/index.ts exists" { [ -f "$SCRIPT_DIR/as-hello/assembly/index.ts" ] } -@test "as-hello/build.sh is executable" { +@test "source: as-hello/build.sh is executable" { [ -x "$SCRIPT_DIR/as-hello/build.sh" ] } -# ── Runtime leg tests (skipped when ports not listening) ───────────────────── +# ── Build artifact checks (require: make build) ────────────────────────────── + +@test "build: hello-js-spin.wasm exists" { + [ -f "$BUILD_DIR/hello-js-spin.wasm" ] +} + +@test "build: hello-py-raw.wasm exists" { + [ -f "$BUILD_DIR/hello-py-raw.wasm" ] +} + +@test "build: hello-py-spin.wasm exists" { + [ -f "$BUILD_DIR/hello-py-spin.wasm" ] +} + +@test "build: hello-rust.wasm exists" { + [ -f "$BUILD_DIR/hello-rust.wasm" ] +} + +@test "build: hello-as.wasm exists" { + [ -f "$BUILD_DIR/hello-as.wasm" ] +} + +# ── Runtime leg tests (skipped when ports not listening) ────────────────────── -@test "leg 1a JS/Spin native — returns Hello World JSON" { +@test "leg 1a: JS/Spin native (port 5030) — returns Hello World JSON" { skip_if_no_port 5030 result=$(json_get 5030) [ "$(valid_hello_json "$result")" = "ok" ] } -@test "leg 1b JS/Spin podman — returns Hello World JSON" { +@test "leg 1b: JS/Spin podman (port 5031) — returns Hello World JSON" { skip_if_no_port 5031 result=$(json_get 5031) [ "$(valid_hello_json "$result")" = "ok" ] } -@test "leg 2a Python/raw wasmtime — returns Hello World JSON" { +@test "leg 2a: Python/raw wasmtime (port 5032) — returns Hello World JSON" { skip_if_no_port 5032 result=$(json_get 5032) [ "$(valid_hello_json "$result")" = "ok" ] } -@test "leg 2b Python/Spin native — returns Hello World JSON" { +@test "leg 2b: Python/Spin native (port 5033) — returns Hello World JSON" { skip_if_no_port 5033 result=$(json_get 5033) [ "$(valid_hello_json "$result")" = "ok" ] } -@test "leg 2c Python/Spin podman — returns Hello World JSON" { +@test "leg 2c: Python/Spin podman (port 5034) — returns Hello World JSON" { skip_if_no_port 5034 result=$(json_get 5034) [ "$(valid_hello_json "$result")" = "ok" ] } -@test "leg 3 Rust/wasmtime baseline — returns Hello World JSON" { +@test "leg 3: Rust/wasmtime baseline (port 5035) — returns Hello World JSON" { skip_if_no_port 5035 result=$(json_get 5035) [ "$(valid_hello_json "$result")" = "ok" ] } -@test "leg 4 AS/wasmtime — returns Hello World JSON" { +@test "leg 4: AS/wasmtime (port 5036) — returns Hello World JSON" { skip_if_no_port 5036 result=$(json_get 5036) [ "$(valid_hello_json "$result")" = "ok" ] } - -# ── Helper loaded after test definitions ────────────────────────────────────── -skip_if_no_port() { - local port=$1 - if ! curl -sf --max-time 1 "http://127.0.0.1:${port}/" >/dev/null 2>&1; then - skip "port ${port} not listening — start the leg first" - fi -} From 72bbbcf3965cf2152454305c2f0db27ed5116941 Mon Sep 17 00:00:00 2001 From: Ilja Heitlager Date: Fri, 6 Mar 2026 16:19:02 +0100 Subject: [PATCH 3/4] fix(003): fix python-spin build with spin-sdk 3.4.1 + componentize-py 0.17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update app.py to use class-based IncomingHandler API (spin-sdk 3.x) - Update spin.toml build command to use componentize-py on PATH (no hardcoded venv path, no -d wit flag — spin-sdk provides WIT) - Add --all-features to python-raw build for @unstable WIT annotations - Pin requirements: componentize-py>=0.17,<0.18 + spin-sdk>=3.4,<4 Co-Authored-By: Claude Opus 4.6 --- experiments/003_wasm_compile/Makefile | 2 +- .../003_wasm_compile/python-spin/app.py | 28 +++++++++---------- .../003_wasm_compile/python-spin/spin.toml | 2 +- experiments/003_wasm_compile/requirements.txt | 3 +- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/experiments/003_wasm_compile/Makefile b/experiments/003_wasm_compile/Makefile index f67b706..0ace639 100644 --- a/experiments/003_wasm_compile/Makefile +++ b/experiments/003_wasm_compile/Makefile @@ -30,7 +30,7 @@ build-js: ## Build JS → .wasm (Spin) build-py: $(VENV)/bin/activate ## Build Python → .wasm (componentize-py + Spin) @mkdir -p $(BUILD_DIR) @echo "→ Building Python/raw (componentize-py)..." - cd python-raw && VIRTUAL_ENV= $(VENV)/bin/componentize-py -d wit -w example:hello/proxy componentize app -o hello-py-raw.wasm + cd python-raw && VIRTUAL_ENV= $(VENV)/bin/componentize-py -d wit -w example:hello/proxy --all-features componentize app -o hello-py-raw.wasm cp python-raw/hello-py-raw.wasm $(BUILD_DIR)/hello-py-raw.wasm @echo "→ Building Python (Spin)..." cd python-spin && $(ACTIVATE) spin build diff --git a/experiments/003_wasm_compile/python-spin/app.py b/experiments/003_wasm_compile/python-spin/app.py index 07c1cdd..a12d1a5 100644 --- a/experiments/003_wasm_compile/python-spin/app.py +++ b/experiments/003_wasm_compile/python-spin/app.py @@ -1,19 +1,19 @@ import json import time -from spin_sdk import http -from spin_sdk.http import Request, Response +from spin_sdk.http import IncomingHandler, Request, Response -def handle_request(request: Request) -> Response: - body = json.dumps( - { - "message": "Hello World", - "timestamp": time.time(), - } - ) - return Response( - 200, - {"content-type": "application/json"}, - bytes(body, "utf-8"), - ) +class IncomingHandler(IncomingHandler): + def handle_request(self, request: Request) -> Response: + body = json.dumps( + { + "message": "Hello World", + "timestamp": time.time(), + } + ) + return Response( + 200, + {"content-type": "application/json"}, + bytes(body, "utf-8"), + ) diff --git a/experiments/003_wasm_compile/python-spin/spin.toml b/experiments/003_wasm_compile/python-spin/spin.toml index 22827aa..b236cd4 100644 --- a/experiments/003_wasm_compile/python-spin/spin.toml +++ b/experiments/003_wasm_compile/python-spin/spin.toml @@ -14,5 +14,5 @@ source = "app.wasm" allowed_outbound_hosts = [] [component.hello-py.build] -command = "../.venv/bin/componentize-py -d wit -w proxy componentize app -o app.wasm" +command = "componentize-py -w spin-http componentize app -o app.wasm" watch = ["app.py"] diff --git a/experiments/003_wasm_compile/requirements.txt b/experiments/003_wasm_compile/requirements.txt index 8ea3771..438c236 100644 --- a/experiments/003_wasm_compile/requirements.txt +++ b/experiments/003_wasm_compile/requirements.txt @@ -1 +1,2 @@ -componentize-py +componentize-py>=0.17,<0.18 +spin-sdk>=3.4,<4 From 20229e653e42d90dfc7d803fdcb1e95844cb9712 Mon Sep 17 00:00:00 2001 From: Ilja Heitlager Date: Fri, 6 Mar 2026 16:28:34 +0100 Subject: [PATCH 4/4] fix(003): restore enumerated NNN_component test naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore the test naming convention from 5ebe81b, grouped by component (js-spin, py-raw, py-spin, rust) with source → build → runtime order. Add AS tests as 016–019. Co-Authored-By: Claude Opus 4.6 --- .../003_wasm_compile/tests/test_003.bats | 123 +++++++++--------- 1 file changed, 64 insertions(+), 59 deletions(-) diff --git a/experiments/003_wasm_compile/tests/test_003.bats b/experiments/003_wasm_compile/tests/test_003.bats index f2ac573..181a102 100644 --- a/experiments/003_wasm_compile/tests/test_003.bats +++ b/experiments/003_wasm_compile/tests/test_003.bats @@ -2,25 +2,27 @@ # Experiment 003 — BATS tests # Validates sources, build artifacts, and runtime legs. # -# Build artifacts: -# build/hello-js-spin.wasm — JS compiled via Spin (legs 1a, 1b) -# build/hello-py-raw.wasm — Python compiled via componentize-py (leg 2a) -# build/hello-py-spin.wasm — Python compiled via Spin (legs 2b, 2c) -# build/hello-rust.wasm — Rust compiled via cargo (leg 3) -# build/hello-as.wasm — AssemblyScript via asc + wasm-tools (leg 4) +# Naming: NNN_component — phase: description +# +# Build artifacts (in build/): +# hello-js-spin.wasm — JS compiled via Spin (legs 1a, 1b) +# hello-py-raw.wasm — Python compiled via componentize-py (leg 2a) +# hello-py-spin.wasm — Python compiled via Spin (legs 2b, 2c) +# hello-rust.wasm — Rust compiled via cargo (leg 3) +# hello-as.wasm — AssemblyScript via asc + wasm-tools (leg 4) SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)" GIT_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)" PYTHON="uv run --directory $GIT_ROOT python" BUILD_DIR="$SCRIPT_DIR/build" -# ── Helper: fetch JSON from a running leg ───────────────────────────────────── +# ── Helpers ─────────────────────────────────────────────────────────────────── + json_get() { local port=$1 path=${2:-/} curl -sf "http://127.0.0.1:${port}${path}" } -# ── Source validation helper ────────────────────────────────────────────────── valid_hello_json() { local json=$1 echo "$json" | $PYTHON -c " @@ -34,7 +36,6 @@ print('ok') " } -# ── Helper: skip if port not listening ──────────────────────────────────────── skip_if_no_port() { local port=$1 if ! curl -sf --max-time 1 "http://127.0.0.1:${port}/" >/dev/null 2>&1; then @@ -42,97 +43,101 @@ skip_if_no_port() { fi } -# ── Source checks (no build required) ───────────────────────────────────────── - -@test "source: python-spin/app.py is valid Python" { - $PYTHON -m py_compile "$SCRIPT_DIR/python-spin/app.py" -} - -@test "source: python-raw/app.py is valid Python" { - $PYTHON -m py_compile "$SCRIPT_DIR/python-raw/app.py" -} +# ── js-spin ─────────────────────────────────────────────────────────────────── -@test "source: js-spin/spin.toml exists" { +@test "001_js-spin — source: spin.toml exists" { [ -f "$SCRIPT_DIR/js-spin/spin.toml" ] } -@test "source: python-spin/spin.toml exists" { - [ -f "$SCRIPT_DIR/python-spin/spin.toml" ] -} - -@test "source: python-raw/wit/proxy.wit exists" { - [ -f "$SCRIPT_DIR/python-raw/wit/proxy.wit" ] +@test "002_js-spin — build: hello-js-spin.wasm exists" { + [ -f "$BUILD_DIR/hello-js-spin.wasm" ] } -@test "source: as-hello/assembly/index.ts exists" { - [ -f "$SCRIPT_DIR/as-hello/assembly/index.ts" ] +@test "003_js-spin — leg 1a: native (port 5030) returns Hello World JSON" { + skip_if_no_port 5030 + result=$(json_get 5030) + [ "$(valid_hello_json "$result")" = "ok" ] } -@test "source: as-hello/build.sh is executable" { - [ -x "$SCRIPT_DIR/as-hello/build.sh" ] +@test "004_js-spin — leg 1b: podman (port 5031) returns Hello World JSON" { + skip_if_no_port 5031 + result=$(json_get 5031) + [ "$(valid_hello_json "$result")" = "ok" ] } -# ── Build artifact checks (require: make build) ────────────────────────────── +# ── py-raw ──────────────────────────────────────────────────────────────────── -@test "build: hello-js-spin.wasm exists" { - [ -f "$BUILD_DIR/hello-js-spin.wasm" ] -} - -@test "build: hello-py-raw.wasm exists" { - [ -f "$BUILD_DIR/hello-py-raw.wasm" ] +@test "005_py-raw — source: app.py is valid Python" { + $PYTHON -m py_compile "$SCRIPT_DIR/python-raw/app.py" } -@test "build: hello-py-spin.wasm exists" { - [ -f "$BUILD_DIR/hello-py-spin.wasm" ] +@test "006_py-raw — source: wit/proxy.wit exists" { + [ -f "$SCRIPT_DIR/python-raw/wit/proxy.wit" ] } -@test "build: hello-rust.wasm exists" { - [ -f "$BUILD_DIR/hello-rust.wasm" ] +@test "007_py-raw — build: hello-py-raw.wasm exists" { + [ -f "$BUILD_DIR/hello-py-raw.wasm" ] } -@test "build: hello-as.wasm exists" { - [ -f "$BUILD_DIR/hello-as.wasm" ] +@test "008_py-raw — leg 2a: wasmtime (port 5032) returns Hello World JSON" { + skip_if_no_port 5032 + result=$(json_get 5032) + [ "$(valid_hello_json "$result")" = "ok" ] } -# ── Runtime leg tests (skipped when ports not listening) ────────────────────── +# ── py-spin ─────────────────────────────────────────────────────────────────── -@test "leg 1a: JS/Spin native (port 5030) — returns Hello World JSON" { - skip_if_no_port 5030 - result=$(json_get 5030) - [ "$(valid_hello_json "$result")" = "ok" ] +@test "009_py-spin — source: app.py is valid Python" { + $PYTHON -m py_compile "$SCRIPT_DIR/python-spin/app.py" } -@test "leg 1b: JS/Spin podman (port 5031) — returns Hello World JSON" { - skip_if_no_port 5031 - result=$(json_get 5031) - [ "$(valid_hello_json "$result")" = "ok" ] +@test "010_py-spin — source: spin.toml exists" { + [ -f "$SCRIPT_DIR/python-spin/spin.toml" ] } -@test "leg 2a: Python/raw wasmtime (port 5032) — returns Hello World JSON" { - skip_if_no_port 5032 - result=$(json_get 5032) - [ "$(valid_hello_json "$result")" = "ok" ] +@test "011_py-spin — build: hello-py-spin.wasm exists" { + [ -f "$BUILD_DIR/hello-py-spin.wasm" ] } -@test "leg 2b: Python/Spin native (port 5033) — returns Hello World JSON" { +@test "012_py-spin — leg 2b: native (port 5033) returns Hello World JSON" { skip_if_no_port 5033 result=$(json_get 5033) [ "$(valid_hello_json "$result")" = "ok" ] } -@test "leg 2c: Python/Spin podman (port 5034) — returns Hello World JSON" { +@test "013_py-spin — leg 2c: podman (port 5034) returns Hello World JSON" { skip_if_no_port 5034 result=$(json_get 5034) [ "$(valid_hello_json "$result")" = "ok" ] } -@test "leg 3: Rust/wasmtime baseline (port 5035) — returns Hello World JSON" { +# ── rust ────────────────────────────────────────────────────────────────────── + +@test "014_rust — build: hello-rust.wasm exists" { + [ -f "$BUILD_DIR/hello-rust.wasm" ] +} + +@test "015_rust — leg 3: wasmtime (port 5035) returns Hello World JSON" { skip_if_no_port 5035 result=$(json_get 5035) [ "$(valid_hello_json "$result")" = "ok" ] } -@test "leg 4: AS/wasmtime (port 5036) — returns Hello World JSON" { +# ── as-hello ────────────────────────────────────────────────────────────────── + +@test "016_as — source: assembly/index.ts exists" { + [ -f "$SCRIPT_DIR/as-hello/assembly/index.ts" ] +} + +@test "017_as — source: build.sh is executable" { + [ -x "$SCRIPT_DIR/as-hello/build.sh" ] +} + +@test "018_as — build: hello-as.wasm exists" { + [ -f "$BUILD_DIR/hello-as.wasm" ] +} + +@test "019_as — leg 4: wasmtime (port 5036) returns Hello World JSON" { skip_if_no_port 5036 result=$(json_get 5036) [ "$(valid_hello_json "$result")" = "ok" ]