mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-13 23:13:22 +03:00
test(skills): свести verify-snapshots с runner по разбору кейса
Верификатор читает тот же DSL, что и функциональный раннер, но своей
реализацией, и отставал на пять ключей: preRun[].cwd, inputFrom, cwd на уровне
кейса, раскрытие {workDir} в args_extra и маппинг from: outputPath. Кейсы,
опирающиеся на них, до платформы не доезжали вовсе — падали на подготовке
фикстуры, причём шаг preRun с относительным путём писал её в корень репозитория.
Незнакомый from теперь роняет кейс с внятным сообщением: молчаливый default
маскировал расхождение под дефект навыка (флаг уходил без значения).
Платформенная верификация: mxl-compile 28/36 -> 36/36, mxl-decompile 0/7 -> 7/7,
mxl-info 2/7 -> 7/7, mxl-validate 0/4 -> 4/4, skd-decompile 0/17 -> 17/17.
Оба ключа, которых не было в документации DSL, дописаны в README.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
241a56a29f
commit
37108b15c4
@@ -260,6 +260,7 @@ ibcmd-проход автоматически `○ skipped`, если рядом
|
||||
| `outputPath` | нет | Относительный путь для навыков с `-OutputPath` |
|
||||
| `args_extra` | нет | Массив дополнительных CLI-аргументов |
|
||||
| `preRun` | нет | Массив шагов подготовки (см. ниже) |
|
||||
| `inputFrom` | нет | Имя файла в `workDir` — вход берётся из него, а не из `input`. Нужно, когда вход производит `preRun` (напр. декомпилятор): `input` пишется ПОСЛЕ preRun и затёр бы его |
|
||||
| `expect` | нет | Дополнительные проверки (см. ниже) |
|
||||
| `expectError` | нет | `true` или строка — ожидается ошибка |
|
||||
| `noSnapshot` | нет | Непустая строка с причиной — кейс объявляет, что эталон не нужен (см. «Эталоны») |
|
||||
@@ -309,6 +310,16 @@ ibcmd-проход автоматически `○ skipped`, если рядом
|
||||
| `{ "script": "<навык>/scripts/<файл>", "input": {...}, "args": { "-Flag": "{inputFile}" } }` | Прогон другого навыка для подготовки фикстуры. Плейсхолдеры: `{inputFile}`, `{workDir}` |
|
||||
| `{ "writeFile": { "path": "<путь>", "content": "<строка или объект>" } }` | Записать произвольный файл в `workDir` (объект сериализуется в JSON) |
|
||||
|
||||
Дополнительно у шага-скрипта: `"cwd": "{workDir}"` — запустить шаг из рабочего каталога. Нужно,
|
||||
когда относительный путь в `args` (напр. `-OutputPath: "Template.xml"`) должен лечь в фикстуру, а
|
||||
не в текущий каталог процесса. Записи различаются: у шага — `{workDir}` в скобках, у кейса —
|
||||
`"cwd": "workDir"` без них.
|
||||
|
||||
Оба раннера (`runner.mjs` и `verify-snapshots.mjs`) читают этот DSL **каждый своей реализацией**.
|
||||
Добавляя ключ, добавляй его в оба и в эту таблицу: недокументированный ключ, который понимает
|
||||
только один раннер, даёт тихую дыру — кейс зелёный в функциональном прогоне и не доезжает до
|
||||
платформы в верификации.
|
||||
|
||||
## Эталоны (snapshots)
|
||||
|
||||
Эталон — директория `snapshots/<имя-кейса>/` внутри папки навыка. Содержит ожидаемый выход навыка после нормализации.
|
||||
|
||||
@@ -563,11 +563,17 @@ function buildSkillArgs(skillConfig, caseData, workDir, inputFile, runtime) {
|
||||
args.push(mapping.flag);
|
||||
switch (mapping.from) {
|
||||
case 'inputFile':
|
||||
args.push(inputFile || '');
|
||||
// inputFrom: вход берётся из файла в workDir, а не из case.input. Нужно, когда вход
|
||||
// производит preRun (например, декомпилятор) — case.input пишется ПОСЛЕ preRun и
|
||||
// затёр бы его. Как в runner.mjs.
|
||||
args.push(caseData.inputFrom ? join(workDir, caseData.inputFrom) : (inputFile || ''));
|
||||
break;
|
||||
case 'workDir':
|
||||
args.push(workDir);
|
||||
break;
|
||||
case 'outputPath':
|
||||
args.push(join(workDir, caseData.outputPath || ''));
|
||||
break;
|
||||
case 'workPath': {
|
||||
const field = mapping.field || 'objectPath';
|
||||
const val = caseData.params?.[field] ?? caseData[field];
|
||||
@@ -592,10 +598,19 @@ function buildSkillArgs(skillConfig, caseData, workDir, inputFile, runtime) {
|
||||
args.push(String(caseData.params?.[field] ?? caseData[field] ?? ''));
|
||||
} else if (mapping.from === 'literal') {
|
||||
args.push(mapping.value || '');
|
||||
} else {
|
||||
// Незнакомый from раньше молча не давал значения — флаг уходил без аргумента, и
|
||||
// это выглядело как дефект навыка. DSL читают два раннера, поэтому расхождение
|
||||
// должно быть громким.
|
||||
throw new Error(`_skill.json: неизвестный "from": "${mapping.from}" у флага ${mapping.flag}`
|
||||
+ ' — verify-snapshots.mjs не знает этого маппинга (см. buildSkillArgs)');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (caseData.args_extra) args.push(...caseData.args_extra);
|
||||
// Плейсхолдер {workDir} раскрывается и в args_extra — как в runner.mjs.
|
||||
if (caseData.args_extra) {
|
||||
args.push(...caseData.args_extra.map(a => typeof a === 'string' ? a.replace('{workDir}', workDir) : a));
|
||||
}
|
||||
return { scriptPath, args };
|
||||
}
|
||||
|
||||
@@ -631,7 +646,10 @@ function runPreSteps(preRun, workDir, runtime, log) {
|
||||
}
|
||||
const stepName = step.script.split('/').pop();
|
||||
try {
|
||||
execSkill(runtime, step.script, preArgs);
|
||||
// cwd: "{workDir}" — шаг запускается из рабочего каталога, чтобы относительные
|
||||
// пути в его args (напр. -OutputPath Template.xml) легли в фикстуру, а не в репозиторий.
|
||||
const preCwd = step.cwd === '{workDir}' ? workDir : REPO_ROOT;
|
||||
execSkill(runtime, step.script, preArgs, 60_000, preCwd);
|
||||
log(`preRun: ${stepName}`, true);
|
||||
} catch (e) {
|
||||
log(`preRun: ${stepName}`, false, e.stderr || e.message);
|
||||
@@ -875,7 +893,7 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
|
||||
try {
|
||||
const { args } = buildSkillArgs(skillConfig, caseData, workDir, inputFile, opts.runtime);
|
||||
const mainCwd = skillConfig.cwd === 'workDir' ? workDir : REPO_ROOT;
|
||||
const mainCwd = (caseData.cwd || skillConfig.cwd) === 'workDir' ? workDir : REPO_ROOT;
|
||||
const output = execSkill(opts.runtime, skillConfig.script, args, 60_000, mainCwd);
|
||||
const lastLine = output.trim().split('\n').pop();
|
||||
if (caseData.expectError) {
|
||||
|
||||
Reference in New Issue
Block a user