Skip to content

Commit 68ca4e8

Browse files
committed
fix(ci): the ceiling check folds the shard captures itself
Importing report-test-timings.mjs from a gate family stops that family inheriting the module's declared populations in the dispatch-gates derivation (the module is a gate file only through its self-test), which the derivation's promotion invariant reds on. The fold is the same sum of slice parts with the same completeness mark, and a capture in an unknown schema is named and left out. Claude-Session: https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8ab0fb8 commit 68ca4e8

1 file changed

Lines changed: 59 additions & 6 deletions

File tree

‎scripts/check-test-suite-ceilings.mjs‎

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
*
6262
* A file-sharded package (the CLI, cut into 3 slices on 3 shards whenever a
6363
* run's own list makes it the serial floor) arrives as three partial windows
64-
* on three shards. They are SUMMED across shards (mergeCaptures, which marks a
64+
* on three shards. They are SUMMED across shards (foldCaptures, which marks a
6565
* set missing a part as incomplete) and the sum is graded against the whole
6666
* package's ceiling, because that is the quantity the ceiling was measured in:
6767
* the generator records a sliced package as the per-run SUM of its slices, and
@@ -101,7 +101,6 @@ import process from 'node:process';
101101

102102
import { isEntrypoint } from './invoked-as.mjs';
103103
import { escapeWorkflowCommandMessage } from './partition-test-shards.mjs';
104-
import { mergeCaptures } from './report-test-timings.mjs';
105104

106105
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
107106
const DATASET_PATH = path.join(REPO_ROOT, 'scripts', 'test-shard-timings.json');
@@ -214,7 +213,7 @@ export function readCeilingTable(dataset) {
214213
// ── Grading ───────────────────────────────────────────────────────────────
215214

216215
/**
217-
* Grade one run. `merged` is mergeCaptures()' result: every executed package's
216+
* Grade one run. `merged` is foldCaptures()' result: every executed package's
218217
* window, a sliced package already summed across shards with `complete` saying
219218
* whether every part turned up, and every file line.
220219
*
@@ -421,6 +420,52 @@ export function renderCeilingVerdict(report, label) {
421420

422421
// ── Reading the shard captures ────────────────────────────────────────────
423422

423+
/** The capture schema `report-test-timings.mjs --capture` writes; any other is refused. */
424+
export const CAPTURE_SCHEMA = 'test-timing-capture/1';
425+
426+
/**
427+
* Fold the per-shard captures into one run: every executed package's window,
428+
* a file-sliced package SUMMED across the shards that ran its parts and marked
429+
* `complete` only when every part turned up, and every file line.
430+
*
431+
* The same fold `report-test-timings.mjs` makes for its table, kept here rather
432+
* than imported: that module is a gate file only through its own self-test, and
433+
* a gate family importing it would silently stop inheriting its declared
434+
* populations in the dispatch-gates derivation (its promotion invariant reds on
435+
* exactly that). The windows themselves are not re-read here -- each capture's
436+
* package rows are `samplesFromSummary`'s, taken on the shard.
437+
*/
438+
export function foldCaptures(captures) {
439+
const files = [];
440+
const byName = new Map();
441+
const problems = [];
442+
const shards = [];
443+
for (const capture of captures) {
444+
if (capture?.schema !== CAPTURE_SCHEMA) {
445+
problems.push(`a capture declares schema ${JSON.stringify(capture?.schema ?? null)}, not ${CAPTURE_SCHEMA}`);
446+
continue;
447+
}
448+
if (capture.shard) shards.push(capture.shard);
449+
files.push(...(capture.files ?? []));
450+
for (const p of capture.problems ?? []) problems.push(p);
451+
for (const row of capture.packages ?? []) {
452+
const acc = byName.get(row.pkg) ?? { pkg: row.pkg, seconds: 0, sliceCount: null, parts: [] };
453+
acc.seconds += row.seconds;
454+
if (row.sliceCount) {
455+
acc.sliceCount = row.sliceCount;
456+
acc.parts.push(...(row.parts ?? []));
457+
}
458+
byName.set(row.pkg, acc);
459+
}
460+
}
461+
const packages = [...byName.values()].map((row) => {
462+
const parts = [...new Set(row.parts)].sort((a, b) => a - b);
463+
return { ...row, parts, complete: row.sliceCount === null || parts.length === row.sliceCount };
464+
});
465+
return { files, packages, problems, shards: shards.sort() };
466+
}
467+
468+
424469
/** Every capture JSON under `dir`, recursively; unreadable files become problems. */
425470
export function readCaptures(dir) {
426471
const captures = [];
@@ -472,7 +517,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
472517
'provisional prints "no ceiling: provisional" and is not red': 3,
473518
'a package absent from the dataset is red; an uncapped one is not': 4,
474519
'no ceiling table yet is NOT MEASURED, never red': 5,
475-
'file-sliced packages are summed across shards': 5,
520+
'file-sliced packages are summed across shards': 6,
476521
'a ruled raise lifts a ceiling, and only a well-formed one does': 7,
477522
'a malformed ceiling table is refused, never read as a pass': 4,
478523
});
@@ -529,7 +574,7 @@ function selfTest() {
529574
...extra,
530575
});
531576
const run = (captures, ds = dataset(), raises = {}) => {
532-
const report = gradeCeilings({ merged: mergeCaptures(captures), dataset: ds, raises });
577+
const report = gradeCeilings({ merged: foldCaptures(captures), dataset: ds, raises });
533578
return { report, ...renderCeilingVerdict(report, 'Test Core') };
534579
};
535580
const status = (r, name) => r.report.rows.find((row) => row.name === name)?.status;
@@ -661,6 +706,14 @@ function selfTest() {
661706
const r = run([capture('2/6', [['cli', 10, [1, 3]]]), capture('3/6', [['a', 1]])]);
662707
if (!r.err[0].includes('cli: NOT MEASURED') || !r.err[0].includes('slices 1 of 3')) throw new Error(`slices: the partial line is wrong:\n${r.err[0]}`);
663708
});
709+
check(() => {
710+
// A capture in a shape this fold does not know contributes nothing and is
711+
// named, never read as a set of windows.
712+
const folded = foldCaptures([{ ...capture('2/6', [['cli', 600, [1, 3]]]), schema: 'test-timing-capture/2' }, capture('3/6', [['a', 1]])]);
713+
if (folded.packages.some((p) => p.pkg === 'cli') || !folded.problems.some((p) => p.includes('test-timing-capture/2'))) {
714+
throw new Error(`slices: an unknown capture schema was folded in: ${JSON.stringify(folded)}`);
715+
}
716+
});
664717

665718
battery('a ruled raise lifts a ceiling, and only a well-formed one does');
666719
check(() => {
@@ -777,7 +830,7 @@ function main() {
777830
}
778831

779832
const { captures, problems: readProblems } = readCaptures(capturesDir);
780-
const merged = mergeCaptures(captures);
833+
const merged = foldCaptures(captures);
781834
const dataset = JSON.parse(readFileSync(DATASET_PATH, 'utf8'));
782835
const report = gradeCeilings({ merged, dataset });
783836
const rendered = renderCeilingVerdict(report, label);

0 commit comments

Comments
 (0)