ci: add fast-feedback PR test step

When a PR modifies test files, add a separate lightweight CI step per
platform that runs only those tests with no parallelism. This step runs
alongside the full test suite but finishes much faster, giving immediate
pass/fail feedback on the tests you actually changed instead of waiting
for all 20 shards to grind through the entire suite.

- New getPrTestStep() generates a no-parallelism step with 10min timeout
- Detects .test./.spec. files plus node compat and cluster test patterns
- Only added for PR builds (not main branch)
- Full test suite still runs unchanged in parallel
This commit is contained in:
Alistair Smith
2026-02-13 14:26:09 -08:00
parent 3debd0a2d2
commit 2fa2fce79c

View File

@@ -729,6 +729,48 @@ function getTestBunStep(platform, options, testOptions = {}) {
};
}
/**
* Returns a lightweight test step that runs only the PR-modified test files.
* Runs with no parallelism so it finishes fast and gives early feedback.
*
* @param {Platform} platform
* @param {PipelineOptions} options
* @param {{ buildId?: string, prTestFiles: string[] }} testOptions
* @returns {Step}
*/
function getPrTestStep(platform, options, testOptions) {
const { os } = platform;
const { buildId, prTestFiles } = testOptions;
const args = [`--step=${getTargetKey(platform)}-build-bun`];
if (buildId) {
args.push(`--build-id=${buildId}`);
}
args.push(...prTestFiles.map(f => `--include=${f}`));
const depends = [];
if (!buildId) {
depends.push(`${getTargetKey(platform)}-build-bun`);
}
return {
key: `${getPlatformKey(platform)}-test-pr`,
label: `:dart: ${getPlatformLabel(platform)} - PR tests`,
depends_on: depends,
agents: getTestAgent(platform, options),
retry: getRetry(),
cancel_on_build_failing: isMergeQueue(),
timeout_in_minutes: 10,
env: {
ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=0",
},
command:
os === "windows"
? `node .\\scripts\\runner.node.mjs ${args.join(" ")}`
: `./scripts/runner.node.mjs ${args.join(" ")}`,
};
}
/**
* @param {Platform} platform
* @param {PipelineOptions} options
@@ -1275,14 +1317,40 @@ async function getPipeline(options = {}) {
}
if (!isMainBranch()) {
const { skipTests, forceTests, testFiles } = options;
const { skipTests, forceTests, testFiles, changedFiles = [] } = options;
if (!skipTests || forceTests) {
// Detect test files added/modified in this PR for the fast-feedback step.
const isTestFile = f => {
if (!f.startsWith("test/")) return false;
if (/\.(?:test|spec)\./.test(f)) return true;
// Node.js compat tests and cluster tests don't use .test. naming.
const u = f.replaceAll("\\", "/");
return (
u.includes("js/node/test/parallel/") ||
u.includes("js/node/test/sequential/") ||
u.includes("js/bun/test/parallel/") ||
/js\/node\/cluster\/test-.*\.ts$/.test(u)
);
};
const prTestFiles = changedFiles.filter(isTestFile).map(f => f.slice("test/".length));
steps.push(
...testPlatforms.map(target => ({
key: getTargetKey(target),
group: getTargetLabel(target),
steps: [getTestBunStep(target, options, { testFiles, buildId })],
})),
...testPlatforms.map(target => {
const groupSteps = [];
// Fast-feedback step: runs only PR-modified tests with no parallelism.
if (prTestFiles.length > 0) {
groupSteps.push(getPrTestStep(target, options, { buildId, prTestFiles }));
}
groupSteps.push(getTestBunStep(target, options, { testFiles, buildId }));
return {
key: getTargetKey(target),
group: getTargetLabel(target),
steps: groupSteps,
};
}),
);
}
}