From 8b99f4f3c30c2dd0ad8d12dcbe50983c1cc41161 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 8 Jul 2026 22:49:25 -0400 Subject: [PATCH] fix(#2088): weight install-heavy test files so they spread across chunks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The targeted CI lane runs changed files UNSHARDED; #2088 touched 13 install-heavy test files that all landed in one chunk, blowing the 600s per-chunk backstop on the slow Windows runner (pure slowness, not a leak — per run-tests.cjs's own comment). Weight install*/codex-* files (~10x a unit file) toward the per-chunk budget so they spread across chunks instead of clustering; light-file chunking is unchanged (weight 1). Adds harness regression tests (heavy split vs light control). Co-Authored-By: Claude Opus 4.8 --- scripts/run-tests.cjs | 22 +++++++++++++++++++- tests/run-tests-harness.test.cjs | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/scripts/run-tests.cjs b/scripts/run-tests.cjs index e52c7fe92..e03ab8a98 100644 --- a/scripts/run-tests.cjs +++ b/scripts/run-tests.cjs @@ -577,6 +577,23 @@ function main() { const MAX_FILES_PER_CHUNK = process.env.RUN_TESTS_MAX_FILES_PER_CHUNK ? Number(process.env.RUN_TESTS_MAX_FILES_PER_CHUNK) : 60; + // #2088: file COUNT is a poor proxy for a chunk's wall-clock — install-heavy + // files (real installs; install-minimal-hooks.test.cjs alone runs ~250 cases + // doing dozens of installs) are ~10× a unit file. When several land in the SAME + // chunk — e.g. a PR touching the whole install surface, whose *targeted* lane is + // unsharded (13 install-heavy files → one chunk) — that chunk blows the 600s + // backstop while unit-only chunks finish in seconds. WEIGHT install-heavy files + // so they fill a chunk's budget faster and therefore SPREAD across chunks + // instead of clustering. Light files keep weight 1, so pure-unit chunking (and + // its harness tests) is byte-for-byte unchanged. `MAX_FILES_PER_CHUNK` is now a + // per-chunk WEIGHT budget (backwards-compatible: it equals the file count when + // every file is light). Tune via RUN_TESTS_HEAVY_FILE_WEIGHT; classify via the + // basename prefix (install*/installer*/codex-* are the real install-heavy suites). + const HEAVY_TEST_RE = /^(?:install|codex-)/; + const HEAVY_FILE_WEIGHT = process.env.RUN_TESTS_HEAVY_FILE_WEIGHT + ? Number(process.env.RUN_TESTS_HEAVY_FILE_WEIGHT) + : 12; + const fileWeight = (f) => (HEAVY_TEST_RE.test(basename(f)) ? HEAVY_FILE_WEIGHT : 1); // node:test does not exit until the event loop drains. A unit test that leaks // an open handle (un-terminated Worker, un-killed child_process, ref'd timer) @@ -595,18 +612,21 @@ function main() { const chunks = []; let current = []; let currentLen = FIXED_OVERHEAD; + let currentWeight = 0; for (const file of selected) { const add = file.length + 1; // +1 for the inter-arg separator if ( current.length > 0 && - (currentLen + add > MAX_CMDLINE_CHARS || current.length >= MAX_FILES_PER_CHUNK) + (currentLen + add > MAX_CMDLINE_CHARS || currentWeight >= MAX_FILES_PER_CHUNK) ) { chunks.push(current); current = []; currentLen = FIXED_OVERHEAD; + currentWeight = 0; } current.push(file); currentLen += add; + currentWeight += fileWeight(file); // heavy install files count for more } if (current.length > 0) chunks.push(current); diff --git a/tests/run-tests-harness.test.cjs b/tests/run-tests-harness.test.cjs index 6a1310676..7310ff42a 100644 --- a/tests/run-tests-harness.test.cjs +++ b/tests/run-tests-harness.test.cjs @@ -389,6 +389,41 @@ test('ambient GSD workstream vars are stripped by the runner', () => { `expected final file-count chunking marker in stderr; STDERR:\n${r.stderr}`, ); }); + + // #2088: install-heavy files (real installs) are weighted so they never all + // land in one chunk — otherwise the unsharded targeted lane packs the whole + // install surface into a single chunk that blows the 600s per-chunk backstop + // on the slow Windows runner. + test('install-heavy files carry more weight so they SPREAD across chunks (#2088)', () => { + // 4 install-* files at weight 3 = 12 against a 6-weight cap → 2 chunks + // (2 heavy each). The whole install load is never in a single chunk. + const heavy = Array.from({ length: 4 }, (_, i) => `install-weighttest-${i}.test.cjs`); + seed(tmpDir, heavy); + const rh = runHarness(tmpDir, [], { + RUN_TESTS_MAX_CMDLINE_CHARS: '100000', + RUN_TESTS_MAX_FILES_PER_CHUNK: '6', + RUN_TESTS_HEAVY_FILE_WEIGHT: '3', + }); + assert.strictEqual(rh.status, 0, `heavy: expected zero exit; STDERR:\n${rh.stderr}`); + assert.match(rh.stderr, /run-tests: chunk 1\/2 — 2 files/, `install-heavy files must split into 2 chunks; STDERR:\n${rh.stderr}`); + assert.match(rh.stderr, /run-tests: chunk 2\/2 — 2 files/, `STDERR:\n${rh.stderr}`); + }); + + test('light files of the same count stay in ONE chunk — split is weight-driven, not count-driven (#2088)', () => { + // Same file COUNT (4) but LIGHT (weight 1 each): 4 < 6 cap → a single + // chunk. Proves the split above is driven by install-heavy WEIGHT. + const light = Array.from({ length: 4 }, (_, i) => `lightweighttest-${i}.test.cjs`); + seed(tmpDir, light); + const rl = runHarness(tmpDir, [], { + RUN_TESTS_MAX_CMDLINE_CHARS: '100000', + RUN_TESTS_MAX_FILES_PER_CHUNK: '6', + RUN_TESTS_HEAVY_FILE_WEIGHT: '3', + }); + assert.strictEqual(rl.status, 0, `light: expected zero exit; STDERR:\n${rl.stderr}`); + // A single chunk emits NO `chunk N/M` split marker (it only prints when + // chunks.length > 1), so its absence proves the 4 light files stayed together. + assert.doesNotMatch(rl.stderr, /run-tests: chunk \d+\/\d+ — /, `4 light files must stay in one chunk (no split marker); STDERR:\n${rl.stderr}`); + }); }); describe('shard partitioning CLI (#1212)', () => {