fix(ci): give nightly mutation shards headroom and report cancellation accurately

Split the full registry into ten shards without increasing the one-hour
limit. Preserve summary and step outcome separately so cancellation after
194/194 remains red without claiming the summary was missing.

Issue: #795
User-Visible: no
This commit is contained in:
Matysh
2026-10-05 11:04:29 +03:00
parent 865312e0b0
commit aef02229c6
5 changed files with 88 additions and 37 deletions
+11 -8
View File
@@ -442,23 +442,26 @@ test('#472 AC2 / #549: каждый шард сохраняет лог и identi
assert.match(mutationWorkflow, /--write-evidence=.*evidence\.json/);
});
// #604. Шард 2/4 снят по timeout-minutes: реестр вырос до 810 мутантов, ~203 на
// шард, 55–61 мин при потолке 60. Шардов шесть, и делитель повторяется в
// четырёх местах workflow — тест не даёт им разойтись при следующем делении.
test('#604: делитель шардов ночного прогона один во всех местах workflow, шардов шесть', () => {
// #795: на шести шардах ночь снова дошла до часового потолка. Контракт
// проверяет не только одинаковые делители, но и полный охват реальной раскладкой.
test('#795: десять ночных шардов согласованы с runner/evidence и покрывают реестр ровно один раз', () => {
const matrix = /shard: \[([0-9, ]+)\]/.exec(mutationWorkflow);
assert.ok(matrix, 'matrix.shard объявлена списком');
const shards = matrix[1].split(',').map((n) => Number(n.trim()));
assert.deepEqual(shards, [1, 2, 3, 4, 5, 6]);
assert.deepEqual(shards, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
const n = shards.length;
assert.match(mutationWorkflow, new RegExp(`\\(шард \\$\\{\\{ matrix\\.shard \\}\\} из ${n}\\)`), 'имя job');
assert.match(mutationWorkflow, new RegExp(`--shard=\\$\\{\\{ matrix\\.shard \\}\\}/${n} `), 'делитель раннера');
// Только строки команд: комментарий про `--shards=6` — не место вызова.
// Только строки команд, не комментарии.
const shardsArgs = mutationWorkflow.split('\n').filter((line) => !/^\s*#/.test(line))
.flatMap((line) => line.match(/--shards=\d+/g) || []);
assert.equal(shardsArgs.length, 3, 'evidence шарда, агрегатор, отчёт');
assert.ok(shardsArgs.every((arg) => arg === `--shards=${n}`), shardsArgs.join(' '));
assert.ok(!/--shard=\$\{\{ matrix\.shard \}\}\/4\b|--shards=4\b|из 4\)/.test(mutationWorkflow), 'старый делитель 4 не остался');
const scheduled = shards.flatMap((shard) => shardMutants(MUTANTS, shard, n).map((m) => m.id));
assert.deepEqual(scheduled.sort(), MUTANTS.map((m) => m.id).sort(), 'нет пропусков и повторов');
for (const shard of shards) {
assert.ok(shardMutants(MUTANTS, shard, n).length <= Math.ceil(MUTANTS.length / n));
}
assert.match(mutationWorkflow, /timeout-minutes: 60/, 'потолок остаётся стражем от зависшего Chromium');
});
@@ -488,7 +491,7 @@ test('#549: moving ref фиксируется один раз, а каждый
assert.ok(!mutants.includes("inputs.ref || 'dev'"), 'шарды не должны независимо читать moving ref');
});
test('#549: агрегатор требует четыре evidence одного material и report не перечитывает dev', () => {
test('#549: агрегатор требует evidence одного material и report не перечитывает dev', () => {
const evidence = mutationWorkflow.slice(mutationWorkflow.indexOf(' evidence:'), mutationWorkflow.indexOf(' report:'));
const report = mutationWorkflow.slice(mutationWorkflow.indexOf('\n report:\n'));
assert.match(evidence, /--verify-only/);