Files
scriptc/tests/harness/three.test.ts
Chris Tate a07998f667 Expand static three.js workflows and host integration (#621)
* Expand static three.js workflows and host integration

* Preserve JavaScript class state in static three.js workflows

* Preserve inferred JavaScript behavior across static workflows

* Preserve inferred callables and native compatibility boundaries

* Retain annotated JavaScript factory exports and bootstrap-safe call spreads

* Preserve Buffer and child callback identities in static packages

* Clarify JavaScript symbol spread support
2026-10-02 19:56:34 -05:00

71 lines
3.9 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { join } from "node:path";
import { expect, test } from "vitest";
import { compile } from "@scriptc/compiler";
import { balancedShardSelect } from "./shard.js";
const hasZig = spawnSync("zig", ["version"], { stdio: "ignore" }).status === 0;
const requestedMode = process.env["SCRIPTC_LLVM_TEST_MODE"];
if (requestedMode !== undefined && requestedMode !== "release" && requestedMode !== "dev") {
throw new Error("SCRIPTC_LLVM_TEST_MODE must be release or dev when set");
}
const wasiRunner = `
const {readFileSync} = require('node:fs');
const {WASI} = require('node:wasi');
const path = process.argv[1];
const wasi = new WASI({version:'preview1', args:[path], returnOnExit:true});
const module = new WebAssembly.Module(readFileSync(path));
const instance = new WebAssembly.Instance(module, wasi.getImportObject());
process.exitCode = wasi.start(instance);
`;
function run(command: string, args: string[]) {
const result = spawnSync(command, args, { encoding: "utf8", timeout: 30_000 });
if (result.error) throw result.error;
return { stdout: result.stdout, stderr: result.stderr, status: result.status, signal: result.signal };
}
// Relative scheduling estimates spread the large TSL imports and loaders
// across workers; every target/fixture pair remains in the complete gate.
const cases = balancedShardSelect(["math", "spatial", "attributes", "geometry", "scene", "materials", "mesh", "raycast-mesh", "raycast-camera", "raycast-lines-points", "buffers-animation", "collision", "package-imports", "curves", "textures", "math-extended", "procedural-geometry", "render-data", "mesh-workflows", "animation", "loaders", "gltf", "nodes"].flatMap((fixture) =>
["native", "wasm32-wasi"].map((target) => ({ fixture, target }))), ({ fixture, target }) => `three:${fixture}:${target}`,
({ fixture }) => fixture === "nodes" ? 8 : fixture === "loaders" || fixture === "gltf" ? 4 : 1);
test.for(cases)("published three.js $fixture runs statically through LLVM on $target", { timeout: 1_200_000 }, async ({ fixture, target }, context) => {
const entry = join(import.meta.dirname, `../fixtures/three/${fixture}.mjs`);
if (target === "wasm32-wasi" && !hasZig) context.skip();
const dir = await mkdtemp("/tmp/scriptc-three-");
const previousTarget = process.env["SCRIPTC_TARGET"];
const previousCc = process.env["SCRIPTC_CC"];
const wasm = target === "wasm32-wasi";
const sanitize = !wasm && process.env["SCRIPTC_SAN"] === "1";
try {
if (wasm) {
process.env["SCRIPTC_TARGET"] = target;
process.env["SCRIPTC_CC"] = "zigcc";
}
const result = await compile(entry, {
outDir: dir, outPath: join(dir, wasm ? `${fixture}.wasm` : fixture),
backend: "llvm", dynamic: false, npmStatic: ["three"],
optimization: sanitize ? "dev" : requestedMode ?? "release", sanitize,
});
if (!result.ok) throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
expect(result.backend).toBe("llvm");
if (wasm) expect([...(await readFile(result.binaryPath)).subarray(0, 4)]).toEqual([0, 97, 115, 109]);
const reference = run(process.execPath, ["--no-warnings", entry]);
expect(reference.status).toBe(0);
const actual = wasm
? run(process.execPath, ["--no-warnings", "-e", wasiRunner, result.binaryPath])
: run(result.binaryPath, []);
if (sanitize) actual.stderr = actual.stderr.split("\n").filter((line) =>
!/^==\d+==WARNING: ASan doesn't fully support makecontext\/swapcontext/.test(line)).join("\n");
expect(actual).toEqual(reference);
} finally {
if (previousTarget === undefined) delete process.env["SCRIPTC_TARGET"];
else process.env["SCRIPTC_TARGET"] = previousTarget;
if (previousCc === undefined) delete process.env["SCRIPTC_CC"];
else process.env["SCRIPTC_CC"] = previousCc;
await rm(dir, { recursive: true, force: true });
}
});