diff --git a/tests/skills/README.md b/tests/skills/README.md
index e43d8154..3f86199c 100644
--- a/tests/skills/README.md
+++ b/tests/skills/README.md
@@ -274,7 +274,7 @@ ibcmd-проход автоматически `○ skipped`, если рядом
| `noSnapshot` | нет | Непустая строка с причиной — кейс объявляет, что эталон не нужен (см. «Эталоны») |
| `idempotent` | нет | `true` — повторный прогон с теми же аргументами должен дать байт-в-байт тот же `workDir` |
| `runtimeOnly` | нет | `"powershell"` / `"python"` — кейс имеет смысл только на одном порте, на другом `○ skipped` |
-| `osOnly` | нет | `"win32"` / `"darwin"` / `"linux"` — кейс работает только на этой ОС (напр. фейк платформы написан как `.cmd`), на других `○ skipped` |
+| `osOnly` | нет | `"win32"` / `"darwin"` / `"linux"` — кейс работает только на этой ОС (напр. фейк платформы написан как `.cmd`), на других `○ skipped`. Принимает и массив: `["darwin","linux"]` |
| `cwd` | нет | `"workDir"` — запустить навык из рабочего каталога (нужно, если кейс кладёт туда `.v8-project.json`) |
| `skipValidation` | нет | `true` — не запускать `postValidate` из `_skill.json` (только при `--with-validation`) |
| `skipPlatformVerify` | нет | Непустая строка с причиной — `verify-snapshots.mjs` пропускает кейс. Нужен, когда фикстура не является конфигурацией, которую можно загрузить (напр. фрагмент расширения) |
@@ -318,7 +318,7 @@ ibcmd-проход автоматически `○ skipped`, если рядом
| Форма шага | Описание |
|---|---|
| `{ "script": "<навык>/scripts/<файл>", "input": {...}, "args": { "-Flag": "{inputFile}" } }` | Прогон другого навыка для подготовки фикстуры. Плейсхолдеры: `{inputFile}`, `{workDir}` |
-| `{ "writeFile": { "path": "<путь>", "content": "<строка или объект>" } }` | Записать произвольный файл в `workDir` (объект сериализуется в JSON) |
+| `{ "writeFile": { "path": "<путь>", "content": "<строка или объект>" } }` | Записать произвольный файл в `workDir` (объект сериализуется в JSON). Ключ `"executable": true` ставит бит исполнения — нужен фейку платформы на *nix, где навык запускает её через `exec`; на Windows no-op |
Дополнительно у шага-скрипта: `"cwd": "{workDir}"` — запустить шаг из рабочего каталога. Нужно,
когда относительный путь в `args` (напр. `-OutputPath: "Template.xml"`) должен лечь в фикстуру, а
diff --git a/tests/skills/cases/db-load-xml/clean-log-no-warning-posix.json b/tests/skills/cases/db-load-xml/clean-log-no-warning-posix.json
new file mode 100644
index 00000000..81726892
--- /dev/null
+++ b/tests/skills/cases/db-load-xml/clean-log-no-warning-posix.json
@@ -0,0 +1,47 @@
+{
+ "name": "Чистый лог на *nix: предупреждения нет",
+ "osOnly": [
+ "darwin",
+ "linux"
+ ],
+ "runtimeOnly": "python",
+ "preRun": [
+ {
+ "writeFile": {
+ "path": "fake.sh",
+ "content": "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then OUT=\"$2\"; fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 0\n",
+ "executable": true
+ }
+ },
+ {
+ "writeFile": {
+ "path": "log.txt",
+ "content": "Загрузка конфигурации из файлов.\r\nЗагрузка завершена. Суммарное время выполнения операции: 780 миллисекунд.\r\n"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "ib/1Cv8.1CD",
+ "content": "stub"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "src/Configuration.xml",
+ "content": "\n"
+ }
+ }
+ ],
+ "args_extra": [
+ "-V8Path",
+ "{workDir}/fake.sh",
+ "-InfoBasePath",
+ "{workDir}/ib",
+ "-ConfigDir",
+ "{workDir}/src"
+ ],
+ "expect": {
+ "stdoutNotContains": "[warning] platform reported success, but the log contains"
+ },
+ "noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
+}
diff --git a/tests/skills/cases/db-load-xml/compat-mode-above-platform-posix.json b/tests/skills/cases/db-load-xml/compat-mode-above-platform-posix.json
new file mode 100644
index 00000000..b191eb3e
--- /dev/null
+++ b/tests/skills/cases/db-load-xml/compat-mode-above-platform-posix.json
@@ -0,0 +1,50 @@
+{
+ "name": "Режим совместимости выше платформы на *nix: классифицируется как отказ (issue #63)",
+ "osOnly": [
+ "darwin",
+ "linux"
+ ],
+ "runtimeOnly": "python",
+ "preRun": [
+ {
+ "writeFile": {
+ "path": "fake.sh",
+ "content": "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then OUT=\"$2\"; fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 0\n",
+ "executable": true
+ }
+ },
+ {
+ "writeFile": {
+ "path": "log.txt",
+ "content": "Для работы с конфигурацией необходима версия платформы не меньше, чем 8.3.27.\r\nТекущая версия 8.3.24.1691.\r\n"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "ib/1Cv8.1CD",
+ "content": "stub"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "src/Configuration.xml",
+ "content": "\n"
+ }
+ }
+ ],
+ "args_extra": [
+ "-V8Path",
+ "{workDir}/fake.sh",
+ "-InfoBasePath",
+ "{workDir}/ib",
+ "-ConfigDir",
+ "{workDir}/src"
+ ],
+ "expect": {
+ "stdoutContains": [
+ "[warning] platform reported success, but the log contains 1 problem(s):",
+ "необходима версия платформы не меньше"
+ ]
+ },
+ "noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
+}
diff --git a/tests/skills/cases/db-load-xml/silent-rejection-detected-posix.json b/tests/skills/cases/db-load-xml/silent-rejection-detected-posix.json
new file mode 100644
index 00000000..a6552abe
--- /dev/null
+++ b/tests/skills/cases/db-load-xml/silent-rejection-detected-posix.json
@@ -0,0 +1,51 @@
+{
+ "name": "Тихий отказ в логе на *nix: py-порт предупреждает (fake platform)",
+ "osOnly": [
+ "darwin",
+ "linux"
+ ],
+ "runtimeOnly": "python",
+ "preRun": [
+ {
+ "writeFile": {
+ "path": "fake.sh",
+ "content": "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then OUT=\"$2\"; fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 0\n",
+ "executable": true
+ }
+ },
+ {
+ "writeFile": {
+ "path": "log.txt",
+ "content": "Загрузка конфигурации из файлов.\r\nНеверное свойство объекта метаданных. Свойство LineNumberLength не входит в состав объекта метаданных TabularSection. - LineNumberLength\r\nЗагрузка завершена.\r\n"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "ib/1Cv8.1CD",
+ "content": "stub"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "src/Configuration.xml",
+ "content": "\n"
+ }
+ }
+ ],
+ "args_extra": [
+ "-V8Path",
+ "{workDir}/fake.sh",
+ "-InfoBasePath",
+ "{workDir}/ib",
+ "-ConfigDir",
+ "{workDir}/src"
+ ],
+ "expect": {
+ "stdoutContains": [
+ "[warning] platform reported success, but the log contains 1 problem(s):",
+ "Свойство LineNumberLength не входит в состав объекта метаданных"
+ ],
+ "stdoutNotContains": "-StrictLog"
+ },
+ "noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
+}
diff --git a/tests/skills/cases/db-load-xml/silent-rejection-strict-posix.json b/tests/skills/cases/db-load-xml/silent-rejection-strict-posix.json
new file mode 100644
index 00000000..32ebf37f
--- /dev/null
+++ b/tests/skills/cases/db-load-xml/silent-rejection-strict-posix.json
@@ -0,0 +1,49 @@
+{
+ "name": "Тот же лог под -StrictLog на *nix: код возврата 1 (fake platform)",
+ "osOnly": [
+ "darwin",
+ "linux"
+ ],
+ "runtimeOnly": "python",
+ "preRun": [
+ {
+ "writeFile": {
+ "path": "fake.sh",
+ "content": "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then OUT=\"$2\"; fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 0\n",
+ "executable": true
+ }
+ },
+ {
+ "writeFile": {
+ "path": "log.txt",
+ "content": "Загрузка конфигурации из файлов.\r\nНеверное свойство объекта метаданных. Свойство LineNumberLength не входит в состав объекта метаданных TabularSection. - LineNumberLength\r\nЗагрузка завершена.\r\n"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "ib/1Cv8.1CD",
+ "content": "stub"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "src/Configuration.xml",
+ "content": "\n"
+ }
+ }
+ ],
+ "args_extra": [
+ "-V8Path",
+ "{workDir}/fake.sh",
+ "-InfoBasePath",
+ "{workDir}/ib",
+ "-ConfigDir",
+ "{workDir}/src",
+ "-StrictLog"
+ ],
+ "expect": {
+ "stdoutContains": "[warning] platform reported success, but the log contains 1 problem(s):"
+ },
+ "noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы",
+ "expectError": true
+}
diff --git a/tests/skills/cases/db-update/clean-log-no-warning-posix.json b/tests/skills/cases/db-update/clean-log-no-warning-posix.json
new file mode 100644
index 00000000..b3d02cc8
--- /dev/null
+++ b/tests/skills/cases/db-update/clean-log-no-warning-posix.json
@@ -0,0 +1,39 @@
+{
+ "name": "Чистый лог на *nix: предупреждения нет",
+ "osOnly": [
+ "darwin",
+ "linux"
+ ],
+ "runtimeOnly": "python",
+ "preRun": [
+ {
+ "writeFile": {
+ "path": "fake.sh",
+ "content": "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then OUT=\"$2\"; fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 0\n",
+ "executable": true
+ }
+ },
+ {
+ "writeFile": {
+ "path": "log.txt",
+ "content": "Загрузка конфигурации из файлов.\r\nЗагрузка завершена. Суммарное время выполнения операции: 780 миллисекунд.\r\n"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "ib/1Cv8.1CD",
+ "content": "stub"
+ }
+ }
+ ],
+ "args_extra": [
+ "-V8Path",
+ "{workDir}/fake.sh",
+ "-InfoBasePath",
+ "{workDir}/ib"
+ ],
+ "expect": {
+ "stdoutNotContains": "[warning] platform reported success, but the log contains"
+ },
+ "noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
+}
diff --git a/tests/skills/cases/db-update/compat-mode-above-platform-posix.json b/tests/skills/cases/db-update/compat-mode-above-platform-posix.json
new file mode 100644
index 00000000..6251b023
--- /dev/null
+++ b/tests/skills/cases/db-update/compat-mode-above-platform-posix.json
@@ -0,0 +1,42 @@
+{
+ "name": "Режим совместимости выше платформы на *nix: классифицируется как отказ (issue #63)",
+ "osOnly": [
+ "darwin",
+ "linux"
+ ],
+ "runtimeOnly": "python",
+ "preRun": [
+ {
+ "writeFile": {
+ "path": "fake.sh",
+ "content": "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then OUT=\"$2\"; fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 0\n",
+ "executable": true
+ }
+ },
+ {
+ "writeFile": {
+ "path": "log.txt",
+ "content": "Для работы с конфигурацией необходима версия платформы не меньше, чем 8.3.27.\r\nТекущая версия 8.3.24.1691.\r\n"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "ib/1Cv8.1CD",
+ "content": "stub"
+ }
+ }
+ ],
+ "args_extra": [
+ "-V8Path",
+ "{workDir}/fake.sh",
+ "-InfoBasePath",
+ "{workDir}/ib"
+ ],
+ "expect": {
+ "stdoutContains": [
+ "[warning] platform reported success, but the log contains 1 problem(s):",
+ "необходима версия платформы не меньше"
+ ]
+ },
+ "noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
+}
diff --git a/tests/skills/cases/db-update/silent-rejection-detected-posix.json b/tests/skills/cases/db-update/silent-rejection-detected-posix.json
new file mode 100644
index 00000000..c04c4b89
--- /dev/null
+++ b/tests/skills/cases/db-update/silent-rejection-detected-posix.json
@@ -0,0 +1,43 @@
+{
+ "name": "Тихий отказ в логе на *nix: py-порт предупреждает (fake platform)",
+ "osOnly": [
+ "darwin",
+ "linux"
+ ],
+ "runtimeOnly": "python",
+ "preRun": [
+ {
+ "writeFile": {
+ "path": "fake.sh",
+ "content": "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then OUT=\"$2\"; fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 0\n",
+ "executable": true
+ }
+ },
+ {
+ "writeFile": {
+ "path": "log.txt",
+ "content": "Загрузка конфигурации из файлов.\r\nНеверное свойство объекта метаданных. Свойство LineNumberLength не входит в состав объекта метаданных TabularSection. - LineNumberLength\r\nЗагрузка завершена.\r\n"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "ib/1Cv8.1CD",
+ "content": "stub"
+ }
+ }
+ ],
+ "args_extra": [
+ "-V8Path",
+ "{workDir}/fake.sh",
+ "-InfoBasePath",
+ "{workDir}/ib"
+ ],
+ "expect": {
+ "stdoutContains": [
+ "[warning] platform reported success, but the log contains 1 problem(s):",
+ "Свойство LineNumberLength не входит в состав объекта метаданных"
+ ],
+ "stdoutNotContains": "-StrictLog"
+ },
+ "noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
+}
diff --git a/tests/skills/cases/db-update/silent-rejection-strict-posix.json b/tests/skills/cases/db-update/silent-rejection-strict-posix.json
new file mode 100644
index 00000000..b33e6394
--- /dev/null
+++ b/tests/skills/cases/db-update/silent-rejection-strict-posix.json
@@ -0,0 +1,41 @@
+{
+ "name": "Тот же лог под -StrictLog на *nix: код возврата 1 (fake platform)",
+ "osOnly": [
+ "darwin",
+ "linux"
+ ],
+ "runtimeOnly": "python",
+ "preRun": [
+ {
+ "writeFile": {
+ "path": "fake.sh",
+ "content": "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then OUT=\"$2\"; fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 0\n",
+ "executable": true
+ }
+ },
+ {
+ "writeFile": {
+ "path": "log.txt",
+ "content": "Загрузка конфигурации из файлов.\r\nНеверное свойство объекта метаданных. Свойство LineNumberLength не входит в состав объекта метаданных TabularSection. - LineNumberLength\r\nЗагрузка завершена.\r\n"
+ }
+ },
+ {
+ "writeFile": {
+ "path": "ib/1Cv8.1CD",
+ "content": "stub"
+ }
+ }
+ ],
+ "args_extra": [
+ "-V8Path",
+ "{workDir}/fake.sh",
+ "-InfoBasePath",
+ "{workDir}/ib",
+ "-StrictLog"
+ ],
+ "expect": {
+ "stdoutContains": "[warning] platform reported success, but the log contains 1 problem(s):"
+ },
+ "noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы",
+ "expectError": true
+}
diff --git a/tests/skills/runner.mjs b/tests/skills/runner.mjs
index 194e384b..b1f3ba2b 100644
--- a/tests/skills/runner.mjs
+++ b/tests/skills/runner.mjs
@@ -4,7 +4,7 @@
import { execFileSync, execFile } from 'child_process';
import { existsSync, mkdirSync, mkdtempSync, rmSync, readFileSync, writeFileSync,
- readdirSync, statSync, cpSync, copyFileSync } from 'fs';
+ readdirSync, statSync, cpSync, copyFileSync, chmodSync } from 'fs';
import { createHash } from 'crypto';
import { join, resolve, dirname, relative, basename, extname } from 'path';
import { tmpdir, cpus } from 'os';
@@ -713,7 +713,9 @@ async function runCaseAsync(testCase, opts) {
// osOnly: gate a case to one OS (e.g. a fake platform written as a .cmd cannot run on
// macOS/Linux at all, whatever the port). Values are process.platform strings.
- if (caseData.osOnly && caseData.osOnly !== process.platform) {
+ // Значение — строка или массив строк process.platform: фейк платформы бывает нужен и на
+ // darwin, и на linux, а дублировать кейс ради второй ОС смысла нет.
+ if (caseData.osOnly && ![].concat(caseData.osOnly).includes(process.platform)) {
return { id: testCase.id, skill: testCase.skillDir, name: testCase.name, passed: true, skipped: true, errors: [], elapsed: '0.0s' };
}
@@ -745,6 +747,9 @@ async function runCaseAsync(testCase, opts) {
: JSON.stringify(step.writeFile.content, null, 2);
mkdirSync(dirname(wfPath), { recursive: true });
writeFileSync(wfPath, wfContent, 'utf8');
+ // Бит исполнения: на *nix навык запускает платформу через exec, и фейк без +x
+ // не стартует вовсе. На Windows chmod — no-op.
+ if (step.writeFile.executable) chmodSync(wfPath, 0o755);
continue;
}
const preScript = resolveScript(step.script, opts.runtime);
@@ -1363,6 +1368,9 @@ async function runIntegrationOnce(test, opts, engine, labelEngine) {
const abs = target.includes(':') || target.startsWith('/') ? target : join(workDir, target);
mkdirSync(dirname(abs), { recursive: true });
writeFileSync(abs, step.content ?? '', 'utf8');
+ // Бит исполнения: на *nix навык запускает платформу через exec, и фейк без +x
+ // не стартует вовсе. На Windows chmod — no-op.
+ if (step.executable) chmodSync(abs, 0o755);
const stepElapsed = ((performance.now() - stepT0) / 1000).toFixed(1);
stepResults.push({ name: step.name, passed: true, elapsed: `${stepElapsed}s` });
} catch (e) {
diff --git a/tests/skills/verify-snapshots.mjs b/tests/skills/verify-snapshots.mjs
index 0017b394..bb4831ff 100644
--- a/tests/skills/verify-snapshots.mjs
+++ b/tests/skills/verify-snapshots.mjs
@@ -13,7 +13,7 @@
import { execFileSync } from 'child_process';
import { existsSync, mkdirSync, mkdtempSync, rmSync, readFileSync, writeFileSync,
- readdirSync, statSync, cpSync, copyFileSync } from 'fs';
+ readdirSync, statSync, cpSync, copyFileSync, chmodSync } from 'fs';
import { join, resolve, dirname, basename } from 'path';
import { tmpdir } from 'os';
@@ -627,6 +627,9 @@ function runPreSteps(preRun, workDir, runtime, log) {
: JSON.stringify(step.writeFile.content, null, 2);
mkdirSync(dirname(wfPath), { recursive: true });
writeFileSync(wfPath, wfContent, 'utf8');
+ // Бит исполнения: на *nix навык запускает платформу через exec, и фейк без +x
+ // не стартует вовсе. На Windows chmod — no-op.
+ if (step.writeFile.executable) chmodSync(wfPath, 0o755);
log(`preRun: writeFile ${step.writeFile.path}`, true);
continue;
}