test(runner): фейковая платформа — забота раннера, а не кейса

Кейс описывал механику: сам клал fake.cmd с batch-кодом, прописывал путь к нему
и помечался osOnly win32, потому что .cmd на маке не исполнится. Тот же сценарий
на POSIX требовал второго файла с fake.sh — отсюда двойники, а забытый двойник
означал дыру: у db-repo на маке выполнялся один кейс из двенадцати, и заметили
это случайно.

Теперь кейс объявляет намерение — лог и код возврата в поле fakePlatform, — а
раннер сам кладёт .cmd или .sh под текущую ОС, пишет лог и заглушку базы и
подставляет {fakePlatform} в аргументы. Механизм включается только по объявлению
поля; остальные кейсы не затронуты.

Мигрированы db-repo (11), db-update (4), db-load-xml (4); 19 двойников удалены.
Пропусков на Windows стало 0 вместо 19 — это и были двойники.

Кейсы db-create, db-dump-cf, db-run, epf-build оставлены как есть: часть из них
однопортовая по существу (cp866 в выводе, гейт «ложного успеха» на runtimeOnly),
и каждый требует отдельного решения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-23 16:19:09 +03:00
co-authored by Claude Opus 5
parent 84e908304e
commit 0704c0ef12
39 changed files with 148 additions and 1254 deletions
+41 -2
View File
@@ -355,14 +355,48 @@ function buildArgs(skillConfig, caseData, workDir, inputFilePath, runtime) {
}
// Append extra args from case (for optional params like -Vendor, -Version).
// Supports {workDir} substitution for tests that need absolute paths inside the workspace.
// Supports {workDir} substitution for tests that need absolute paths inside the workspace,
// and {fakePlatform} — путь к фейковой платформе, которую раскладывает writeFakePlatform.
if (caseData.args_extra) {
args.push(...caseData.args_extra.map(a => typeof a === 'string' ? a.replace('{workDir}', workDir) : a));
const fakePath = join(workDir, process.platform === 'win32' ? 'fake.cmd' : 'fake.sh');
args.push(...caseData.args_extra.map(a => typeof a === 'string'
? a.replace('{workDir}', workDir).replace('{fakePlatform}', fakePath)
: a));
}
return { scriptPath, args };
}
// ─── Фейковая платформа ─────────────────────────────────────────────────────
// Кейс объявляет ЛОГ и код возврата, а не механику запуска. Раннер сам кладёт .cmd или .sh
// под текущую ОС, поэтому один кейс проверяется на обеих. Раньше каждый такой сценарий
// приходилось дублировать -posix двойником, и забытый двойник означал дыру: у db-repo на
// маке выполнялся 1 кейс из 12, и заметили это случайно.
const FAKE_PLATFORM_CMD = "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n";
const FAKE_PLATFORM_SH = "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\n# Значение /Out несёт кавычки ВНУТРИ токена (соглашение 1С, см. run_v8) — в batch их\n# снимает %~2, в sh их надо снять руками, иначе cp целится в имя с кавычками.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then\n OUT=\"$2\"\n OUT=\"${OUT#\\\"}\"\n OUT=\"${OUT%\\\"}\"\n fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 0\n";
function writeFakePlatform(workDir, spec) {
const isWin = process.platform === 'win32';
const code = Number.isInteger(spec.exit) ? spec.exit : 0;
const body = isWin
? FAKE_PLATFORM_CMD.replace('exit /b 0', `exit /b ${code}`)
: FAKE_PLATFORM_SH.replace('exit 0', `exit ${code}`);
const exe = join(workDir, isWin ? 'fake.cmd' : 'fake.sh');
writeFileSync(exe, body, 'utf8');
// Бит исполнения: на *nix навык запускает платформу через exec, без +x фейк не стартует.
if (!isWin) chmodSync(exe, 0o755);
// Лог пишется как есть — вместе с BOM и CRLF, если кейс их объявил: /Out платформы
// выглядит именно так, и разбор должен проверяться на настоящей форме.
writeFileSync(join(workDir, 'log.txt'), spec.log ?? '', 'utf8');
// Заглушка базы: навыки отказываются работать, не найдя 1Cv8.1CD, и это правильно.
if (spec.baseStub !== false) {
const ib = join(workDir, 'ib');
mkdirSync(ib, { recursive: true });
writeFileSync(join(ib, '1Cv8.1CD'), 'stub', 'utf8');
}
}
// ─── Snapshot normalization ─────────────────────────────────────────────────
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
@@ -785,6 +819,11 @@ async function runCaseAsync(testCase, opts) {
}
}
// Фейковая платформа раскладывается ДО preRun: шаги preRun могут на неё опираться.
if (caseData.fakePlatform) {
writeFakePlatform(workDir, caseData.fakePlatform);
}
// Pre-run steps
if (caseData.preRun) {
for (const step of caseData.preRun) {