ci: rebalance sharded test lanes

This commit is contained in:
Peter Steinberger
2026-03-24 01:43:46 +00:00
parent 5b4fd6bf31
commit ecc8fe5dc2
3 changed files with 91 additions and 4 deletions

View File

@@ -12,7 +12,32 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
build-bun-artifacts:
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Build A2UI bundle
run: pnpm canvas:a2ui:bundle
- name: Upload A2UI bundle artifact
uses: actions/upload-artifact@v4
with:
name: canvas-a2ui-bundle
path: src/canvas-host/a2ui/
bun-checks:
needs: [build-bun-artifacts]
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 20
strategy:
@@ -37,8 +62,11 @@ jobs:
install-bun: "true"
use-sticky-disk: "false"
- name: Build A2UI bundle
run: pnpm canvas:a2ui:bundle
- name: Download A2UI bundle artifact
uses: actions/download-artifact@v8
with:
name: canvas-a2ui-bundle
path: src/canvas-host/a2ui/
- name: Run Bun test shard
run: ${{ matrix.command }}

View File

@@ -257,7 +257,7 @@ jobs:
- runtime: node
task: channels
shard_index: 1
shard_count: 2
shard_count: 3
command: pnpm test:channels
- runtime: node
task: contracts
@@ -265,7 +265,12 @@ jobs:
- runtime: node
task: channels
shard_index: 2
shard_count: 2
shard_count: 3
command: pnpm test:channels
- runtime: node
task: channels
shard_index: 3
shard_count: 3
command: pnpm test:channels
- runtime: node
task: protocol

View File

@@ -807,6 +807,53 @@ const targetedEntries = (() => {
return [createTargetedEntry(owner, false, uniqueFilters)];
}).flat();
})();
const estimateTopLevelEntryDurationMs = (entry) => {
const filters = getExplicitEntryFilters(entry.args);
if (filters.length === 0) {
return unitTimingManifest.defaultDurationMs;
}
return filters.reduce((totalMs, file) => {
if (isUnitConfigTestFile(file)) {
return totalMs + estimateUnitDurationMs(file);
}
if (channelTestPrefixes.some((prefix) => file.startsWith(prefix))) {
return totalMs + 3_000;
}
if (file.startsWith("extensions/")) {
return totalMs + 2_000;
}
return totalMs + 1_000;
}, 0);
};
const topLevelSingleShardAssignments = (() => {
if (shardIndexOverride === null || shardCount <= 1) {
return new Map();
}
// Single-file and other non-shardable explicit lanes would otherwise run on
// every shard. Assign them to one top-level shard instead.
const entriesNeedingAssignment = runs.filter((entry) => {
const explicitFilterCount = countExplicitEntryFilters(entry.args);
if (explicitFilterCount === null) {
return false;
}
const effectiveShardCount = Math.min(shardCount, Math.max(1, explicitFilterCount - 1));
return effectiveShardCount <= 1;
});
const assignmentMap = new Map();
const buckets = packFilesByDuration(
entriesNeedingAssignment,
shardCount,
estimateTopLevelEntryDurationMs,
);
for (const [bucketIndex, bucket] of buckets.entries()) {
for (const entry of bucket) {
assignmentMap.set(entry, bucketIndex + 1);
}
}
return assignmentMap;
})();
// Node 25 local runs still show cross-process worker shutdown contention even
// after moving the known heavy files into singleton lanes.
const topLevelParallelEnabled =
@@ -1258,6 +1305,13 @@ const runOnce = (entry, extraArgs = []) =>
const run = async (entry, extraArgs = []) => {
const explicitFilterCount = countExplicitEntryFilters(entry.args);
const topLevelAssignedShard = topLevelSingleShardAssignments.get(entry);
if (topLevelAssignedShard !== undefined) {
if (shardIndexOverride !== null && shardIndexOverride !== topLevelAssignedShard) {
return 0;
}
return runOnce(entry, extraArgs);
}
// Vitest requires the shard count to stay strictly below the number of
// resolved test files, so explicit-filter lanes need a `< fileCount` cap.
const effectiveShardCount =