From 56c5ccdbc4dd57a0d3a97b9d0461947812cc1fd9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 29 Sep 2026 13:25:48 -0500 Subject: [PATCH] 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. --- .github/workflows/ci.yml | 2 - .../harness/self-hosting-compiler-process.ts | 37 +++++++++++++++++++ .../self-hosting-native-toolchain.test.ts | 8 ++-- .../harness/self-hosting-runtime-pack.test.ts | 8 ++-- 4 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 tests/harness/self-hosting-compiler-process.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0080575b..520eb6e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/tests/harness/self-hosting-compiler-process.ts b/tests/harness/self-hosting-compiler-process.ts new file mode 100644 index 00000000..d0665c4d --- /dev/null +++ b/tests/harness/self-hosting-compiler-process.ts @@ -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 { + 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 { + return await compilerProcess("analyze", entry, options) as CoverageInput; +} + +type ExecutableResult = + | Extract + | Omit, "sourceTexts">; + +export async function compileInChild(entry: string, options: CompileOptions): Promise { + return await compilerProcess("compile", entry, options) as ExecutableResult; +} diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index d877dc28..e73ba2c1 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -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", }); diff --git a/tests/harness/self-hosting-runtime-pack.test.ts b/tests/harness/self-hosting-runtime-pack.test.ts index ffd66691..4ac14e0d 100644 --- a/tests/harness/self-hosting-runtime-pack.test.ts +++ b/tests/harness/self-hosting-runtime-pack.test.ts @@ -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", });