Keep self-hosting test workers responsive during compilation

- Run runtime-pack and native-toolchain compiler passes in child processes.
- Preserve static coverage and native behavior assertions without relying on an unsupported RPC timeout setting.
This commit is contained in:
Chris Tate
2026-09-29 13:25:48 -05:00
parent 3750065f1b
commit 56c5ccdbc4
4 changed files with 45 additions and 10 deletions
-2
View File
@@ -22,8 +22,6 @@ jobs:
# calling onTaskUpdate") with every test green — the same contention
# artifact the harness README documents for local parallel suites.
SCRIPTC_TEST_WORKERS: "4"
# Native self-hosting analysis can block worker RPC during lowering.
VITEST_RPC_TIMEOUT: "180000"
# san = ASan + RC audit over the same suite; anything but "1" is plain.
SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }}
# Within-file partition for the large case-driven harnesses
@@ -0,0 +1,37 @@
import { execFile } from "node:child_process";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import type { AnalyzeOptions, CompileOptions, CompileExecutableResult, CoverageInput } from "@scriptc/compiler";
const execFileAsync = promisify(execFile);
const root = fileURLToPath(new URL("../../", import.meta.url));
const api = new URL("../../packages/compiler/src/index.ts", import.meta.url).href;
// Large synchronous frontend passes must leave Vitest's worker RPC responsive.
// Execute the source API in a child and return only JSON-compatible results.
async function compilerProcess(operation: "analyze" | "compile", entry: string, options: object): Promise<unknown> {
const { stdout, stderr } = await execFileAsync(process.execPath, [
"--max-old-space-size=8192", "--import", "tsx", "--input-type=module", "--eval",
`import { analyze, compile } from ${JSON.stringify(api)};
const options = JSON.parse(process.argv[3]);
const result = process.argv[1] === 'analyze'
? analyze(process.argv[2], options).coverage
: await compile(process.argv[2], options);
console.log(JSON.stringify(result, (key, value) => key === 'sourceTexts' ? undefined : value));`,
operation, entry, JSON.stringify(options),
], { cwd: root, timeout: 300_000, maxBuffer: 16 * 1024 * 1024 });
if (stderr !== "") throw new Error(stderr);
return JSON.parse(stdout);
}
export async function analyzeInChild(entry: string, options: AnalyzeOptions): Promise<CoverageInput> {
return await compilerProcess("analyze", entry, options) as CoverageInput;
}
type ExecutableResult =
| Extract<CompileExecutableResult, { ok: true }>
| Omit<Extract<CompileExecutableResult, { ok: false }>, "sourceTexts">;
export async function compileInChild(entry: string, options: CompileOptions): Promise<ExecutableResult> {
return await compilerProcess("compile", entry, options) as ExecutableResult;
}
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, test } from "vitest";
import { analyze, compile } from "@scriptc/compiler";
import { analyzeInChild, compileInChild } from "./self-hosting-compiler-process.js";
import { nativeCodegenTarget } from "../../packages/compiler/src/backend/targets.js";
import type { NativeLinkFeatures } from "../../packages/compiler/src/backend/native-link-info.js";
import type { RuntimePackManifest } from "../../packages/compiler/src/backend/runtime-pack-core.js";
@@ -20,8 +20,8 @@ const features: NativeLinkFeatures = {
http2: false, dgram: false, watch: false, foreignFfi: false, nodeTest: false, tls: false, tlsCa: false,
};
test("native toolchain configuration, staging and helper validation are static", () => {
const { coverage } = analyze(entry, { dynamic: false });
test("native toolchain configuration, staging and helper validation are static", async () => {
const coverage = await analyzeInChild(entry, { dynamic: false });
expect(coverage.preflightFailed).toBe(false);
expect(coverage.stats.statementsFailed).toBe(0);
expect(coverage.stats.statementsIsland).toBe(0);
@@ -33,7 +33,7 @@ for (const backend of ["llvm"] as const) {
const directory = mkdtempSync(join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-native-integrity-"));
const target = nativeCodegenTarget()!;
try {
const built = await compile(entry, {
const built = await compileInChild(entry, {
outDir: directory, outPath: join(directory, "toolchain" + target.outputSuffixes.exe),
backend, optimization: "dev", dynamic: false, sanitize: process.env["SCRIPTC_SAN"] === "1",
});
@@ -3,7 +3,7 @@ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, test } from "vitest";
import { analyze, compile } from "@scriptc/compiler";
import { analyzeInChild, compileInChild } from "./self-hosting-compiler-process.js";
import { NATIVE_TARGETS, nativeCodegenTarget } from "../../packages/compiler/src/backend/targets.js";
import type { NativeLinkFeatures } from "../../packages/compiler/src/backend/native-link-info.js";
import type { RuntimePackManifest } from "../../packages/compiler/src/backend/runtime-pack-core.js";
@@ -18,8 +18,8 @@ const base: NativeLinkFeatures = {
http2: false, dgram: false, watch: false, foreignFfi: false, nodeTest: false, tls: false, tlsCa: false,
};
test("runtime manifest validation and selection are entirely static", () => {
const { coverage } = analyze(entry, { dynamic: false });
test("runtime manifest validation and selection are entirely static", async () => {
const coverage = await analyzeInChild(entry, { dynamic: false });
expect(coverage.preflightFailed).toBe(false);
expect(coverage.stats.statementsTotal).toBeGreaterThan(50);
expect(coverage.stats.statementsFailed).toBe(0);
@@ -31,7 +31,7 @@ for (const backend of ["llvm"] as const) {
test(`native runtime manifest selection matches Node (${backend})`, async () => {
const directory = mkdtempSync(join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-pack-selection-"));
try {
const built = await compile(entry, {
const built = await compileInChild(entry, {
outDir: directory, outPath: join(directory, "selection" + (process.platform === "win32" ? ".exe" : "")),
backend, dynamic: false, optimization: "dev", sanitize: process.env["SCRIPTC_SAN"] === "1",
});