mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-04 01:18:49 +08:00
Build the Linux LLVM helper and runtime pack before running both test lanes, and align native cache contracts with the current target policy. Full managed Sandbox gate passed.
144 lines
6.4 KiB
TypeScript
144 lines
6.4 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { release as osRelease, tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
import { expect, test } from "vitest";
|
|
import { precompiledRuntimePackTarget } from "../../compiler/src/startup-cache.js";
|
|
import { platformLinkerSupportsPersistentCache } from "../../compiler/src/backend/linker.js";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const repoRoot = join(import.meta.dirname, "../../..");
|
|
const bootstrap = join(repoRoot, "packages/cli/dist/bootstrap.js");
|
|
const runtimePackHost = process.platform === "darwin" && process.arch === "arm64" &&
|
|
Number.parseInt(osRelease().split(".", 1)[0] ?? "", 10) >= 24;
|
|
const helperTarget = precompiledRuntimePackTarget();
|
|
const persistentExecutable = helperTarget === null || platformLinkerSupportsPersistentCache(process.env, helperTarget);
|
|
|
|
test("bootstrap serves version and help without loading the compiler graph", async () => {
|
|
const preloadDir = await mkdtemp(join(tmpdir(), "scriptc-bootstrap-preload-"));
|
|
const preload = join(preloadDir, "preload.mjs");
|
|
try {
|
|
await writeFile(preload, [
|
|
"import { registerHooks } from 'node:module';",
|
|
"registerHooks({ load(url, context, nextLoad) {",
|
|
" if (url.includes('/packages/compiler/dist/index.js')) throw new Error('compiler graph loaded');",
|
|
" return nextLoad(url, context);",
|
|
"}});",
|
|
"",
|
|
].join("\n"));
|
|
const version = await execFileAsync(process.execPath, ["--import", preload, bootstrap, "--version"]);
|
|
expect(version.stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/);
|
|
const help = await execFileAsync(process.execPath, ["--import", preload, bootstrap, "--help"]);
|
|
expect(help.stdout).toContain("scriptc build <file.ts|.js>");
|
|
} finally {
|
|
await rm(preloadDir, { recursive: true, force: true }).catch(() => undefined);
|
|
}
|
|
});
|
|
|
|
test("bootstrap exact builds use the routed cache and source edits fall through", async () => {
|
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-bootstrap-cache-"));
|
|
const cacheRoot = join(dir, "cache");
|
|
const entry = join(dir, "main.ts");
|
|
const outDir = join(dir, ".scriptc");
|
|
const outPath = join(outDir, process.platform === "win32" ? "main.exe" : "main");
|
|
const preload = join(dir, "reject-full-compiler.mjs");
|
|
const env = { ...process.env, SCRIPTC_CACHE_DIR: cacheRoot, SCRIPTC_TIMING: "1" };
|
|
const build = (rejectFullCompiler = false): Promise<{ stderr: string }> =>
|
|
execFileAsync(process.execPath, [
|
|
...(rejectFullCompiler ? ["--import", preload] : []),
|
|
bootstrap,
|
|
"build",
|
|
entry,
|
|
], {
|
|
env,
|
|
maxBuffer: 4 * 1024 * 1024,
|
|
});
|
|
try {
|
|
await mkdir(cacheRoot, { mode: 0o700 });
|
|
await Promise.all([
|
|
writeFile(entry, 'console.log("one");\n'),
|
|
writeFile(preload, [
|
|
"import { registerHooks } from 'node:module';",
|
|
"registerHooks({ load(url, context, nextLoad) {",
|
|
" if (url.includes('/packages/compiler/dist/index.js')) throw new Error('compiler graph loaded');",
|
|
" return nextLoad(url, context);",
|
|
"}});",
|
|
"",
|
|
].join("\n")),
|
|
]);
|
|
expect((await build()).stderr).toContain("scriptc lowering");
|
|
expect((await build()).stderr).not.toContain("scriptc lowering");
|
|
expect((await execFileAsync(outPath)).stdout).toBe("one\n");
|
|
|
|
// A source-primary build may replace the cached executable between exact
|
|
// invocations. The shipped bootstrap must not leave its stale IR sibling
|
|
// behind, whether cache validation returns directly or falls through.
|
|
const staleIr = join(outDir, "main.ir.json");
|
|
await writeFile(staleIr, "stale source-primary IR\n");
|
|
await expect(build()).resolves.toBeDefined();
|
|
await expect(readFile(staleIr)).rejects.toMatchObject({ code: "ENOENT" });
|
|
|
|
// Route metadata can be evicted independently of the executable payload.
|
|
// One full-compiler fallback must repair it so the following invocation is
|
|
// once again able to run with the package root import forbidden.
|
|
await Promise.all([
|
|
rm(join(cacheRoot, "early-exe-route"), { recursive: true, force: true }),
|
|
rm(join(cacheRoot, "early-exe-implementation"), { recursive: true, force: true }),
|
|
]);
|
|
expect((await build()).stderr).not.toContain("scriptc lowering");
|
|
if (persistentExecutable) {
|
|
await expect(build(true)).resolves.toMatchObject({ stderr: "" });
|
|
} else {
|
|
// This target intentionally lacks a persistent native dependency proof.
|
|
// It must enter the full compiler to relink, even on a frontend hit.
|
|
await expect(build(true)).rejects.toMatchObject({ stderr: expect.stringContaining("compiler graph loaded") });
|
|
expect((await build()).stderr).not.toContain("scriptc lowering");
|
|
expect((await execFileAsync(outPath)).stdout).toBe("one\n");
|
|
}
|
|
|
|
await writeFile(entry, 'console.log("two");\n');
|
|
expect((await build()).stderr).toContain("scriptc lowering");
|
|
expect((await build()).stderr).not.toContain("scriptc lowering");
|
|
expect((await execFileAsync(outPath)).stdout).toBe("two\n");
|
|
expect(await readFile(join(outDir, "main.ll"), "utf8")).toContain("two");
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
}, 120_000);
|
|
|
|
test.skipIf(!runtimePackHost)(
|
|
"bootstrap cache hits retain the legacy C executable warning",
|
|
async () => {
|
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-bootstrap-legacy-warning-"));
|
|
const cacheRoot = join(dir, "cache");
|
|
const entry = join(dir, "main.ts");
|
|
const outPath = join(dir, "program");
|
|
const env = {
|
|
...process.env,
|
|
SCRIPTC_CACHE_DIR: cacheRoot,
|
|
SCRIPTC_CC: "clang",
|
|
SCRIPTC_TIMING: "1",
|
|
};
|
|
delete env.SCRIPTC_NO_CACHE;
|
|
const build = () => execFileAsync(
|
|
process.execPath,
|
|
[bootstrap, "build", entry, "-o", outPath],
|
|
{ env, maxBuffer: 4 * 1024 * 1024 },
|
|
);
|
|
try {
|
|
await writeFile(entry, 'console.log("legacy warning");\n');
|
|
const first = await build();
|
|
expect(first.stderr).toContain("deprecated legacy C executable path");
|
|
expect(first.stderr).toContain("scriptc lowering");
|
|
|
|
const cached = await build();
|
|
expect(cached.stderr).toContain("deprecated legacy C executable path");
|
|
expect(cached.stderr).not.toContain("scriptc lowering");
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
},
|
|
120_000,
|
|
);
|