Build the sanitized three.js nodes fixture in its own CI job

On the san 1/20 shard the sanitized nodes build shares the runner with the
corpus's other two workers and the side queue. It took 944 s and 860 s on
#2194's runs and passed 1200 s on main's 7fa88083, failing the test timeout.

The Linux san corpus now skips that one case
(SCRIPTC_THREE_SANITIZED_NODES_JOB), and a three_nodes_san job runs it alone
with the corpus's LeakSanitizer settings. The required test check waits for
the new job. Every other three.js case, the plain nodes build and the macOS
suites are unchanged.
This commit is contained in:
Chris Tate
2026-10-25 06:49:46 -05:00
parent c8b40bd73e
commit c2e6c962e8
4 changed files with 89 additions and 0 deletions
+33
View File
@@ -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 }}"
+10
View File
@@ -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",
},
+37
View File
@@ -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<string, string>; 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<string, number>;
+9
View File
@@ -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"];