diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e949339fc..2b690b100 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -346,6 +346,37 @@ jobs: - run: tar -xzf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz" - run: pnpm test tests/harness/effect.test.ts tests/harness/effect4.test.ts + # The sanitized three.js nodes build alone: beside the san corpus's other + # workers it took 860 to more than 1200 seconds of its shard + # (SCRIPTC_THREE_SANITIZED_NODES_JOB in scripts/ci-test-plan.mjs). It keeps + # the corpus's LeakSanitizer settings. + three_nodes_san: + needs: [validation_scope, lanes, native_linux] + if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }} + name: test (Linux, san, three.js nodes) + runs-on: ubuntu-24.04 + timeout-minutes: 40 + env: + SCRIPTC_SAN: "1" + ASAN_OPTIONS: detect_leaks=1 + LSAN_OPTIONS: suppressions=${{ github.workspace }}/tests/harness/lsan.supp:print_suppressions=0 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 11.1.3 + - uses: actions/setup-node@v4 + with: + node-version-file: .node-version + cache: pnpm + - run: pnpm install --frozen-lockfile + - uses: actions/download-artifact@v4 + with: + name: native-linux-x64-gnu + path: ${{ runner.temp }} + - run: tar -xzf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz" + - run: pnpm test tests/harness/three.test.ts -t "'nodes' runs statically through LLVM on 'native'" + # Build each instrumented seed once. Downstream contracts retain the same # compiler and source artifacts without repeating seed construction. bootstrap_seed: @@ -950,6 +981,7 @@ jobs: tests_macos, native_linux, effects, + three_nodes_san, bootstrap_seed, bootstrap, linux_host_clang, @@ -982,6 +1014,7 @@ jobs: lane tests_macos "${{ needs.tests_macos.result }}" "${{ needs.lanes.outputs.macos }}" lane native_linux "${{ needs.native_linux.result }}" "${{ needs.lanes.outputs.linux }}" lane effects "${{ needs.effects.result }}" "${{ needs.lanes.outputs.effects }}" + lane three_nodes_san "${{ needs.three_nodes_san.result }}" "${{ needs.lanes.outputs.linux }}" lane bootstrap_seed "${{ needs.bootstrap_seed.result }}" "${{ needs.lanes.outputs.bootstrap }}" lane bootstrap "${{ needs.bootstrap.result }}" "${{ needs.lanes.outputs.bootstrap }}" lane linux_host_clang "${{ needs.linux_host_clang.result }}" "${{ needs.lanes.outputs.linux }}" diff --git a/scripts/ci-test-plan.mjs b/scripts/ci-test-plan.mjs index 184f04d1c..88a77e32e 100644 --- a/scripts/ci-test-plan.mjs +++ b/scripts/ci-test-plan.mjs @@ -134,6 +134,15 @@ export const leakSuppressions = fileURLToPath( new URL("../tests/harness/lsan.supp", import.meta.url), ); +// The sanitized three.js nodes build runs in its own Linux job +// (three_nodes_san in .github/workflows/ci.yml). Beside the corpus's other +// workers it took 860 to more than 1200 seconds, past its test timeout. +export const sanitizedNodesJobEnv = { SCRIPTC_THREE_SANITIZED_NODES_JOB: "1" }; + +function corpusThreeEnv(flavor, platform) { + return flavor === "san" && platform === "linux" ? sanitizedNodesJobEnv : {}; +} + function corpusLeakEnv(flavor, platform) { if (flavor !== "san" || platform !== "linux") return {}; return { @@ -246,6 +255,7 @@ export function ciTestPlan({ env: { ...env, ...corpusLeakEnv(flavor, platform), + ...corpusThreeEnv(flavor, platform), SCRIPTC_TEST_WORKERS: String(workers - sideWorkers), SCRIPTC_LLVM_TEST_MODE: "dev", }, diff --git a/tests/harness/ci-test-plan.test.ts b/tests/harness/ci-test-plan.test.ts index 29268c841..e67e5582b 100644 --- a/tests/harness/ci-test-plan.test.ts +++ b/tests/harness/ci-test-plan.test.ts @@ -12,6 +12,7 @@ import { isolatedFiles, leakSuppressions, memoryHeavyFiles, + sanitizedNodesJobEnv, sideOwners, testCosts, } from "../../scripts/ci-test-plan.mjs"; @@ -90,6 +91,42 @@ test("LeakSanitizer runs over the sanitized corpus on Linux only", () => { for (const task of plan("san", "linux").side) expect(task.env.ASAN_OPTIONS).toBeUndefined(); }); +test("the sanitized three.js nodes build leaves the Linux corpus for its own job", () => { + const plan = (flavor: "plain" | "san", platform: string) => + ciTestPlan({ shard: "1/20", flavor, workers: 4, platform }); + const [name, value] = Object.entries(sanitizedNodesJobEnv)[0]!; + expect(plan("san", "linux").corpus.env[name]).toBe(value); + for (const env of [plan("plain", "linux").corpus.env, plan("san", "darwin").corpus.env]) + expect(env[name]).toBeUndefined(); + const three = readFileSync(new URL("./three.test.ts", import.meta.url), "utf8"); + expect(three).toContain(`process.env["${name}"] === "${value}"`); + const { jobs } = load( + readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"), + ) as { + jobs: Record< + string, + { needs: string[]; env: Record; steps: { run?: string }[] } + >; + }; + const job = jobs["three_nodes_san"]!; + expect(job.needs).toContain("native_linux"); + expect(jobs["test"]!.needs).toContain("three_nodes_san"); + expect(job.env[name]).toBeUndefined(); + expect(job.env["SCRIPTC_SAN"]).toBe("1"); + // The same LeakSanitizer settings as the corpus it left. + const corpus = plan("san", "linux").corpus.env; + expect(job.env["ASAN_OPTIONS"]).toBe(corpus.ASAN_OPTIONS); + expect(job.env["LSAN_OPTIONS"]).toBe( + corpus.LSAN_OPTIONS!.replace( + leakSuppressions, + "${{ github.workspace }}/tests/harness/lsan.supp", + ), + ); + expect(job.steps.at(-1)!.run).toBe( + `pnpm test tests/harness/three.test.ts -t "'nodes' runs statically through LLVM on 'native'"`, + ); +}); + test("the side queue balances measured files across the twenty Linux shards", () => { for (const flavor of ["plain", "san"] as const) { const costs = testCosts[flavor] as Record; diff --git a/tests/harness/three.test.ts b/tests/harness/three.test.ts index 27fb86d1f..e55242149 100644 --- a/tests/harness/three.test.ts +++ b/tests/harness/three.test.ts @@ -68,6 +68,15 @@ test.for(cases)( async ({ fixture, target }, context) => { const entry = join(import.meta.dirname, `../fixtures/three/${fixture}.mjs`); if (target === "wasm32-wasi" && !hasZig) context.skip(); + // CI's Linux sanitized lane builds this one in its own job + // (scripts/ci-test-plan.mjs). + if ( + fixture === "nodes" && + target === "native" && + process.env["SCRIPTC_SAN"] === "1" && + process.env["SCRIPTC_THREE_SANITIZED_NODES_JOB"] === "1" + ) + context.skip(); const dir = await mkdtemp("/tmp/scriptc-three-"); const previousTarget = process.env["SCRIPTC_TARGET"]; const previousCc = process.env["SCRIPTC_CC"];