Keep Free Tests planning under its job cap when the duration seed goes stale (GST-2193) (#4245)

* Keep Free Tests planning under the job cap when the duration seed is stale (GST-2193)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Refresh the free-suite duration seed from run 42691699570 (GST-2193)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Admit the eight test files the fresh seed records over 60 seconds as annotated exceptions (GST-2193)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-09 21:46:36 +00:00
1 parent dcebc44fa7
commit 72cbb49ddf
8 files changed
+1813 -1453

No files matched your search

File diff suppressed because it is too large. Load diff
+9 -1
View File
@@ -4,6 +4,14 @@
{"file": "test/shared-libs-stage-actor.test.ts", "reason": "re-runs the paid shared-libs runner body against git fixture repos per test; owned by the paid-harness value-bar review"},
{"file": "test/setup-install-registry.test.ts", "reason": "82s in the 2026-10-15 recording (58s on 2026-10-04): 11 cases each run the real ./setup, gstack-relink, gstack-config and gstack-uninstall against throwaway checkouts", "todo": "split by describe block so the packer can balance it, then rerun bun run test:ubicloud --record-durations (TODOS.md, fix wave 2 follow-ups)"},
{"file": "test/brain-sync.test.ts", "reason": "89s in the 2026-10-19 weekly recording (58s on 2026-10-15): #3254 added the case-collision suite, and every case drains a real gstack-brain-sync against a local bare remote", "todo": "split by describe block so the packer can balance it, then rerun bun run test:ubicloud --record-durations (TODOS.md, fix wave 2 follow-ups)"},
{"file": "test/setup-codex-scope.test.ts", "reason": "64s in the 2026-10-19 weekly recording (59s on 2026-10-15): each case runs the real ./setup twice against a Codex install fixture", "todo": "split by describe block so the packer can balance it, then rerun bun run test:ubicloud --record-durations (TODOS.md, fix wave 2 follow-ups)"}
{"file": "test/setup-codex-scope.test.ts", "reason": "64s in the 2026-10-19 weekly recording (59s on 2026-10-15): each case runs the real ./setup twice against a Codex install fixture", "todo": "split by describe block so the packer can balance it, then rerun bun run test:ubicloud --record-durations (TODOS.md, fix wave 2 follow-ups)"},
{"file": "test/review-pass.test.ts", "reason": "221s in the 2026-11-09 recording (run 42691699570; 24s on 2026-10-31); admitted so the refreshed seed can land", "todo": "split or speed up; post-11-21 slow-test pass"},
{"file": "test/review-exclude.test.ts", "reason": "126s in the 2026-11-09 recording (run 42691699570; unseeded before); admitted so the refreshed seed can land", "todo": "split or speed up; post-11-21 slow-test pass"},
{"file": "test/ubicloud-runner-recovery.test.ts", "reason": "79s in the 2026-11-09 recording (run 42691699570; unseeded before); admitted so the refreshed seed can land", "todo": "split or speed up; post-11-21 slow-test pass"},
{"file": "test/ship-rereview-triggers.test.ts", "reason": "78s in the 2026-11-09 recording (run 42691699570; unseeded before); admitted so the refreshed seed can land", "todo": "split or speed up; post-11-21 slow-test pass"},
{"file": "crew/test/ship-queue-train-generic.test.ts", "reason": "77s in the 2026-11-09 recording (run 42691699570; unseeded before); admitted so the refreshed seed can land", "todo": "split or speed up; post-11-21 slow-test pass"},
{"file": "test/ship-finish-stack-parent.test.ts", "reason": "68s in the 2026-11-09 recording (run 42691699570; unseeded before); admitted so the refreshed seed can land", "todo": "split or speed up; post-11-21 slow-test pass"},
{"file": "test/ship-ci-carry.test.ts", "reason": "67s in the 2026-11-09 recording (run 42691699570; unseeded before); admitted so the refreshed seed can land", "todo": "split or speed up; post-11-21 slow-test pass"},
{"file": "test/ship-queue-land.test.ts", "reason": "60s in the 2026-11-09 recording (run 42691699570; 59s on 2026-10-31); admitted so the refreshed seed can land", "todo": "split or speed up; post-11-21 slow-test pass"}
]
}
+22 -4
View File
@@ -460,16 +460,34 @@ const runsSerialUnderJobs = (file: string) => file in TREE_MUTATING || file in S
const planDigest = (plan: Omit<FreeCiPlan, 'id'>): string =>
createHash('sha256').update(JSON.stringify(plan)).digest('hex');
/** One immutable plan is shared by isolated CI machines; never repack per job. */
export function createFreeCiPlan(files: string[], count: number, durations: Record<string, number>, revision: string, shardJobs = 1): FreeCiPlan {
/**
* The largest packed prediction whose CI job still fits MAX_CI_JOB_TIMEOUT_MINUTES
* (inverse of ciShardJobTimeoutMinutes's prediction term).
*/
export function maxPredictedMsUnderJobCap(setupSeconds: number): number {
return Math.floor(((MAX_CI_JOB_TIMEOUT_MINUTES * 60 - setupSeconds) * 1000 - wallTimeoutForShard(FLAKY_RETRY_CAP)) / 3);
}
/**
* One immutable plan is shared by isolated CI machines; never repack per job.
* With setupSeconds (--ci-plan --job-setup-seconds), a shard whose 99th-percentile
* estimates for unseeded files would push its job past the cap has its prediction
* cut back to the cap (GST-2193): a stale seed lengthens the job up to the cap and
* never fails the plan. Recorded durations are never cut, so a shard whose seeded
* files alone pass the cap still fails it.
*/
export function createFreeCiPlan(files: string[], count: number, durations: Record<string, number>, revision: string, shardJobs = 1, setupSeconds?: number): FreeCiPlan {
const readers = files.filter(file => !(file in TREE_MUTATING));
const exclusive = files.filter(file => file in TREE_MUTATING).sort();
const packed = packShardsByDuration(readers, count, durations);
const planned = packed.shards.map((files, index) => ({ files, predictedMs: packed.predictedMs[index] }));
if (exclusive.length) planned.push({ files: exclusive, predictedMs: exclusive.reduce((ms, file) => ms + (durations[file] ?? 0), 0) });
const limitMs = setupSeconds === undefined ? Infinity : maxPredictedMsUnderJobCap(setupSeconds);
const seededMs = (shardFiles: string[]) => shardFiles.reduce((ms, file) => ms + (durations[normalizeRelativePath(file)] ?? 0), 0);
const shards = planned.map(({ files, predictedMs }, index) => {
const jobs = plannedShardJobs(files, shardJobs, runsSerialUnderJobs);
return { shard: index + 1, files, predictedMs, ...(jobs ? { jobs } : {}) };
const capped = predictedMs > limitMs && seededMs(files) <= limitMs ? limitMs : predictedMs;
return { shard: index + 1, files, predictedMs: capped, ...(jobs ? { jobs } : {}) };
});
const body = { version: 1 as const, revision, shards };
return { ...body, id: planDigest(body) };
@@ -2036,7 +2054,7 @@ async function main(): Promise<number> {
appendStepSummary(line);
}
warnUnseededFreeFiles(ciFiles, durations);
const plan = createFreeCiPlan(ciFiles, options.shardCount, durations, revision, options.shardJobs);
const plan = createFreeCiPlan(ciFiles, options.shardCount, durations, revision, options.shardJobs, options.jobSetupSeconds);
validateFreeCiPlan(plan, ciFiles, revision);
const matrix = ciPlanMatrix(plan, options.jobSetupSeconds);
if ('include' in matrix) {