mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-26 21:19:42 +03:00
fix(tests): verify-snapshots грузит фикстурные кейсы и показывает долю непроверенного
configDir приходил только из скилл-уровневого setup (empty-config) и из ветки external, поэтому у навыков с setup: none кейс на фикстуре до платформы не доезжал — и всё равно получал PASS. Таких кейсов девять: пять support-edit, три meta-validate и один skd-decompile. В наборе по умолчанию дефект не проявлялся: там у всех навыков setup: empty-config, и загрузка шла. Теперь фикстура-конфигурация грузится как external-выгрузка, а путь «грузить нечего и спец-маршрут не подошёл» вместо молчаливого PASS требует объявить skipPlatformVerify с причиной. skd-decompile отнесён к standalone-навыкам: его выход — JSON-описание, грузить нечего. Отдельно — видимость: «прошло» и «проверено платформой» больше не одно и то же число. В консоли и в отчёте печатается, сколько кейсов реально обратилось к платформе, а сколько прошло мимо и почему (external, standalone, ожидаемый отказ навыка, недоступная платформа); в таблице отчёта появилась колонка «Платформа». Падения в эту долю не попадают: у них обращение было и не удалось. Девяти кейсам проставлен пропуск с проверенной причиной. Для support-edit причина выяснялась опытом: фикстуры содержат рукотворный Ext/ParentConfigurations.bin, и платформа отвечает «Ошибка формата потока», тогда как настоящий bin из типовой грузится, а выгрузка без bin — тоже. Попытка пересобрать фикстуры полноценными объектами вылечила XML, но не bin, и была откачена. Документация: в README добавлена таблица маршрутов платформенной проверки и правила появления configDir — раньше это знание жило только в коде, и «зелёный» отчёт не отличался от «проверенного». В docs/1c-support-state-spec.md записано, что по описанной грамматике bin можно читать, но нельзя собрать файл, который примет платформа. Полный прогон verify-snapshots до и после правки даёт одинаковый состав: 461 passed, 6 failed, 30 skipped из 497 — новых падений нет. Тесты: 871/879, интеграционные 13/13. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
333bcad938
commit
9c66e89372
@@ -62,6 +62,8 @@ guidA, X, guidПоставщика, "версия", "вендор", "имя", co
|
||||
|
||||
Парсер, разбирающий весь файл по этой грамматике, потребляет 100% токенов на образце с `K = 7` (458107 токенов, остатка нет) — грамматика подтверждена.
|
||||
|
||||
**Грамматика описывает чтение: собрать по ней файл, который примет платформа, нельзя.** Рукотворный `bin` (`K = 1`, UTF-8 с BOM) `/LoadConfigFromFiles` отвергает — «Ошибка формата потока», тогда как выгрузка с настоящим `bin` либо вовсе без него загружается (проверено на 8.3.24.1691). Для стендов и фикстур файл берут у платформы — выгрузкой конфигурации, стоящей на поддержке.
|
||||
|
||||
---
|
||||
|
||||
## 3. Глобальная «возможность изменения» (`G`)
|
||||
|
||||
@@ -46,6 +46,49 @@ node tests/skills/verify-snapshots.mjs --help # полный
|
||||
|
||||
Перепрогоняет навык из DSL кейса и грузит результат в 1С — отлавливает случаи, когда снапшоты обновили, но платформа уже не принимает выход.
|
||||
|
||||
#### Когда кейс доезжает до платформы
|
||||
|
||||
Сначала — что вообще попадает в прогон: навыки из `DEFAULT_SKILLS` (без `--skill`), у которых есть
|
||||
каталог `snapshots/`, и их кейсы кроме `error-*`. Остальное молча не запускается, так что «в отчёте
|
||||
нет падений» и «всё проверено» — не одно и то же.
|
||||
|
||||
Дальше маршрут выбирается по навыку; из кейса на него влияют только `skipPlatformVerify` и `setup`.
|
||||
`OK` в отчёте ещё не значит, что 1С что-то видела:
|
||||
|
||||
| Условие | Что делает verify | Статус |
|
||||
|---|---|---|
|
||||
| кейс объявил `skipPlatformVerify` | ничего не запускает | `SKIP` с причиной из кейса |
|
||||
| `mxl-compile` | макет вкладывается в обработку, `epf-build` | `OK` / `FAIL` |
|
||||
| `skd-compile`, `skd-edit` | схема оборачивается во внешний отчёт, `erf-build` | `OK` / `FAIL` |
|
||||
| прочие `skd-*`, `mxl-*` (`STANDALONE_SKILLS`) | ничего: выход не конфигурация | `OK`, помечен «без платформы» |
|
||||
| `epf-init`, `erf-init` | `epf-build` по произведённому исходнику | `OK` / `FAIL` |
|
||||
| `cfe-*` (`CFE_SKILLS`) | двухэтапно: база, затем расширение | `OK` / `FAIL` |
|
||||
| `template-add`, `help-add` | EPF или конфигурация — по наличию `Configuration.xml` | `OK` / `FAIL` |
|
||||
| есть каталог конфигурации | `db-create` → `db-load-xml` → `db-update` | `OK` / `FAIL` |
|
||||
| ничего из перечисленного | — | `FAIL` с требованием объявить `skipPlatformVerify` |
|
||||
|
||||
Каталог конфигурации (`configDir`) появляется в трёх случаях: скилл-уровневый `setup` —
|
||||
`empty-config` (именно **скилл**-уровневый, из `_skill.json`; переопределение в кейсе на это не
|
||||
влияет), навык — `cf-init`, либо кейс объявил `setup: external:` или `setup: fixture:` — для
|
||||
фикстуры дополнительно нужен `Configuration.xml` в её корне.
|
||||
|
||||
Отдельный случай — `external:`: конфигурация есть, но грузить её бессмысленно, 1С читала бы
|
||||
собственную выгрузку типовой (~3 мин на кейс, ноль информации о навыке). Кейс проходит, но
|
||||
обращения к платформе не было.
|
||||
|
||||
Поэтому «прошло» и «проверено платформой» — разные числа, и второе печатается отдельной строкой
|
||||
с разбивкой по причинам:
|
||||
|
||||
```
|
||||
Results: <passed> passed, <failed> failed, <skipped> skipped out of <total>
|
||||
из них проверено платформой: <N>; без обращения к платформе: <M>
|
||||
• <k> — external: грузилась бы собственная выгрузка типовой
|
||||
• <k> — standalone-навык: выход не конфигурация
|
||||
• <k> — навык ожидаемо отказал: своего выхода нет, грузить нечего
|
||||
```
|
||||
|
||||
Пока эта доля не названа, отчёт читается как «всё проверено», хотя платформу спрашивали не у всех.
|
||||
|
||||
## Гарды-инварианты
|
||||
|
||||
Проверяют не вывод навыков, а инварианты исходников — то, что снапшотами не ловится. Навыки
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
"noSnapshot": "meta-validate только читает и печатает — эталон зафиксировал бы фикстуру, а не выход проверяемого навыка; проверяется stdout",
|
||||
"expect": {
|
||||
"stdoutNotContains": ["появился в формате", "Unusual version"]
|
||||
}
|
||||
},
|
||||
"skipPlatformVerify": "фикстура — одиночный объект без Configuration.xml: загружать нечего"
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
"noSnapshot": "meta-validate только читает и печатает — эталон зафиксировал бы фикстуру, а не выход проверяемого навыка; проверяется stdout",
|
||||
"expect": {
|
||||
"stdoutNotContains": ["Unusual version", "появился в формате"]
|
||||
}
|
||||
},
|
||||
"skipPlatformVerify": "фикстура — одиночный объект без Configuration.xml: загружать нечего"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
"params": { "objectPath": "Catalogs/Товары.xml" },
|
||||
"expect": {
|
||||
"stdoutNotContains": "20."
|
||||
}
|
||||
},
|
||||
"skipPlatformVerify": "фикстура — одиночный объект без Configuration.xml: загружать нечего"
|
||||
}
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": "ВЫКЛЮЧЕНА"
|
||||
}
|
||||
},
|
||||
"skipPlatformVerify": "фикстура синтетическая: рукотворный Ext/ParentConfigurations.bin платформа не принимает"
|
||||
}
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": "ВКЛЮЧЕНА"
|
||||
}
|
||||
},
|
||||
"skipPlatformVerify": "фикстура синтетическая: рукотворный Ext/ParentConfigurations.bin платформа не принимает"
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
"file": "Ext/ParentConfigurations.bin",
|
||||
"bom": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"skipPlatformVerify": "фикстура синтетическая: рукотворный Ext/ParentConfigurations.bin платформа не принимает"
|
||||
}
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
],
|
||||
"expect": {
|
||||
"stdoutContains": "снят с поддержки"
|
||||
}
|
||||
},
|
||||
"skipPlatformVerify": "фикстура синтетическая: рукотворный Ext/ParentConfigurations.bin платформа не принимает"
|
||||
}
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
"-Set",
|
||||
"editable"
|
||||
],
|
||||
"expectError": "Capability on"
|
||||
"expectError": "Capability on",
|
||||
"skipPlatformVerify": "фикстура синтетическая: рукотворный Ext/ParentConfigurations.bin платформа не принимает"
|
||||
}
|
||||
|
||||
@@ -680,7 +680,7 @@ function runPreSteps(preRun, workDir, runtime, log) {
|
||||
|
||||
// Standalone file skills — produce files (not configs), platform load = just run script
|
||||
const STANDALONE_SKILLS = new Set([
|
||||
'skd-compile', 'skd-edit', 'skd-info', 'skd-validate',
|
||||
'skd-compile', 'skd-edit', 'skd-info', 'skd-validate', 'skd-decompile',
|
||||
'mxl-decompile', 'mxl-info', 'mxl-validate',
|
||||
]);
|
||||
|
||||
@@ -871,6 +871,10 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
}
|
||||
cpSync(fixturePath, workDir, { recursive: true });
|
||||
log(`fixture: ${fixtureName}`, true);
|
||||
// Фикстура-конфигурация — такой же вход для платформы, как external-выгрузка. Раньше
|
||||
// configDir приходил только из скилл-уровневого setup (empty-config), поэтому у навыков
|
||||
// с setup: none фикстура до платформы не доезжала, а кейс всё равно получал PASS.
|
||||
if (existsSync(join(workDir, 'Configuration.xml'))) configDir = workDir;
|
||||
} else if (typeof caseData.setup === 'string' && caseData.setup.startsWith('external:')) {
|
||||
const extPath = resolve(REPO_ROOT, caseData.setup.slice('external:'.length));
|
||||
// Недоступная внешняя выгрузка — СКИП, как в runner.mjs (`ensureSetup`, ветка
|
||||
@@ -1029,6 +1033,7 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
return result;
|
||||
}
|
||||
log(skillName, true, `(expected error) ${detail.substring(0, 100)}`);
|
||||
result.noPlatformReason = 'навык ожидаемо отказал — своего выхода нет, грузить нечего';
|
||||
result.passed = true;
|
||||
return result;
|
||||
}
|
||||
@@ -1057,6 +1062,7 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
// Wrap produced Template.xml in an external report (ERF) and try to build —
|
||||
// platform either accepts the schema or rejects it with an error.
|
||||
if (!opts.v8ctx) {
|
||||
result.noPlatformReason = 'платформа недоступна в этом окружении';
|
||||
result.passed = true;
|
||||
log('platform-load', true, 'skipped (no v8 context)');
|
||||
return result;
|
||||
@@ -1090,6 +1096,7 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
'-OutputFile', join(erfOutDir, 'TestReport.erf'),
|
||||
], 120_000);
|
||||
log('erf-build', true, 'platform accepted schema');
|
||||
result.platformChecked = true;
|
||||
result.passed = true;
|
||||
} catch (e) {
|
||||
const detail = errDetail(e);
|
||||
@@ -1142,6 +1149,7 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
'-OutputFile', join(epfOutDir, 'TestProc.epf'),
|
||||
], 180_000);
|
||||
log('epf-build', true, 'platform accepted MXL');
|
||||
result.platformChecked = true;
|
||||
result.passed = true;
|
||||
} catch (e) {
|
||||
const detail = errDetail(e);
|
||||
@@ -1152,6 +1160,7 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
}
|
||||
|
||||
if (isStandalone) {
|
||||
result.noPlatformReason = 'standalone-навык: выход не конфигурация';
|
||||
result.passed = true;
|
||||
log('platform-load', true, 'skipped (standalone file, not a config)');
|
||||
return result;
|
||||
@@ -1199,6 +1208,7 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
'-OutputFile', outFile,
|
||||
], 180_000);
|
||||
log('epf-build', true, `platform built ${epfExt}`);
|
||||
result.platformChecked = true;
|
||||
result.passed = true;
|
||||
} catch (e) {
|
||||
const detail = errDetail(e);
|
||||
@@ -1314,6 +1324,7 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
}
|
||||
}
|
||||
|
||||
result.platformChecked = true;
|
||||
result.passed = true;
|
||||
return result;
|
||||
}
|
||||
@@ -1326,8 +1337,13 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
}
|
||||
|
||||
if (!configDir) {
|
||||
// No config to load — setup was 'none' and not EPF/standalone
|
||||
result.passed = true;
|
||||
// Грузить нечего, и спец-маршрута (MXL/SKD/EPF/CFE/standalone) для навыка нет. Раньше здесь
|
||||
// стоял PASS — кейс выглядел проверенным, ни разу не обратившись к платформе. Пропуск
|
||||
// допустим, но только объявленный: причина должна быть в кейсе и видна в отчёте.
|
||||
result.errors.push(
|
||||
'Платформенной проверки не было: конфигурации для загрузки нет, спец-маршрут не подошёл. '
|
||||
+ 'Если для этого кейса проверка невозможна или вырождается — объявите в кейсе '
|
||||
+ '"skipPlatformVerify": "<причина>"');
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1418,6 +1434,7 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
// to exercise the skill script against real-world XML, not to validate
|
||||
// that an entire vendor config loads into a fresh DB.
|
||||
if (caseProvidedConfig && caseData.setup.startsWith('external:')) {
|
||||
result.noPlatformReason = 'external: грузилась бы собственная выгрузка типовой (~3 мин, ноль информации о навыке)';
|
||||
result.passed = true;
|
||||
log('platform-load', true, 'skipped (external setup)');
|
||||
return result;
|
||||
@@ -1456,6 +1473,7 @@ async function verifyCase(skillName, caseName, skillConfig, caseData, opts) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.platformChecked = true;
|
||||
result.passed = true;
|
||||
} catch (e) {
|
||||
result.errors.push(`Unexpected error: ${e.message}`);
|
||||
@@ -1533,18 +1551,21 @@ function writeReport(results) {
|
||||
`Total: ${results.length} | Passed: ${results.filter(r => r.passed && !r.skipped).length}`
|
||||
+ ` | Failed: ${results.filter(r => !r.passed && !r.skipped).length}`
|
||||
+ ` | Skipped: ${results.filter(r => r.skipped).length}`,
|
||||
`Проверено платформой: ${results.filter(r => r.platformChecked).length}`
|
||||
+ ` | Прошло без обращения к платформе: ${results.filter(r => r.passed && !r.skipped && !r.platformChecked).length}`,
|
||||
``,
|
||||
];
|
||||
|
||||
lines.push('| Skill | Case | Status | Error |');
|
||||
lines.push('|-------|------|--------|-------|');
|
||||
lines.push('| Skill | Case | Status | Платформа | Error |');
|
||||
lines.push('|-------|------|--------|-----------|-------|');
|
||||
for (const r of results) {
|
||||
// Пропуск — не падение: в консольной сводке они уже различались, а в файле отчёта пропуск
|
||||
// выглядел как FAIL и попадал в счётчик падений. Отчёт читают глазами и по нему решают,
|
||||
// есть ли проблема, — расхождение с консолью здесь дороже всего.
|
||||
const status = r.skipped ? 'SKIP' : (r.passed ? 'OK' : 'FAIL');
|
||||
const error = r.errors.length > 0 ? r.errors[0].substring(0, 100).replace(/\|/g, '\\|').replace(/\n/g, ' ') : '';
|
||||
lines.push(`| ${r.skill} | ${r.case} | ${status} | ${error} |`);
|
||||
const plat = r.platformChecked ? 'да' : (r.skipped ? '—' : (r.noPlatformReason || 'нет'));
|
||||
lines.push(`| ${r.skill} | ${r.case} | ${status} | ${plat.substring(0, 60)} | ${error} |`);
|
||||
}
|
||||
|
||||
const failures = results.filter(r => !r.passed && !r.skipped);
|
||||
@@ -1635,6 +1656,22 @@ async function main() {
|
||||
console.log(`\n${'='.repeat(60)}`);
|
||||
console.log(`Results: ${passed} passed, ${failed} failed`
|
||||
+ (skipped ? `, ${skipped} skipped` : '') + ` out of ${results.length}`);
|
||||
// «Прошло» и «проверено платформой» — разные вещи: часть кейсов законно идёт мимо неё
|
||||
// (навык отказал, выход не конфигурация, external). Пока эта доля не названа, отчёт читается
|
||||
// как «всё проверено», хотя платформу спрашивали не у всех.
|
||||
const onPlatform = results.filter(r => r.platformChecked).length;
|
||||
// Только успешные: у падения обращение к платформе было — оно и не удалось, мешать их
|
||||
// с «мимо платформы» значит завышать долю непроверенного.
|
||||
const offPlatform = results.filter(r => r.passed && !r.skipped && !r.platformChecked);
|
||||
console.log(` из них проверено платформой: ${onPlatform}; без обращения к платформе: ${offPlatform.length}`);
|
||||
const byReason = {};
|
||||
for (const r of offPlatform) {
|
||||
const key = r.noPlatformReason || 'причина не указана';
|
||||
(byReason[key] = byReason[key] || []).push(`${r.skill}/${r.case}`);
|
||||
}
|
||||
for (const [reason, list] of Object.entries(byReason)) {
|
||||
console.log(` • ${list.length} — ${reason}`);
|
||||
}
|
||||
for (const r of results.filter(x => x.skipped)) {
|
||||
console.log(` \u25cb ${r.skill}/${r.case} \u2014 ${r.skipReason}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user