diff --git a/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/КолонкиСТакимЖеРазмером.xml b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/КолонкиСТакимЖеРазмером.xml new file mode 100644 index 00000000..e017eec5 --- /dev/null +++ b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/КолонкиСТакимЖеРазмером.xml @@ -0,0 +1,96 @@ + + + + ru + ru + + ru + Русский + Русский + + + en + Английский + Английский + + + + 1 + + 0 + + 1 + + + + + eb5ee63c-905d-4d8b-ab55-dc5ec80c067a + 2 + + 0 + + 1 + + + + 1 + + 1 + + + + + 0 + + + + 0 + + + ru + Ширина 20 + + + + + + + + 1 + + eb5ee63c-905d-4d8b-ab55-dc5ec80c067a + + + 0 + + + ru + Ширина 20 + + + + + + + 0 + + + ru + Ширина 20 + + + + + + + true + 2 + 2 + 2 + + 160 + + + 72 + + \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/МакетСоскрытойСтрокойИСтрокойСЗаданнымШрифтом.xml b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/МакетСоскрытойСтрокойИСтрокойСЗаданнымШрифтом.xml new file mode 100644 index 00000000..2faddddc --- /dev/null +++ b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/МакетСоскрытойСтрокойИСтрокойСЗаданнымШрифтом.xml @@ -0,0 +1,126 @@ + + + + ru + ru + + ru + Русский + Русский + + + en + Английский + Английский + + + + 1 + + + 0 + + + + 0 + + + ru + 1 строка + + + + + + + + 1 + + 1 + + + 0 + + + ru + 2 скрыта + + + + + + + + 2 + + + + 0 + + + ru + 3 строка + + + + + + + + 3 + + true + + + + 4 + + + + 2 + + + ru + задан полужирный шрифт + + + + + + + + 5 + + 3 + + + 3 + + + ru + задан шрифт строки + + + + + + + true + 4 + 6 + 6 + + + + true + + + 0 + + + 1 + + + 72 + + \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой2КолонкиШиринаПоУмолчанию.xml b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой2КолонкиШиринаПоУмолчанию.xml new file mode 100644 index 00000000..98d85d21 --- /dev/null +++ b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой2КолонкиШиринаПоУмолчанию.xml @@ -0,0 +1,54 @@ + + + + ru + ru + + ru + Русский + Русский + + + en + Английский + Английский + + + + 2 + + + 0 + + + + 0 + + + ru + 1 (Ширина по умолчанию) + + + + + + + 0 + + + ru + 2 (Ширина по умолчанию) + + + + + + + true + 1 + 1 + 1 + + 72 + + \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой2Строки.xml b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой2Строки.xml new file mode 100644 index 00000000..e037b2c0 --- /dev/null +++ b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой2Строки.xml @@ -0,0 +1,99 @@ + + + + ru + ru + + ru + Русский + Русский + + + en + Английский + Английский + + + + 2 + + 0 + + 1 + + + + 1 + + 2 + + + + + 0 + + + + 0 + + + ru + ширина 20 + + + + + + + 0 + + + ru + шинина 40 + + + + + + + + 1 + + + + 0 + + + ru + 2. Ширина 20 + + + + + + + 0 + + + ru + 2. Ширина 40 + + + + + + + true + 3 + 2 + 2 + + 160 + + + 320 + + + 72 + + \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой3КолонкиСРазнойШириной.xml b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой3КолонкиСРазнойШириной.xml new file mode 100644 index 00000000..604aa807 --- /dev/null +++ b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой3КолонкиСРазнойШириной.xml @@ -0,0 +1,92 @@ + + + + ru + ru + + ru + Русский + Русский + + + en + Английский + Английский + + + + 3 + + 0 + + 1 + + + + 1 + + 2 + + + + 2 + + 3 + + + + + 0 + + + + 0 + + + ru + Ширина 10 + + + + + + + 0 + + + ru + Ширина 20 + + + + + + + 0 + + + ru + Ширина 30 + + + + + + + true + 4 + 1 + 1 + + 80 + + + 160 + + + 240 + + + 72 + + \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой3КолонкиСмешанный.xml b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой3КолонкиСмешанный.xml new file mode 100644 index 00000000..2732bfff --- /dev/null +++ b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/Простой3КолонкиСмешанный.xml @@ -0,0 +1,83 @@ + + + + ru + ru + + ru + Русский + Русский + + + en + Английский + Английский + + + + 3 + + 0 + + 1 + + + + 2 + + 2 + + + + + 0 + + + + 0 + + + ru + Ширина 20 + + + + + + + 0 + + + ru + Ширина по умолчанию + + + + + + + 0 + + + ru + Ширина 40 + + + + + + + true + 3 + 1 + 1 + + 160 + + + 320 + + + 72 + + \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/ЯчейкиСОформлением.xml b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/ЯчейкиСОформлением.xml new file mode 100644 index 00000000..fc459a7a --- /dev/null +++ b/tests/skills/cases/mxl-compile/fixtures/platform-roundtrip/ЯчейкиСОформлением.xml @@ -0,0 +1,90 @@ + + + + ru + ru + + ru + Русский + Русский + + + en + Английский + Английский + + + + 1 + + + 0 + + + + 1 + + + ru + рамка + + + + + + + + 1 + + + + 2 + + + ru + курсив, красный + + + + + + + + 2 + + + + 3 + + + ru + Полужирный + + + + + + + true + 4 + 3 + 3 + + Solid + + + + + 0 + + + 0 + style:SpecialTextColor + + + 1 + + + 72 + + \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/platform-default-widths.json b/tests/skills/cases/mxl-compile/platform-default-widths.json new file mode 100644 index 00000000..eac5b519 --- /dev/null +++ b/tests/skills/cases/mxl-compile/platform-default-widths.json @@ -0,0 +1,25 @@ +{ + "name": "Раундтрип макета ПЛАТФОРМЫ байт в байт: Простой2КолонкиШиринаПоУмолчанию", + "setup": "fixture:platform-roundtrip", + "preRun": [ + { + "script": "mxl-decompile/scripts/mxl-decompile", + "args": { + "-TemplatePath": "Простой2КолонкиШиринаПоУмолчанию.xml", + "-OutputPath": "back.json" + }, + "cwd": "{workDir}" + } + ], + "params": { + "outputPath": "out.xml" + }, + "noSnapshot": "эталон — файл платформы, сверяется expect.filesEqual", + "expect": { + "filesEqual": { + "actual": "out.xml", + "expected": "Простой2КолонкиШиринаПоУмолчанию.xml" + } + }, + "inputFrom": "back.json" +} \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/platform-hidden-row-and-fonts.json b/tests/skills/cases/mxl-compile/platform-hidden-row-and-fonts.json new file mode 100644 index 00000000..a942fa42 --- /dev/null +++ b/tests/skills/cases/mxl-compile/platform-hidden-row-and-fonts.json @@ -0,0 +1,25 @@ +{ + "name": "Раундтрип макета ПЛАТФОРМЫ байт в байт: МакетСоскрытойСтрокойИСтрокойСЗаданнымШрифтом", + "setup": "fixture:platform-roundtrip", + "preRun": [ + { + "script": "mxl-decompile/scripts/mxl-decompile", + "args": { + "-TemplatePath": "МакетСоскрытойСтрокойИСтрокойСЗаданнымШрифтом.xml", + "-OutputPath": "back.json" + }, + "cwd": "{workDir}" + } + ], + "params": { + "outputPath": "out.xml" + }, + "noSnapshot": "эталон — файл платформы, сверяется expect.filesEqual", + "expect": { + "filesEqual": { + "actual": "out.xml", + "expected": "МакетСоскрытойСтрокойИСтрокойСЗаданнымШрифтом.xml" + } + }, + "inputFrom": "back.json" +} \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/platform-mixed.json b/tests/skills/cases/mxl-compile/platform-mixed.json new file mode 100644 index 00000000..fc38bf07 --- /dev/null +++ b/tests/skills/cases/mxl-compile/platform-mixed.json @@ -0,0 +1,25 @@ +{ + "name": "Раундтрип макета ПЛАТФОРМЫ байт в байт: Простой3КолонкиСмешанный", + "setup": "fixture:platform-roundtrip", + "preRun": [ + { + "script": "mxl-decompile/scripts/mxl-decompile", + "args": { + "-TemplatePath": "Простой3КолонкиСмешанный.xml", + "-OutputPath": "back.json" + }, + "cwd": "{workDir}" + } + ], + "params": { + "outputPath": "out.xml" + }, + "noSnapshot": "эталон — файл платформы, сверяется expect.filesEqual", + "expect": { + "filesEqual": { + "actual": "out.xml", + "expected": "Простой3КолонкиСмешанный.xml" + } + }, + "inputFrom": "back.json" +} \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/platform-same-width-columns.json b/tests/skills/cases/mxl-compile/platform-same-width-columns.json new file mode 100644 index 00000000..f182937f --- /dev/null +++ b/tests/skills/cases/mxl-compile/platform-same-width-columns.json @@ -0,0 +1,25 @@ +{ + "name": "Раундтрип макета ПЛАТФОРМЫ байт в байт: КолонкиСТакимЖеРазмером", + "setup": "fixture:platform-roundtrip", + "preRun": [ + { + "script": "mxl-decompile/scripts/mxl-decompile", + "args": { + "-TemplatePath": "КолонкиСТакимЖеРазмером.xml", + "-OutputPath": "back.json" + }, + "cwd": "{workDir}" + } + ], + "params": { + "outputPath": "out.xml" + }, + "noSnapshot": "эталон — файл платформы, сверяется expect.filesEqual", + "expect": { + "filesEqual": { + "actual": "out.xml", + "expected": "КолонкиСТакимЖеРазмером.xml" + } + }, + "inputFrom": "back.json" +} \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/platform-styled-cells.json b/tests/skills/cases/mxl-compile/platform-styled-cells.json new file mode 100644 index 00000000..b46fae39 --- /dev/null +++ b/tests/skills/cases/mxl-compile/platform-styled-cells.json @@ -0,0 +1,25 @@ +{ + "name": "Раундтрип макета ПЛАТФОРМЫ байт в байт: ЯчейкиСОформлением", + "setup": "fixture:platform-roundtrip", + "preRun": [ + { + "script": "mxl-decompile/scripts/mxl-decompile", + "args": { + "-TemplatePath": "ЯчейкиСОформлением.xml", + "-OutputPath": "back.json" + }, + "cwd": "{workDir}" + } + ], + "params": { + "outputPath": "out.xml" + }, + "noSnapshot": "эталон — файл платформы, сверяется expect.filesEqual", + "expect": { + "filesEqual": { + "actual": "out.xml", + "expected": "ЯчейкиСОформлением.xml" + } + }, + "inputFrom": "back.json" +} \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/platform-three-widths.json b/tests/skills/cases/mxl-compile/platform-three-widths.json new file mode 100644 index 00000000..6d85611f --- /dev/null +++ b/tests/skills/cases/mxl-compile/platform-three-widths.json @@ -0,0 +1,25 @@ +{ + "name": "Раундтрип макета ПЛАТФОРМЫ байт в байт: Простой3КолонкиСРазнойШириной", + "setup": "fixture:platform-roundtrip", + "preRun": [ + { + "script": "mxl-decompile/scripts/mxl-decompile", + "args": { + "-TemplatePath": "Простой3КолонкиСРазнойШириной.xml", + "-OutputPath": "back.json" + }, + "cwd": "{workDir}" + } + ], + "params": { + "outputPath": "out.xml" + }, + "noSnapshot": "эталон — файл платформы, сверяется expect.filesEqual", + "expect": { + "filesEqual": { + "actual": "out.xml", + "expected": "Простой3КолонкиСРазнойШириной.xml" + } + }, + "inputFrom": "back.json" +} \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/platform-two-rows.json b/tests/skills/cases/mxl-compile/platform-two-rows.json new file mode 100644 index 00000000..0a55dff3 --- /dev/null +++ b/tests/skills/cases/mxl-compile/platform-two-rows.json @@ -0,0 +1,25 @@ +{ + "name": "Раундтрип макета ПЛАТФОРМЫ байт в байт: Простой2Строки", + "setup": "fixture:platform-roundtrip", + "preRun": [ + { + "script": "mxl-decompile/scripts/mxl-decompile", + "args": { + "-TemplatePath": "Простой2Строки.xml", + "-OutputPath": "back.json" + }, + "cwd": "{workDir}" + } + ], + "params": { + "outputPath": "out.xml" + }, + "noSnapshot": "эталон — файл платформы, сверяется expect.filesEqual", + "expect": { + "filesEqual": { + "actual": "out.xml", + "expected": "Простой2Строки.xml" + } + }, + "inputFrom": "back.json" +} \ No newline at end of file diff --git a/tests/skills/runner.mjs b/tests/skills/runner.mjs index a3b4f29a..860dd237 100644 --- a/tests/skills/runner.mjs +++ b/tests/skills/runner.mjs @@ -275,7 +275,10 @@ function buildArgs(skillConfig, caseData, workDir, inputFilePath, runtime) { switch (mapping.from) { case 'inputFile': - args.push(inputFilePath); + // inputFrom: взять вход из файла в workDir, а не из case.input. Нужно, когда вход + // производит preRun (например, декомпилятор) — case.input пишется ПОСЛЕ preRun и + // затёр бы его. + args.push(caseData.inputFrom ? join(workDir, caseData.inputFrom) : inputFilePath); break; case 'workDir': args.push(workDir); @@ -407,7 +410,7 @@ function checkFileContains(workDir, spec, expectPresent) { // поэтому он ошибка, а не игнор. const KNOWN_EXPECT_KEYS = new Set([ 'files', 'stdoutContains', 'stdoutNotContains', 'preserves', - 'fileContains', 'fileNotContains', + 'fileContains', 'fileNotContains', 'filesEqual', ]); function checkExpectKeys(caseData) { @@ -416,6 +419,29 @@ function checkExpectKeys(caseData) { return unknown.map(k => `expect.${k}: раннер такого ключа не знает — кейс ничего не проверяет`); } +// ─── Побайтовое равенство двух файлов ─────────────────────────────────────── +// Нужно там, где эталон — не наш снэпшот, а файл, произведённый ПЛАТФОРМОЙ: снэпшот +// такую проверку не заменяет, потому что --update-snapshots молча принял бы дрейф. +// spec: { actual, expected }. Пути относительно workDir. +function checkFilesEqual(workDir, spec) { + const errs = []; + const a = join(workDir, spec.actual); + const b = join(workDir, spec.expected); + if (!existsSync(a)) { errs.push(`filesEqual: нет файла ${spec.actual}`); return errs; } + if (!existsSync(b)) { errs.push(`filesEqual: нет файла ${spec.expected}`); return errs; } + const bufA = readFileSync(a); + const bufB = readFileSync(b); + if (bufA.equals(bufB)) return errs; + const linesA = bufA.toString('utf8').split('\n'); + const linesB = bufB.toString('utf8').split('\n'); + let i = 0; + while (i < linesA.length && i < linesB.length && linesA[i] === linesB[i]) i++; + errs.push(`filesEqual: ${spec.actual} != ${spec.expected}, первое расхождение в строке ${i + 1}` + + `\n ожидалось: ${(linesB[i] ?? '<конец файла>').trim()}` + + `\n получено: ${(linesA[i] ?? '<конец файла>').trim()}`); + return errs; +} + // ─── Byte-style preservation check (round-trip #44/#46/#47, канон #57) ────── // Проверяет СЫРЫЕ байты файла (в обход normalizeContent): BOM / EOL / регистр // encoding / финальный перенос / отсутствие / форма пустого элемента. @@ -806,6 +832,11 @@ async function runCaseAsync(testCase, opts) { ? caseData.expect.preserves : [caseData.expect.preserves]; for (const spec of specs) errors.push(...checkPreserves(workDir, spec)); } + if (caseData.expect?.filesEqual) { + const specs = Array.isArray(caseData.expect.filesEqual) + ? caseData.expect.filesEqual : [caseData.expect.filesEqual]; + for (const spec of specs) errors.push(...checkFilesEqual(workDir, spec)); + } if (caseData.expect?.fileContains) { const specs = Array.isArray(caseData.expect.fileContains) ? caseData.expect.fileContains : [caseData.expect.fileContains];