mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 08:35:07 +08:00
- Remove C emission, debugging options, and automatic backend fallback. - Complete LLVM lowering and native bootstrap coverage across supported targets. - Link executables and libraries with precompiled C runtime packs, including iOS and Android.
93 lines
3.5 KiB
TypeScript
93 lines
3.5 KiB
TypeScript
/* Piped-stdin differential tests: the corpus harness closes both
|
|
* children's stdin immediately (programs must see the same empty closed
|
|
* stream), so the stdin DATA paths — chunk delivery to once-'data'
|
|
* listeners, for-await over process.stdin, the race-then-destroy timeout
|
|
* shape — run here, where the harness controls what flows into the pipe
|
|
* and when. Node stays the oracle: same program, same stdin script, same
|
|
* stdout bytes and exit code. SCRIPTC_SAN=1 sanitizes the native side like
|
|
* the corpus. */
|
|
import { execFile } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import { mkdirSync, readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { describe, expect, test } from "vitest";
|
|
import { compile } from "@scriptc/compiler";
|
|
import { eventLoopCases, type StdinScript } from "./event-loop-cases.js";
|
|
|
|
const repoRoot = join(import.meta.dirname, "../..");
|
|
const fixtureDir = join(repoRoot, "tests/fixtures/event-loop");
|
|
const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests");
|
|
const sanitize = process.env["SCRIPTC_SAN"] === "1";
|
|
|
|
interface RunResult {
|
|
stdout: string;
|
|
exitCode: number;
|
|
}
|
|
|
|
function runWithStdin(cmd: string, args: string[], script: StdinScript): Promise<RunResult> {
|
|
return new Promise((resolve, reject) => {
|
|
const child = execFile(cmd, args, { encoding: "utf8" }, (err, stdout) => {
|
|
const e = err as { code?: unknown } | null;
|
|
if (e && typeof e.code !== "number") {
|
|
reject(err);
|
|
return;
|
|
}
|
|
resolve({ stdout, exitCode: e ? (e.code as number) : 0 });
|
|
});
|
|
void (async () => {
|
|
for (const w of script.writes) {
|
|
await new Promise((r) => setTimeout(r, w.delayMs));
|
|
child.stdin?.write(w.data);
|
|
}
|
|
if (script.end) child.stdin?.end();
|
|
// Held-open pipes close when the child exits (execFile cleans up).
|
|
})();
|
|
});
|
|
}
|
|
|
|
async function compileFixture(name: string): Promise<string> {
|
|
const file = join(fixtureDir, name);
|
|
const key = createHash("sha256")
|
|
.update(file)
|
|
.update(readFileSync(file))
|
|
.update(sanitize ? "san" : "plain")
|
|
.digest("hex")
|
|
.slice(0, 16);
|
|
const outDir = join(cacheDir, key);
|
|
mkdirSync(outDir, { recursive: true });
|
|
const result = await compile(file, {
|
|
outPath: join(outDir, "program"),
|
|
outDir,
|
|
sanitize,
|
|
// Pinned: piped-stdin timing fixtures were written against the C
|
|
// lane; lane identity stays fixed so a diff means the stdin/event-loop
|
|
// story changed, never that the default backend moved.
|
|
backend: "llvm",
|
|
});
|
|
if (!result.ok) {
|
|
throw new Error(
|
|
"event-loop fixture failed to compile:\n" +
|
|
result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"),
|
|
);
|
|
}
|
|
return result.binaryPath;
|
|
}
|
|
|
|
async function differential(fixture: string, script: StdinScript): Promise<void> {
|
|
const binary = await compileFixture(fixture);
|
|
const [nodeRes, nativeRes] = await Promise.all([
|
|
runWithStdin("node", [join(fixtureDir, fixture)], script),
|
|
runWithStdin(binary, [], script),
|
|
]);
|
|
expect(nativeRes.stdout).toBe(nodeRes.stdout);
|
|
expect(nativeRes.exitCode).toBe(nodeRes.exitCode);
|
|
}
|
|
|
|
describe("event loop: piped stdin (differential)", () => {
|
|
// The case table lives in event-loop-cases.ts: the Linux lane runs the
|
|
// identical fixtures with the identical stdin scripts in-container.
|
|
test.for(eventLoopCases.map((c) => [c.title, c] as const))("%s", async ([, c]) => {
|
|
await differential(c.fixture, c.script);
|
|
});
|
|
});
|