183 lines
6.3 KiB
TypeScript
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);
|
|
});
|
|
});
|