mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-13 02:23:10 +08:00
184 lines
6.9 KiB
JavaScript
184 lines
6.9 KiB
JavaScript
import { readFileSync } from "node:fs";
|
|
|
|
// Case-sharded harnesses run in every job without Vitest file sharding.
|
|
export const effectFiles = ["tests/harness/effect.test.ts", "tests/harness/effect4.test.ts"];
|
|
export const corpusFiles = [
|
|
"tests/harness/differential.test.ts",
|
|
"tests/harness/llvm-differential.test.ts",
|
|
"tests/harness/npm.test.ts",
|
|
"tests/harness/server.test.ts",
|
|
"tests/harness/test262.test.ts",
|
|
"tests/harness/three.test.ts",
|
|
];
|
|
const coverageFile = "tests/harness/coverage.test.ts";
|
|
const cacheFile = "packages/compiler/src/backend/native-toolchain.test.ts";
|
|
// Expensive integration suites run in their own vitest process, serially
|
|
// beside the corpus, avoiding worker contention between large compilations.
|
|
export const isolatedFiles = [
|
|
["self-hosting serialization", "tests/harness/self-hosting-serialization.test.ts"],
|
|
["self-hosting", "tests/harness/self-hosting.test.ts"],
|
|
["self-hosting LLVM emitter", "tests/harness/self-hosting-llvm-emitter.test.ts"],
|
|
["fetch", "tests/harness/fetch.test.ts"],
|
|
["npm static", "tests/harness/npm-static.test.ts"],
|
|
["surface manifest", "tests/harness/surface-manifest.test.ts"],
|
|
];
|
|
export const fileExclusions = [
|
|
...corpusFiles,
|
|
...effectFiles,
|
|
coverageFile,
|
|
cacheFile,
|
|
...isolatedFiles.map(([, file]) => file),
|
|
"tests/harness/self-hosting-native-driver.test.ts",
|
|
];
|
|
|
|
// Milliseconds each side-queue file takes on a CI runner, per flavor, from the
|
|
// logs of green main runs (scripts/ci-test-costs.mjs refreshes it).
|
|
export const testCosts = JSON.parse(
|
|
readFileSync(new URL("./ci-test-costs.json", import.meta.url), "utf8"),
|
|
);
|
|
|
|
// The shard (1-based) of each measured side-queue file and isolated suite:
|
|
// longest first to the shard with the least work so far, counting about a
|
|
// second per file for vitest to import it. An isolated suite without a
|
|
// measurement weighs as much as the median measured file. Files without a
|
|
// measurement are left to vitest's --shard, which splits them by count.
|
|
export function sideOwners(flavor, count) {
|
|
const costs = testCosts[flavor];
|
|
const known = Object.values(costs).sort((a, b) => a - b);
|
|
const median = known.length === 0 ? 0 : known[Math.floor(known.length / 2)];
|
|
const weight = (file) => (costs[file] ?? median) + 1000;
|
|
const files = [...new Set([...Object.keys(costs), ...isolatedFiles.map(([, file]) => file)])];
|
|
files.sort((a, b) => weight(b) - weight(a) || (a < b ? -1 : a > b ? 1 : 0));
|
|
const loads = new Array(count).fill(0);
|
|
const owners = new Map();
|
|
for (const file of files) {
|
|
let lightest = 0;
|
|
for (let shard = 1; shard < count; shard++)
|
|
if (loads[shard] < loads[lightest]) lightest = shard;
|
|
loads[lightest] += weight(file);
|
|
owners.set(file, lightest + 1);
|
|
}
|
|
return owners;
|
|
}
|
|
|
|
// Pull requests run the full suite on Linux. Their macOS lane keeps the
|
|
// darwin clang/libc/kqueue oracles: the Node differential corpus, plus every
|
|
// file whose assertions run only on darwin or take darwin-specific branches
|
|
// (including the Mach-O library localization arm and the arm64 runtime
|
|
// string oracle).
|
|
// Pushes to main and the nightly schedule still run the full suite on macOS.
|
|
export const darwinCorpusFiles = ["tests/harness/differential.test.ts"];
|
|
export const darwinFiles = [
|
|
"packages/cli/test/bootstrap.test.ts",
|
|
"packages/cli/test/native-link-info.test.ts",
|
|
"packages/cli/test/native-output.test.ts",
|
|
"packages/cli/test/runtime-pack.test.ts",
|
|
"packages/compiler/test/debug-variables.test.ts",
|
|
"packages/compiler/test/native-codegen-integration.test.ts",
|
|
"packages/compiler/test/source-debugging.test.ts",
|
|
"packages/runtime-darwin-arm64/test/archive.test.ts",
|
|
"packages/runtime/test/string.test.ts",
|
|
"tests/harness/ffi-callback-pool.test.ts",
|
|
"tests/harness/ffi-catalog.test.ts",
|
|
"tests/harness/ffi-frameworks.test.ts",
|
|
"tests/harness/library-callbacks.test.ts",
|
|
"tests/harness/library-dispatch.test.ts",
|
|
"tests/harness/library-multi.test.ts",
|
|
"tests/harness/native-object-example.test.ts",
|
|
"tests/harness/runtime-tree-shaking.test.ts",
|
|
"tests/harness/terminal-geometry.test.ts",
|
|
];
|
|
|
|
export function ciTestPlan({ shard, flavor, workers = 4, suite = "full" }) {
|
|
const match = /^([1-9]\d*)\/([1-9]\d*)$/.exec(shard);
|
|
if (
|
|
!match ||
|
|
!Number.isSafeInteger(Number(match[1])) ||
|
|
!Number.isSafeInteger(Number(match[2])) ||
|
|
Number(match[1]) > Number(match[2])
|
|
)
|
|
throw new Error("invalid CI shard");
|
|
if (flavor !== "plain" && flavor !== "san") throw new Error("invalid CI flavor");
|
|
if (!Number.isInteger(workers) || workers < 1) throw new Error("invalid CI worker budget");
|
|
if (suite !== "full" && suite !== "darwin") throw new Error("invalid CI suite");
|
|
const index = Number(match[1]),
|
|
count = Number(match[2]);
|
|
const sideWorkers = workers === 1 ? 0 : 1;
|
|
const env = { SCRIPTC_TEST_SHARD: shard, SCRIPTC_SAN: flavor === "san" ? "1" : "" };
|
|
if (suite === "darwin")
|
|
return {
|
|
sideWorkers,
|
|
corpus: {
|
|
name: "case-sharded differential corpus",
|
|
args: ["test", ...darwinCorpusFiles],
|
|
env: { ...env, SCRIPTC_TEST_WORKERS: String(workers - sideWorkers) },
|
|
},
|
|
side: [
|
|
{
|
|
name: "darwin contracts",
|
|
args: ["test", "--passWithNoTests", "--shard=" + shard, ...darwinFiles],
|
|
env: { ...env, SCRIPTC_TEST_WORKERS: "1" },
|
|
},
|
|
],
|
|
};
|
|
const owners = sideOwners(flavor, count);
|
|
const isolated = new Set(isolatedFiles.map(([, file]) => file));
|
|
const balanced = [...owners]
|
|
.filter(([file, owner]) => owner === index && !isolated.has(file))
|
|
.map(([file]) => file)
|
|
.sort();
|
|
const side = [
|
|
{
|
|
name: "file-sharded tests",
|
|
args: [
|
|
"test",
|
|
"--passWithNoTests",
|
|
"--shard=" + shard,
|
|
...[...new Set([...fileExclusions, ...owners.keys()])].map((file) => "--exclude=" + file),
|
|
],
|
|
env: { ...env, SCRIPTC_TEST_WORKERS: "1" },
|
|
},
|
|
];
|
|
if (balanced.length > 0)
|
|
side.push({
|
|
name: "cost-balanced tests",
|
|
args: ["test", "--passWithNoTests", ...balanced],
|
|
env: { ...env, SCRIPTC_TEST_WORKERS: "1" },
|
|
});
|
|
for (const [name, file] of isolatedFiles) {
|
|
if (owners.get(file) === index)
|
|
side.push({ name, args: ["test", file], env: { ...env, SCRIPTC_TEST_WORKERS: "1" } });
|
|
}
|
|
// Frontend coverage and cache invalidation do not consume sanitizer state.
|
|
if (flavor === "plain") {
|
|
side.push({
|
|
name: "native-cache correctness",
|
|
args: ["test", cacheFile],
|
|
env: {
|
|
...env,
|
|
SCRIPTC_TEST_WORKERS: "1",
|
|
SCRIPTC_CACHE_TEST_SHARD: shard,
|
|
SCRIPTC_TEST_STABLE_TOOLCHAIN: "0",
|
|
},
|
|
});
|
|
side.push({
|
|
name: "coverage sweep",
|
|
args: ["test", coverageFile],
|
|
env: { ...env, SCRIPTC_TEST_WORKERS: "1" },
|
|
});
|
|
}
|
|
return {
|
|
sideWorkers,
|
|
corpus: {
|
|
name: "case-sharded corpus",
|
|
args: ["test", ...corpusFiles],
|
|
env: {
|
|
...env,
|
|
SCRIPTC_TEST_WORKERS: String(workers - sideWorkers),
|
|
SCRIPTC_LLVM_TEST_MODE: "dev",
|
|
},
|
|
},
|
|
side,
|
|
};
|
|
}
|