mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-06 08:23:19 +08:00
Scope-B milestone NI-3. When a program loads Node in-process, its pending ticks, promise jobs, timers and immediates move onto Node's queues in order (timers keep their deadlines relative to the moment Node began loading; the work of a phase the switch interrupted finishes first) and every later one is scheduled there, so the jobs of both sides interleave as in Node. The runtime's I/O hooks are watched from Node's loop, and its liveness is re-read before each poll phase, after each check phase and after every microtask checkpoint. Node's loop runs on Node's stack, and V8's limit follows whichever stack an entry comes from. Island signal and exit listeners join the runtime's registries at their position, so the runtime keeps owning every signal disposition; Node's own 'exit' emission is left to the runtime's exit sequence, and the exit code island code leaves in process.exitCode becomes the program's. Bridge ABI 3. Ordering programs (tests/corpus-node-islands-order, harness node-island-order.test.ts), corpus programs 6804-6811 and the node-island-process harness pin the behavior against Node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
/* The libnode bridge against a real pack, without the compiler: the C host
|
|
* in bridge-smoke.c loads the pack the way scr_node_island.c does and makes
|
|
* calls through the bridge with the runtime's embedded bootstrap. Needs a
|
|
* pack in SCRIPTC_LIBNODE; required in the Linux x64 CI lanes
|
|
* (tests/harness/libnode.ts). */
|
|
import { execFile } from "node:child_process";
|
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
import { expect, test } from "vitest";
|
|
import { libnodePack } from "../../../tests/harness/libnode.js";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const here = import.meta.dirname;
|
|
const outDir = join(here, "../../../node_modules/.cache/scriptc-tests/libnode-bridge");
|
|
|
|
test("a libnode pack runs calls through the bridge", async ({ skip }) => {
|
|
const pack = libnodePack();
|
|
if (pack === null) return skip();
|
|
mkdirSync(outDir, { recursive: true });
|
|
const module = join(outDir, "smoke.cjs");
|
|
writeFileSync(
|
|
module,
|
|
`"use strict";
|
|
exports.add = (a, b) => a + b;
|
|
exports.fails = () => { const e = new RangeError("out of range"); e.code = "ERR_X"; throw e; };
|
|
exports.later = (ms) => new Promise((resolve) => setTimeout(() => resolve({ waited: ms }), ms));
|
|
exports.quit = (code) => process.exit(code);
|
|
`,
|
|
);
|
|
const binary = join(outDir, "bridge-smoke");
|
|
await execFileAsync("cc", [
|
|
"-O1",
|
|
"-I",
|
|
join(here, "../../runtime/src"),
|
|
join(here, "bridge-smoke.c"),
|
|
"-ldl",
|
|
"-o",
|
|
binary,
|
|
]);
|
|
const result = await execFileAsync(binary, [pack, module], { encoding: "utf8" }).then(
|
|
({ stdout, stderr }) => ({ stdout, stderr, code: 0 }),
|
|
(err: { stdout: string; stderr: string; code: number }) => err,
|
|
);
|
|
expect(result.stderr).toBe("");
|
|
expect(result.stdout).toBe(
|
|
[
|
|
"abi 3 ok 1",
|
|
"sync add -> R5",
|
|
'sync fails -> T{"error":{"kind":2,"message":"out of range","code":"ERR_X"}}',
|
|
`sync missing -> T{"error":{"kind":1,"message":"'missing' exported by './smoke.cjs' is not a function"}}`,
|
|
"async later -> pending",
|
|
'settle 7 R{"waited":5}',
|
|
"exit 3",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
expect(result.code).toBe(3);
|
|
});
|