From 26ca7276e6ee348e680ed73dcb2e5b2634db6f9a Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Thu, 13 Aug 2026 15:55:07 +0300 Subject: [PATCH] =?UTF-8?q?test(skills):=20expected-skips.mjs=20=E2=80=94?= =?UTF-8?q?=20=D0=BE=D0=B6=D0=B8=D0=B4=D0=B0=D0=B5=D0=BC=D0=BE=D0=B5=20?= =?UTF-8?q?=D1=87=D0=B8=D1=81=D0=BB=D0=BE=20=D0=BF=D1=80=D0=BE=D0=BF=D1=83?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=B2=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D0=BE=D0=BC=D0=BD=D0=B5=D0=BD=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit «skipped» — норма, а не падение, но само число ни о чём не говорит, пока не с чем сверить, а запоминать его нельзя: оно растёт с набором кейсов. Прежняя сверка жила в личной памятке как grep по 'external:|runtimeOnly|osOnly' и уже сломалась — она считает скипом ЛЮБОЙ osOnly, а posix-кейсы фейка платформы на маке как раз выполняются (grep давал 67 против 59 реальных). Скрипт повторяет правила гейтинга раннера и живёт рядом с ним, поэтому расходиться им негде. --list печатает пропуски поимённо с причиной. Сверено: win32/powershell 8, win32/python 11, darwin/python 59 — совпало с прогонами. Co-Authored-By: Claude Opus 5 (1M context) --- tests/skills/README.md | 15 ++++++++ tests/skills/expected-skips.mjs | 63 +++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 tests/skills/expected-skips.mjs diff --git a/tests/skills/README.md b/tests/skills/README.md index 3f86199c..8e5ae84a 100644 --- a/tests/skills/README.md +++ b/tests/skills/README.md @@ -21,6 +21,21 @@ node tests/skills/runner.mjs --help # полный Exit code: 0 = все прошли, 1 = есть падения. +### Сколько кейсов должно быть пропущено + +`○ skipped` — норма, а не падение: кейс может гейтиться по ОС (`osOnly`), по порту +(`runtimeOnly`) или требовать внешней выгрузки (`setup: external:`), которой на машине нет. +Но само число ни о чём не говорит, пока не с чем сверить, а запоминать его нельзя — оно растёт +с набором кейсов. + +```bash +node tests/skills/expected-skips.mjs --runtime python # ожидаемое число для этой ОС и порта +node tests/skills/expected-skips.mjs --list # + поимённо, с причиной +``` + +Совпало со `Skipped: N` из прогона — норма. Разошлось — разбираться: появился новый источник +скипа либо кейс скипается не по той причине, что заявлена. + ### Платформенная верификация снапшотов ```bash diff --git a/tests/skills/expected-skips.mjs b/tests/skills/expected-skips.mjs new file mode 100644 index 00000000..61eb0d1b --- /dev/null +++ b/tests/skills/expected-skips.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node +// Сколько кейсов ДОЛЖНО быть пропущено на этой ОС и этом порте. +// +// «skipped» в прогоне — норма, а не падение: часть кейсов гейтится по ОС, по порту или по +// наличию внешней выгрузки. Но само число ни о чём не говорит, пока не с чем сверить, а +// запоминать его нельзя — оно растёт с набором кейсов. Отсюда этот скрипт: он считает +// ожидаемое значение по тем же правилам, что и раннер, и его надо сверять с `Skipped: N`. +// +// Совпало — норма. Разошлось — разбираться: появился новый источник скипа либо кейс +// скипается не по той причине, что заявлена. +// +// Запуск: node tests/skills/expected-skips.mjs [--runtime python] [--list] +import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const CASES = join(HERE, 'cases'); + +const argv = process.argv.slice(2); +const runtime = argv.includes('--runtime') ? argv[argv.indexOf('--runtime') + 1] : 'powershell'; +const listMode = argv.includes('--list'); +const os = process.platform; + +const reasons = { external: [], runtimeOnly: [], osOnly: [] }; + +for (const skill of readdirSync(CASES)) { + const dir = join(CASES, skill); + if (!statSync(dir).isDirectory()) continue; + const skillCfgPath = join(dir, '_skill.json'); + const skillCfg = existsSync(skillCfgPath) ? JSON.parse(readFileSync(skillCfgPath, 'utf8')) : {}; + + for (const file of readdirSync(dir)) { + if (!file.endsWith('.json') || file === '_skill.json') continue; + const id = `${skill}/${file.replace(/\.json$/, '')}`; + const c = JSON.parse(readFileSync(join(dir, file), 'utf8')); + + // Порядок совпадает с раннером: сначала ОС, потом порт, потом фикстура. + if (c.osOnly && ![].concat(c.osOnly).includes(os)) { reasons.osOnly.push(id); continue; } + if (c.runtimeOnly && c.runtimeOnly !== runtime) { reasons.runtimeOnly.push(id); continue; } + const setup = String(c.setup || skillCfg.setup || ''); + if (setup.startsWith('external:') && !existsSync(setup.slice('external:'.length))) { + reasons.external.push(id); + } + } +} + +const total = reasons.external.length + reasons.runtimeOnly.length + reasons.osOnly.length; + +if (listMode) { + for (const [key, ids] of Object.entries(reasons)) { + if (!ids.length) continue; + console.log(`\n${key} (${ids.length}):`); + for (const id of ids) console.log(` ${id}`); + } + console.log(''); +} + +console.log( + `Ожидается пропущенных: ${total} [${os}, runtime: ${runtime}] ` + + `= external ${reasons.external.length} + runtimeOnly ${reasons.runtimeOnly.length} + osOnly ${reasons.osOnly.length}`, +); +console.log('Сверить с "Skipped: N" из прогона runner.mjs с теми же --runtime.');