Files
2026-05-27 23:29:53 +01:00

183 lines
6.3 KiB
TypeScript

import { describe, expect, it } from "@suaveplan/testing/runner";
import { buildFixtureWasm, WITNESS_OFFSET } from "./build-wasm.js";
async function instantiate(bytes: Uint8Array) {
const mod = await WebAssembly.compile(bytes);
const instance = await WebAssembly.instantiate(mod, {});
return instance.exports as Record<string, unknown> & {
memory: WebAssembly.Memory;
};
}
describe("buildFixtureWasm", () => {
it("compiles and exports the full ABI surface", {
timeout: 30000,
}, async () => {
const bytes = buildFixtureWasm();
const exports = await instantiate(bytes);
const required = [
"memory",
"__suaveplan_alloc",
"__suaveplan_free",
"__suaveplan_handle_request",
"__suaveplan_routes",
"__suaveplan_start",
"__suaveplan_dispose",
];
for (const name of required) {
expect(exports[name]).toBeDefined();
}
});
it("default routes JSON decodes to a single GET route", {
timeout: 30000,
}, async () => {
const exports = await instantiate(buildFixtureWasm());
const routes = exports.__suaveplan_routes as () => bigint;
const packed = routes();
const ptr = Number((packed >> 32n) & 0xffff_ffffn);
const len = Number(packed & 0xffff_ffffn);
const json = new TextDecoder().decode(
new Uint8Array(exports.memory.buffer, ptr, len)
);
expect(JSON.parse(json)).toEqual([{ method: "GET", path: "/hello" }]);
});
it("handle_request copies request bytes into the witness range", {
timeout: 30000,
}, async () => {
const exports = await instantiate(buildFixtureWasm());
const alloc = exports.__suaveplan_alloc as (n: number) => number;
const handle = exports.__suaveplan_handle_request as (
p: number,
l: number
) => bigint;
const payload = new TextEncoder().encode("hello-witness");
const ptr = alloc(payload.length);
new Uint8Array(exports.memory.buffer).set(payload, ptr);
handle(ptr, payload.length);
const witnessBytes = new Uint8Array(
exports.memory.buffer,
WITNESS_OFFSET,
payload.length
);
expect(new TextDecoder().decode(witnessBytes)).toBe("hello-witness");
});
it("clamps oversize requests to WITNESS_CAPACITY", {
timeout: 30000,
}, async () => {
const exports = await instantiate(buildFixtureWasm());
const alloc = exports.__suaveplan_alloc as (n: number) => number;
const handle = exports.__suaveplan_handle_request as (
p: number,
l: number
) => bigint;
const payload = new Uint8Array(2048).fill(0x41);
const ptr = alloc(payload.length);
new Uint8Array(exports.memory.buffer).set(payload, ptr);
handle(ptr, payload.length);
// First 1024 witness bytes are populated; reading the FULL
// witness range should be all 0x41 (since the clamp wrote
// exactly WITNESS_CAPACITY bytes, not more).
const witness = new Uint8Array(
exports.memory.buffer,
WITNESS_OFFSET,
1024
);
expect(witness.every((b) => b === 0x41)).toBe(true);
});
it("allocAlwaysZero produces an alloc that returns 0", {
timeout: 30000,
}, async () => {
const exports = await instantiate(
buildFixtureWasm({ allocAlwaysZero: true })
);
const alloc = exports.__suaveplan_alloc as (n: number) => number;
expect(alloc(16)).toBe(0);
});
it("handlerTraps fixture traps via unreachable", {
timeout: 30000,
}, async () => {
const exports = await instantiate(
buildFixtureWasm({ handlerTraps: true })
);
const handle = exports.__suaveplan_handle_request as (
p: number,
l: number
) => bigint;
expect(() => handle(0, 0)).toThrow(WebAssembly.RuntimeError);
});
it("startTraps + disposeTraps both surface as RuntimeError", {
timeout: 30000,
}, async () => {
const startExports = await instantiate(
buildFixtureWasm({ startTraps: true })
);
expect(() => (startExports.__suaveplan_start as () => void)()).toThrow(
WebAssembly.RuntimeError
);
const disposeExports = await instantiate(
buildFixtureWasm({ disposeTraps: true })
);
expect(() =>
(disposeExports.__suaveplan_dispose as () => void)()
).toThrow(WebAssembly.RuntimeError);
});
it("routesTraps fixture traps __suaveplan_routes", {
timeout: 30000,
}, async () => {
const exports = await instantiate(
buildFixtureWasm({ routesTraps: true })
);
expect(() => (exports.__suaveplan_routes as () => bigint)()).toThrow(
WebAssembly.RuntimeError
);
});
it("routesEmpty returns (0, 0)", { timeout: 30000 }, async () => {
const exports = await instantiate(
buildFixtureWasm({ routesEmpty: true })
);
expect((exports.__suaveplan_routes as () => bigint)()).toBe(0n);
});
it("handlerEmptyResponse returns (0, 0)", { timeout: 30000 }, async () => {
const exports = await instantiate(
buildFixtureWasm({ handlerEmptyResponse: true })
);
const handle = exports.__suaveplan_handle_request as (
p: number,
l: number
) => bigint;
expect(handle(0, 0)).toBe(0n);
});
it("dropExports omits the requested symbols", {
timeout: 30000,
}, async () => {
const exports = await instantiate(
buildFixtureWasm({ dropExports: ["__suaveplan_start"] })
);
expect(exports.__suaveplan_start).toBeUndefined();
// Memory and the rest remain.
expect(exports.memory).toBeInstanceOf(WebAssembly.Memory);
});
it("rejects oversize routes JSON", { timeout: 5000 }, () => {
expect(() =>
buildFixtureWasm({ routesJson: "x".repeat(2048) })
).toThrow(RangeError);
});
it("rejects oversize response JSON", { timeout: 5000 }, () => {
expect(() =>
buildFixtureWasm({ responseJson: "x".repeat(2048) })
).toThrow(RangeError);
});
});