Files
scriptc/scripts/ci-test-plan.mjs
T
Chris Tate d0cff5e6f3 Run the npm case fixtures natively and fix what they found
The npm case fixtures lost their only native test with the native-only
compilation change. tests/harness/npm.test.ts runs every case again with the
default static npm compile and compares it with Node. A KNOWN table records
the cases the compile refuses (with the diagnostic code), the cases whose
run reaches a deferred refusal (the code in the program's output), and the
two that still diverge, which run as expected failures. The suite joins the
case-sharded CI corpus files.

Fixes from the run:

- A package imported only by subpath (`pkg/env.js` from a package with no
  "main", "exports" or index file) was refused as having no runtime entry.
  Eligibility now resolves the imported specifier when the root has none.
- diagnostics_channel: a throwing subscriber stopped publish, and a
  throwing bindStore transform stopped runStores. Node runs the remaining
  subscribers, skips that store, and reports each error as uncaught on a
  next tick; the runtime now does the same.
- A '#' import that the package's "imports" field maps to another package
  failed at run time with Node's "not defined" error, which Node does not
  raise. It is now a compile-time SC1010 refusal.
- An uncaught runtime refusal whose code is an SC code now names the code.
- Mapping a type in the optional array-element analysis could lower an
  unsupported local class and let its PoisonError escape as a compiler
  crash; the analysis now treats that element ABI as unknown, so the
  program fails with the recorded diagnostic.
- A lexical module binding initialized from an unchecked array element
  holding a function failed validation (SC9001): its widened storage was a
  union, which a TDZ guard cannot hold. Such bindings now keep checked-value
  storage and their guard.
- A dynamically reached ArrayBuffer had no constructor property.
2026-10-15 14:15:33 -05:00

140 lines
5.1 KiB
JavaScript

// 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 keep their existing owners and run serially
// beside the corpus, avoiding worker contention between large compilations.
const isolatedFiles = [
["self-hosting serialization", "tests/harness/self-hosting-serialization.test.ts", 5],
["self-hosting", "tests/harness/self-hosting.test.ts", 1],
["self-hosting LLVM emitter", "tests/harness/self-hosting-llvm-emitter.test.ts", 2],
["fetch", "tests/harness/fetch.test.ts", 3],
["npm static", "tests/harness/npm-static.test.ts", 4],
["surface manifest", "tests/harness/surface-manifest.test.ts", 5],
];
export const fileExclusions = [
...corpusFiles,
...effectFiles,
coverageFile,
cacheFile,
...isolatedFiles.map(([, file]) => file),
"tests/harness/self-hosting-native-driver.test.ts",
];
// 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 side = [
{
name: "file-sharded tests",
args: [
"test",
"--passWithNoTests",
"--shard=" + shard,
...fileExclusions.map((file) => "--exclude=" + file),
],
env: { ...env, SCRIPTC_TEST_WORKERS: "1" },
},
];
for (const [name, file, owner] of isolatedFiles) {
if (((owner - 1) % count) + 1 === 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,
};
}