test(ci): retire loader-ownership history seeding (#10404)

* test(ci): retire loader-ownership history seeding

Both timing tiers now publish javascriptLiteralLoaderOwnership.test.ts's
own duration (fast 23.7s, full 26.6s), so the seeding returned its input
unchanged. Remove it and its tests.

* docs(ci): describe shard weighting without retired loader seeding
This commit is contained in:
morluto authored and GitHub committed 2026-11-13 14:20:11 -05:00
1 parent 3403338ea0
commit da91bbd4eb
3 files changed
+8 -146

No files matched your search

+6 -14
View File
@@ -850,20 +850,12 @@ the sequencer's two-worker schedule balances every shard at about 404 seconds
of file time. The same replay with the stale history weights leaves the slowest
shard near 547 seconds and the fastest near 293. That spread is file-schedule
time, not runner queue, which is why each tier keeps its own history.
CI assigns discovered files by historical duration, with a deterministic
fallback weight for new files, rather than dividing only by file count.
Each file keeps its full duration on one worker. Extra workers overlap other
files in the same sequence group, and `process-boundary` runs after that
group. History published before the competing-loader split still names only
`javascriptLiteralPercentPaths.test.ts`. Until an artifact records
`javascriptLiteralLoaderOwnership.test.ts`, the sequencer assigns that
recorded duration by the pre-split case counts: 54/192 stays with the
percent-path file and 138/192 goes to the consolidated loader file. History
that names the retired Early, Middle, and Late shards sums those durations
onto the consolidated file instead, and scales a partial set as three equal
shares. That weight is the old dual-surface cost. The demoted file runs every
loader once through the shared stdio MCP session, so the first schedule
overestimates it. Every discovered file still belongs to exactly one shard;
CI assigns discovered files by historical duration rather than dividing only
by file count. A file with no recorded duration, such as a newly added or
renamed test, is weighted at the median recorded duration until a published
artifact records it. Each file keeps its full duration on one worker. Extra
workers overlap other files in the same sequence group, and `process-boundary`
runs after that group. Every discovered file still belongs to exactly one shard;
worker limits and project ordering remain unchanged. Fast qualification keeps
eight built shards without coverage. The merge job rejects successful reports
with missing, unexpected, or duplicate portable files. Coverage instrumentation
@@ -11,7 +11,6 @@ import {
parseTestDurations,
readTestDurations,
scheduleWall,
seedLoaderOwnershipDurations,
} from "../../ciTestSequencer.js";
describe("CI test history", () => {
@@ -86,72 +85,6 @@ describe("CI test history", () => {
});
});
describe("loader ownership history", () => {
const source =
"tests/acceptance/applications/javascriptLiteralPercentPaths.test.ts";
const consolidated =
"tests/acceptance/applications/javascriptLiteralLoaderOwnership.test.ts";
const parts = [
"tests/acceptance/applications/javascriptLiteralLoaderOwnershipEarly.test.ts",
"tests/acceptance/applications/javascriptLiteralLoaderOwnershipMiddle.test.ts",
"tests/acceptance/applications/javascriptLiteralLoaderOwnershipLate.test.ts",
];
it("assigns the pre-split loader share to the consolidated file", () => {
const seeded = seedLoaderOwnershipDurations(
new Map([
[source, 192_000],
["tests/other.test.ts", 10],
]),
);
expect(seeded.get(source)).toBe(54_000);
expect(seeded.get(consolidated)).toBe(138_000);
for (const path of parts) expect(seeded.has(path)).toBe(false);
expect(seeded.get("tests/other.test.ts")).toBe(10);
expect(
[...seeded.values()].reduce((sum, duration) => sum + duration, 0),
).toBe(192_000 + 10);
});
it("sums retired shard durations onto the consolidated file", () => {
const seeded = seedLoaderOwnershipDurations(
new Map([
[source, 20_000],
[parts[0]!, 30_000],
[parts[1]!, 40_000],
[parts[2]!, 50_000],
["tests/other.test.ts", 10],
]),
);
expect(seeded.get(source)).toBe(20_000);
expect(seeded.get(consolidated)).toBe(120_000);
expect(seeded.get("tests/other.test.ts")).toBe(10);
});
it("scales a partial retired shard as three equal shares", () => {
const seeded = seedLoaderOwnershipDurations(
new Map([
[source, 20_000],
[parts[0]!, 30_000],
]),
);
expect(seeded.get(source)).toBe(20_000);
expect(seeded.get(consolidated)).toBe(90_000);
});
it("keeps a published consolidated duration", () => {
const recorded = new Map<string, number>([
[source, 20_000],
[parts[0]!, 30_000],
[consolidated, 12_000],
]);
expect(seedLoaderOwnershipDurations(recorded)).toEqual(recorded);
expect(
seedLoaderOwnershipDurations(new Map([["tests/other.test.ts", 10]])),
).toEqual(new Map([["tests/other.test.ts", 10]]));
});
});
describe("duration shard allocation", () => {
it.prop([
fc.uniqueArray(fc.integer({ min: 0, max: 10_000 }), {
+2 -65
View File
@@ -60,68 +60,6 @@ export async function readTestDurations(
}
}
/**
* Case counts from the competing-loader file before it was split: 54 cases
* stayed in the percent-path file and 46 ran in each of the three dual-surface
* shards. History that still names only the original path is that combined
* cost. The shards are retired; one file now runs every loader through stdio
* MCP plus a few compiled CLI parity cases.
*/
const LOADER_HISTORY_SOURCE =
"tests/acceptance/applications/javascriptLiteralPercentPaths.test.ts";
const LOADER_HISTORY_PARTS = [
"tests/acceptance/applications/javascriptLiteralLoaderOwnershipEarly.test.ts",
"tests/acceptance/applications/javascriptLiteralLoaderOwnershipMiddle.test.ts",
"tests/acceptance/applications/javascriptLiteralLoaderOwnershipLate.test.ts",
] as const;
const LOADER_HISTORY_CONSOLIDATED =
"tests/acceptance/applications/javascriptLiteralLoaderOwnership.test.ts";
const LOADER_HISTORY_REMAINDER_CASES = 54;
const LOADER_HISTORY_PART_CASES = 46;
const LOADER_HISTORY_CASES =
LOADER_HISTORY_REMAINDER_CASES +
LOADER_HISTORY_PART_CASES * LOADER_HISTORY_PARTS.length;
/**
* Charge the consolidated loader file until its own duration is published.
* A pre-split percent-path duration keeps 54/192 and gives 138/192 to the
* consolidated file. Recorded Early/Middle/Late durations are summed instead,
* and a partial set is scaled as three equal shares. That weight is the old
* dual-surface cost, so the first schedule overestimates the demoted file.
* A published consolidated duration stands, including beside retired shards.
*/
export function seedLoaderOwnershipDurations(
durations: ReadonlyMap<string, number>,
): Map<string, number> {
if (durations.has(LOADER_HISTORY_CONSOLIDATED)) return new Map(durations);
const recordedParts = LOADER_HISTORY_PARTS.flatMap((path) => {
const duration = durations.get(path);
return duration === undefined ? [] : [duration];
});
if (recordedParts.length > 0) {
const seeded = new Map(durations);
const sum = recordedParts.reduce((total, duration) => total + duration, 0);
seeded.set(
LOADER_HISTORY_CONSOLIDATED,
(sum * LOADER_HISTORY_PARTS.length) / recordedParts.length,
);
return seeded;
}
const combined = durations.get(LOADER_HISTORY_SOURCE);
if (combined === undefined) return new Map(durations);
const seeded = new Map(durations);
const loaderCases = LOADER_HISTORY_CASES - LOADER_HISTORY_REMAINDER_CASES;
seeded.set(
LOADER_HISTORY_SOURCE,
(combined * LOADER_HISTORY_REMAINDER_CASES) / LOADER_HISTORY_CASES,
);
seeded.set(
LOADER_HISTORY_CONSOLIDATED,
(combined * loaderCases) / LOADER_HISTORY_CASES,
);
return seeded;
}
type TimedItem<T> = {
item: T;
key: string;
@@ -263,9 +201,8 @@ export class CiTestSequencer extends BaseSequencer {
const shard = this.ctx.config.shard;
if (!historyPath || !shard) return super.shard(files);
const root = this.ctx.config.root;
const recorded = await readTestDurations(historyPath, root);
if (!recorded) return super.shard(files);
const durations = seedLoaderOwnershipDurations(recorded);
const durations = await readTestDurations(historyPath, root);
if (!durations) return super.shard(files);
const weighted = files.map((item) => {
const path = relative(root, resolve(root, item.moduleId))