Skip to content

Commit ecc8fe5

Browse files
committed
ci: rebalance sharded test lanes
1 parent 5b4fd6b commit ecc8fe5

3 files changed

Lines changed: 91 additions & 4 deletions

File tree

.github/workflows/ci-bun.yml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,32 @@ env:
1212
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
1313

1414
jobs:
15+
build-bun-artifacts:
16+
runs-on: blacksmith-16vcpu-ubuntu-2404
17+
timeout-minutes: 20
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v6
21+
with:
22+
submodules: false
23+
24+
- name: Setup Node environment
25+
uses: ./.github/actions/setup-node-env
26+
with:
27+
install-bun: "false"
28+
use-sticky-disk: "false"
29+
30+
- name: Build A2UI bundle
31+
run: pnpm canvas:a2ui:bundle
32+
33+
- name: Upload A2UI bundle artifact
34+
uses: actions/upload-artifact@v4
35+
with:
36+
name: canvas-a2ui-bundle
37+
path: src/canvas-host/a2ui/
38+
1539
bun-checks:
40+
needs: [build-bun-artifacts]
1641
runs-on: blacksmith-16vcpu-ubuntu-2404
1742
timeout-minutes: 20
1843
strategy:
@@ -37,8 +62,11 @@ jobs:
3762
install-bun: "true"
3863
use-sticky-disk: "false"
3964

40-
- name: Build A2UI bundle
41-
run: pnpm canvas:a2ui:bundle
65+
- name: Download A2UI bundle artifact
66+
uses: actions/download-artifact@v8
67+
with:
68+
name: canvas-a2ui-bundle
69+
path: src/canvas-host/a2ui/
4270

4371
- name: Run Bun test shard
4472
run: ${{ matrix.command }}

.github/workflows/ci.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,15 +257,20 @@ jobs:
257257
- runtime: node
258258
task: channels
259259
shard_index: 1
260-
shard_count: 2
260+
shard_count: 3
261261
command: pnpm test:channels
262262
- runtime: node
263263
task: contracts
264264
command: pnpm test:contracts
265265
- runtime: node
266266
task: channels
267267
shard_index: 2
268-
shard_count: 2
268+
shard_count: 3
269+
command: pnpm test:channels
270+
- runtime: node
271+
task: channels
272+
shard_index: 3
273+
shard_count: 3
269274
command: pnpm test:channels
270275
- runtime: node
271276
task: protocol

scripts/test-parallel.mjs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,53 @@ const targetedEntries = (() => {
807807
return [createTargetedEntry(owner, false, uniqueFilters)];
808808
}).flat();
809809
})();
810+
const estimateTopLevelEntryDurationMs = (entry) => {
811+
const filters = getExplicitEntryFilters(entry.args);
812+
if (filters.length === 0) {
813+
return unitTimingManifest.defaultDurationMs;
814+
}
815+
return filters.reduce((totalMs, file) => {
816+
if (isUnitConfigTestFile(file)) {
817+
return totalMs + estimateUnitDurationMs(file);
818+
}
819+
if (channelTestPrefixes.some((prefix) => file.startsWith(prefix))) {
820+
return totalMs + 3_000;
821+
}
822+
if (file.startsWith("extensions/")) {
823+
return totalMs + 2_000;
824+
}
825+
return totalMs + 1_000;
826+
}, 0);
827+
};
828+
const topLevelSingleShardAssignments = (() => {
829+
if (shardIndexOverride === null || shardCount <= 1) {
830+
return new Map();
831+
}
832+
833+
// Single-file and other non-shardable explicit lanes would otherwise run on
834+
// every shard. Assign them to one top-level shard instead.
835+
const entriesNeedingAssignment = runs.filter((entry) => {
836+
const explicitFilterCount = countExplicitEntryFilters(entry.args);
837+
if (explicitFilterCount === null) {
838+
return false;
839+
}
840+
const effectiveShardCount = Math.min(shardCount, Math.max(1, explicitFilterCount - 1));
841+
return effectiveShardCount <= 1;
842+
});
843+
844+
const assignmentMap = new Map();
845+
const buckets = packFilesByDuration(
846+
entriesNeedingAssignment,
847+
shardCount,
848+
estimateTopLevelEntryDurationMs,
849+
);
850+
for (const [bucketIndex, bucket] of buckets.entries()) {
851+
for (const entry of bucket) {
852+
assignmentMap.set(entry, bucketIndex + 1);
853+
}
854+
}
855+
return assignmentMap;
856+
})();
810857
// Node 25 local runs still show cross-process worker shutdown contention even
811858
// after moving the known heavy files into singleton lanes.
812859
const topLevelParallelEnabled =
@@ -1258,6 +1305,13 @@ const runOnce = (entry, extraArgs = []) =>
12581305

12591306
const run = async (entry, extraArgs = []) => {
12601307
const explicitFilterCount = countExplicitEntryFilters(entry.args);
1308+
const topLevelAssignedShard = topLevelSingleShardAssignments.get(entry);
1309+
if (topLevelAssignedShard !== undefined) {
1310+
if (shardIndexOverride !== null && shardIndexOverride !== topLevelAssignedShard) {
1311+
return 0;
1312+
}
1313+
return runOnce(entry, extraArgs);
1314+
}
12611315
// Vitest requires the shard count to stay strictly below the number of
12621316
// resolved test files, so explicit-filter lanes need a `< fileCount` cap.
12631317
const effectiveShardCount =

0 commit comments

Comments
 (0)