From 53879f909cea9a825432e8d2c8bcb90ee3ab08c8 Mon Sep 17 00:00:00 2001 From: sfaqer Date: Wed, 15 Jul 2026 21:16:41 +1000 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B8=D0=B7=D0=BE=D0=BB=D1=8F=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2=20=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D1=86=D0=B8=D0=B5=D0=B9=20&=D0=98=D0=B7?= =?UTF-8?q?=D0=BE=D0=BB=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Тест или весь набор выполняется в изоляции - свежей поделке или отдельном дочернем процессе - как будто набор прогнали только с этим тестом: - уровни "Поделка" и "Процесс", гранулярность набора и отдельного теста; - совет создания изолированной поделки лямбдой в контексте набора, на уровне процесса через границу переезжает только текст лямбды; - отложенные импорты уровня "Процесс" для библиотек, ядовитых уже при подключении; - сдерживание отказов: падение изолированной среды ломает только набор, с причиной, выводом дочернего прогона и без служебных стеков; - обнаружение (discover, MCP) показывает фактический состав изолированных наборов, разворачивая их в собственных средах без исполнения; - покрытие кода включает изолированные процессные прогоны: статистика дочерних процессов складывается в общий каталог и мержится в отчеты. Попутно обнаружитель перестал разворачивать наборы чужих поделок процесса (аннотированные типы глобальны, прогоны в свежих поделках зацикливались) и исправлена вклейка контейнеров изолированного набора. Co-Authored-By: Claude Fable 5 --- .bsl-language-server.json | 3 +- README.md | 61 + ...20\260\320\275\320\275\321\213\320\271.md" | 49 + lib.config | 1 + ...20\265\320\261\320\275\321\213\320\271.os" | 314 ----- src/cli/main.os | 2 +- ...20\265\321\201\321\202\320\276\320\262.os" | 23 +- ...20\262\320\260\320\275\320\270\320\265.os" | 11 +- ...20\273\321\217\321\206\320\270\320\270.os" | 1155 +++++++++++++++++ ...20\265\321\201\321\202\320\276\320\262.os" | 121 +- ...20\260\320\261\320\276\321\200\320\260.os" | 15 +- ...20\242\320\265\321\201\321\202\320\260.os" | 15 +- ...76\321\200\321\202\320\265\321\200JSON.os" | 54 +- ...00\320\237\320\273\320\260\320\275JSON.os" | 62 + ...20\273\321\217\321\206\320\270\320\270.os" | 5 + ...20\260\320\275\320\275\321\213\320\271.os" | 45 + ...20\262\320\260\320\275\320\270\321\217.os" | 9 +- ...20\265\320\261\320\275\321\213\320\271.os" | 41 +- ...20\262\320\260\320\275\320\270\321\217.os" | 152 ++- ...20\222\321\205\320\276\320\264\320\260.os" | 504 +++++++ ...20\265\320\261\320\275\321\213\320\271.os" | 59 + ...20\274\320\265\320\275\321\202\321\213.os" | 4 +- ...21\217\321\206\320\270\320\265\320\271.os" | 7 + ...20\235\320\260\320\261\320\276\321\200.os" | 13 + ...20\235\320\260\320\261\320\276\321\200.os" | 16 + ...20\265\321\201\321\202\320\276\320\274.os" | 14 + ...20\262\320\265\321\202\320\276\320\274.os" | 14 + ...20\265\321\202\320\276\320\264\320\260.os" | 27 + ...20\235\320\260\320\261\320\276\321\200.os" | 12 + ...20\235\320\260\320\261\320\276\321\200.os" | 19 + ...20\235\320\260\320\261\320\276\321\200.os" | 11 + ...20\235\320\260\320\261\320\276\321\200.os" | 13 + ...20\235\320\260\320\261\320\276\321\200.os" | 16 + ...20\235\320\260\320\261\320\276\321\200.os" | 29 + ...21\203\321\201\320\272\320\276\320\274.os" | 20 + ...20\235\320\260\320\261\320\276\321\200.os" | 14 + ...20\235\320\260\320\261\320\276\321\200.os" | 15 + ...20\235\320\260\320\261\320\276\321\200.os" | 15 + ...20\273\321\217\321\206\320\270\320\270.os" | 46 + ...20\273\321\217\321\206\320\270\320\270.os" | 34 + ...20\273\321\217\321\206\320\270\320\270.os" | 19 + ...20\276\320\262\320\265\321\202\320\260.os" | 22 + ...21\201\321\202\320\260\320\274\320\270.os" | 37 + ...20\265\321\201\321\202\320\276\320\274.os" | 12 + ...20\265\321\201\321\202\320\276\320\274.os" | 14 + ...0\265\320\273\321\203\320\264\321\2141.os" | 3 + ...0\265\320\273\321\203\320\264\321\2142.os" | 3 + ...20\273\321\217\321\206\320\270\321\217.os" | 506 ++++++++ ...20\266\320\265\320\275\320\270\320\265.os" | 16 +- 49 files changed, 3253 insertions(+), 419 deletions(-) create mode 100644 "docs/api/\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\320\270/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271.md" create mode 100644 "src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" create mode 100644 "src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\265\320\277\320\276\321\200\321\202\320\265\321\200\320\237\320\273\320\260\320\275JSON.os" create mode 100644 "src/core/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\243\321\200\320\276\320\262\320\275\320\270\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" create mode 100644 "src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\321\217\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271.os" rename "src/cli/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" => "src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" (55%) create mode 100644 "src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\201\321\202\321\200\320\276\320\270\321\202\320\265\320\273\321\214\320\242\320\276\321\207\320\272\320\270\320\222\321\205\320\276\320\264\320\260.os" create mode 100644 "tests/fixtures/broken/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\262\320\265\321\200\320\275\320\276\320\271\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\265\320\271.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\221\320\270\321\202\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\227\320\265\320\273\320\265\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\203\321\201\321\202\321\213\320\274\320\241\320\276\320\262\320\265\321\202\320\276\320\274.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\241\320\276\320\262\320\265\321\202\320\276\320\274\320\230\320\267\320\234\320\265\321\202\320\276\320\264\320\260.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\265\320\262\320\270\320\264\320\270\320\274\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\236\320\261\321\212\320\265\320\272\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\320\276\320\264\320\265\320\273\320\276\321\207\320\275\321\213\320\271\320\232\321\200\320\260\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\221\320\270\321\202\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\232\321\200\320\260\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\276\320\277\321\203\321\201\320\272\320\276\320\274.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\241\320\276\320\262\320\265\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\241\320\276\320\262\320\265\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" create mode 100644 "tests/fixtures/isolation/\320\234\320\276\320\264\321\203\320\273\320\270/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" create mode 100644 "tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\237\320\260\320\264\320\260\321\216\321\211\320\270\320\271\320\222\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" create mode 100644 "tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\237\321\200\320\276\320\277\321\203\321\201\320\272\320\260\320\265\320\274\321\213\320\271\320\222\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" create mode 100644 "tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\221\320\270\321\202\320\276\320\271\320\233\321\217\320\274\320\261\320\264\320\276\320\271\320\241\320\276\320\262\320\265\321\202\320\260.os" create mode 100644 "tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\274\320\270\320\242\320\265\321\201\321\202\320\260\320\274\320\270.os" create mode 100644 "tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\262\320\270\320\264\320\270\320\274\321\213\320\274\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" create mode 100644 "tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\274\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" create mode 100644 "tests/fixtures/isolation/\320\236\321\210\320\270\320\261\320\276\321\207\320\275\321\213\320\265\320\232\320\273\320\260\321\201\321\201\321\213/\320\224\320\262\320\260\320\226\320\265\320\273\321\203\320\264\321\217\320\241\320\236\320\264\320\275\320\270\320\274\320\230\320\274\320\265\320\275\320\265\320\274/\320\232\320\273\320\260\321\201\321\201\321\213/\320\226\320\265\320\273\321\203\320\264\321\2141.os" create mode 100644 "tests/fixtures/isolation/\320\236\321\210\320\270\320\261\320\276\321\207\320\275\321\213\320\265\320\232\320\273\320\260\321\201\321\201\321\213/\320\224\320\262\320\260\320\226\320\265\320\273\321\203\320\264\321\217\320\241\320\236\320\264\320\275\320\270\320\274\320\230\320\274\320\265\320\275\320\265\320\274/\320\232\320\273\320\260\321\201\321\201\321\213/\320\226\320\265\320\273\321\203\320\264\321\2142.os" create mode 100644 "tests/\320\230\320\267\320\276\320\273\321\217\321\206\320\270\321\217.os" diff --git a/.bsl-language-server.json b/.bsl-language-server.json index f1178bf..8cd61fa 100644 --- a/.bsl-language-server.json +++ b/.bsl-language-server.json @@ -8,7 +8,8 @@ "runAllTestsArguments": "./src/cli/main.os e --file %s --genericCoverage build/genericCoverage.xml --openTestReport build/otr.xml --cobertura build/cobertura.xml", "runTestArguments": "./src/cli/main.os e --file %s -m %s --genericCoverage build/genericCoverage.xml --openTestReport build/otr.xml --cobertura build/cobertura.xml", "debugTestArguments": "./src/cli/main.os e --debug 2801 --file %s -m %s", - "getTestsResultPattern": "^[^.]+\\.([^\\s.]+).*$" + "getTestsResultPattern": "^[^.]+\\.([^\\s.]+).*$", + "getTestsByTestRunner": true } }, "inlayHint": { diff --git a/README.md b/README.md index 17a1dc0..9c8dae4 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ OneUnit поддерживает следующие аннотации для н | `&Тег` | Используется для объявления [тегов для фильтрации](#17-теги) тестов на уровне набора или метода | | `&Выключен` | Используется для [отключения](#15-отключение-тестов) тестового набора или тестового метода | | `&Таймаут` | Используется для провала `&Тест`, `&ПараметризованныйТест` или `&ПовторяемыйТест` метода, если его выполнение превышает заданную продолжительность. | +| `&Изолированный` | Выполняет тест или весь набор [в изоляции](#113-изоляция-тестов) - в свежей поделке или в отдельном процессе | ### 1.2 Тестовые методы и наборы @@ -565,6 +566,66 @@ OneUnit предоставляет возможность повторять т > Если таймаут указан для [`&ПовторяемыйТест`](./docs/api/Аннотации/ПовторяемыйТест.md) или [`&ПараметризованныйТест`](./docs/api/Аннотации/ПараметризованныйТест.md), то он будет применён к каждому вызову тестового метода. +### 1.13 Изоляция тестов + +Аннотация [`&Изолированный`](./docs/api/Аннотации/Изолированный.md) выполняет тест или весь тестовый набор в изоляции: так, как будто набор прогнали только с этим тестом. Снаружи это обычный тест - его статус, сообщение и вывод попадают в общий план, репортёры и статистика не знают об изоляции. + +Изоляция нужна тестам, которые иначе отравляют общий прогон: проверяют ошибки обнаружения, конфликты желудей, падающие импорты - либо требуют собственной поделки с отдельной конфигурацией. + +Аннотация вешается на тест или на конструктор `&ТестовыйНабор`. На наборе изолируется весь набор одним контекстом, на тесте - каждый такой тест своим. Изолированный контекст получает полный жизненный цикл набора, включая `&ПередВсеми` и `&ПослеВсех`. + +Уровень изоляции задаётся параметром `Уровень`: + +* `"Поделка"` (по умолчанию) - тот же процесс, но свежая поделка. Защищает от протечек состояния желудей между тестами; +* `"Процесс"` - отдельный дочерний процесс. Защищает от того, что поделкой не лечится: глобальная система типов, падающие импорты, `ЗавершитьРаботу`. + +```bsl +&Тест +&Изолированный +Процедура ТестВСвежейПоделке() Экспорт +``` + +Падение изолированной среды не роняет общий прогон: набор или тест помечается сломанным с причиной, а прогон продолжается. + +Параметр `Импорты` (только для уровня `"Процесс"`) переносит подключение библиотек из шапки файла в изолированный процесс. Импорт подключается в дочернем процессе после старта его поделки - поэтому библиотека, ломающая процесс уже при подключении, не мешает ни родительскому прогону, ни самому дочернему. Пути разрешаются относительно файла набора. + +```bsl +&Тест +&Изолированный(Уровень = "Процесс", Импорты = "./ОшибочныеКлассы/ДваЖелудяСОднимИменем") +Процедура ДваЖелудяСОднимИменемКонфликтуют() Экспорт + + Попытка + Поделка = Новый Поделка(); + Исключение + Ожидаем.Что(ИнформацияОбОшибке().Описание).Содержит("уже существует"); + КонецПопытки; + +КонецПроцедуры +``` + +Параметр `Совет` принимает [лямбда-выражение](https://github.com/oscript-library/lambdas) без параметров, возвращающее `СоветДругогоМастера` для создания изолированной поделки. Лямбда выполняется в контексте тестового набора - ей доступны его поля и экспортные методы. Так тесты получают собственную поделку с нужной конфигурацией без ручной сборки: + +```bsl +Перем _Поделка; + +&ТестовыйНабор +&Изолированный(Совет = "() -> ГенераторДанных.СоветСОднимКоннектором()") +Процедура ПриСозданииОбъекта(&Пластилин Поделка) + _Поделка = Поделка; +КонецПроцедуры + +&Тест +Процедура ХранилищеПолучается() Экспорт + Хранилище = _Поделка.НайтиЖелудь("ХранилищеСущностейОсновное"); + ... +``` + +Экземпляр изолированного набора создаётся в изолированной поделке, поэтому его конструктор инжектит желуди, сконфигурированные советом, включая саму поделку через `&Пластилин Поделка`. + +> Для уровня `"Процесс"` лямбда совета исполняется в дочернем процессе, поэтому значения деталек совета не ограничены сериализуемостью и могут быть объектами. Экземпляр набора для захвата поднимается там напрямую через `Новый`: если конструктор набора требует параметров, лямбда исполняется без захвата - модули, импортированные файлом набора, доступны в любом случае. + +Обнаружение тестов (`oneunit discover` и MCP-инструмент `discover_tests`) показывает фактический состав изолированных наборов: состав обнаруживается в изолированной среде без исполнения тестов, поэтому внешний тулинг видит их так же, как обычные наборы. + ## 2. Запуск тестов ### 2.1 Запуск тестов из командной строки diff --git "a/docs/api/\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\320\270/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271.md" "b/docs/api/\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\320\270/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271.md" new file mode 100644 index 0000000..5eaebff --- /dev/null +++ "b/docs/api/\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\320\270/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271.md" @@ -0,0 +1,49 @@ +# АннотацияИзолированный + +## ПриСозданииОбъекта + +```bsl +// Аннотация выполняет тест или весь тестовый набор в изоляции: так, как будто +// набор прогнали только с этим тестом - в свежей поделке или в отдельном процессе. +// Снаружи это обычный тест: его статус, сообщение и вывод попадают в общий план. +// +// Нужна для тестов, которые иначе отравляют общий прогон: проверяют ошибки +// обнаружения, конфликты желудей, падающие импорты, либо требуют собственной +// поделки с отдельной конфигурацией. +// +// Вешается на тест или на конструктор `&ТестовыйНабор`. На наборе изолируется весь +// набор одним контекстом, на тесте - каждый такой тест своим. При изоляции и набора, +// и теста выигрывает набор. +// +// Параметры: +// Уровень - Строка - Уровень изоляции, см. УровниИзоляции: +// "Поделка" (по умолчанию) - тот же процесс, но свежая поделка; +// "Процесс" - отдельный дочерний процесс. +// Импорты - Строка - Только для уровня "Процесс": пути импортов через запятую, +// подключаемые в изолированном процессе перед файлом набора. Позволяет проверять +// импорты, которые отравляют текущий процесс уже при подключении. На уровне +// "Поделка" запрещены - импорт глобален для процесса и поделкой не изолируется. +// Совет - Строка - [Лямбда-выражение](https://github.com/oscript-library/lambdas) +// без параметров, должно вернуть [СоветДругогоМастера](https://github.com/autumn-library/autumn) +// для создания изолированной поделки. Лямбда захватывает объект тестового набора: +// ей доступны поля объекта и его экспортные методы. На уровне "Процесс" лямбда +// исполняется в дочернем процессе, поэтому значения деталек совета не ограничены +// сериализуемостью; экземпляр набора для захвата поднимается там напрямую +// через Новый - если конструктор набора требует параметров, лямбда исполняется +// без захвата (модули, импортированные файлом набора, доступны в любом случае). +// +// Пример: +// 1. +// // Набор целиком выполняется в свежей поделке, сконфигурированной советом +// &ТестовыйНабор +// &Изолированный(Совет = "() -> ГенераторДанных.СоветСОднимКоннектором()") +// Процедура ПриСозданииОбъекта(&Пластилин Поделка) Экспорт +// +// 2. +// // Тест выполняется в отдельном процессе с ядовитым импортом +// &Тест +// &Изолированный(Уровень = "Процесс", Импорты = "../ОшибочныеКлассы/ДваЖелудяСОднимИменем") +// Процедура ДваЖелудяСОднимИменемКонфликтуют() Экспорт +// +Процедура ПриСозданииОбъекта(Уровень = "Поделка", Импорты = "", Совет = "") +``` diff --git a/lib.config b/lib.config index b963195..569d8e2 100644 --- a/lib.config +++ b/lib.config @@ -12,6 +12,7 @@ + diff --git "a/src/cli/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" "b/src/cli/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" index e8a4ae6..ad4956b 100644 --- "a/src/cli/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" +++ "b/src/cli/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" @@ -1,306 +1,5 @@ -#Использовать fs -#Использовать logos -#Использовать decorator -#Использовать fluent -#Использовать collectionos #Использовать "../../../shared" -Функция СоздатьТочкуВхода(Детальки, МенеджерВременныхФайлов, Лог, ВыполняемаяКоманда) Экспорт - - Если Детальки.Получить("КаталогиТестов").Количество() = 0 - И Детальки.Получить("ФайлыТестов").Количество() = 0 Тогда - Детальки.Получить("КаталогиТестов").Добавить("./tests"); - КонецЕсли; - - ФайлыТестовРазвернутые = ТестированиеСлужебный.РазвернутьСписокТестовыхНаборов( - Детальки.Получить("КаталогиТестов"), - Детальки.Получить("ФайлыТестов"), - Детальки.Получить("ИскатьВПодкаталогах") - ); - - Импорты = Новый КартаСоответствие(); - - ТекущийКаталог = ТекущийКаталог(); - - Для каждого Тест Из ФайлыТестовРазвернутые Цикл - - ЧтениеТекста = Новый ЧтениеТекста(Тест.ПолноеИмя); - - УстановитьТекущийКаталог(Тест.Путь); - - Строка = ЧтениеТекста.ПрочитатьСтроку(); - - Пока Строка <> Неопределено Цикл - - Если СтрНачинаетсяС(Строка, "#Использовать ") Тогда - - ПоПути = Ложь; - - Импорт = СтрЗаменить(Строка, "#Использовать ", ""); - - Если СокрЛП(НРег(Импорт)) = "oneunit" Тогда - Строка = ЧтениеТекста.ПрочитатьСтроку(); - Продолжить; - КонецЕсли; - - Если СтрНайти(Импорт, """") > 0 Тогда - Импорт = ФС.НормализоватьПуть(СтрЗаменить(Импорт, """", "")); - ПоПути = Истина; - КонецЕсли; - - Импорты.Вставить(Импорт, ПоПути); - - КонецЕсли; - - Строка = ЧтениеТекста.ПрочитатьСтроку(); - - КонецЦикла; - - ЧтениеТекста.Закрыть(); - - УстановитьТекущийКаталог(ТекущийКаталог); - - КонецЦикла; - - Лог.Отладка("Импорты считанные из тестов (Импорт=ПодключаетсяПоПути): %1", Импорты); - - КаталогПриложения = ФС.НормализоватьПуть( - ОбъединитьПути(ОбъединитьПути(ТекущийСценарий().Каталог, "..", "..", ".."), "core") - ); - - ТекстСценария = Новый ПостроительДекоратора() - .Импорт(Новый Импорт(КаталогПриложения).ТипПодключения(ТипыПодключенияБиблиотек.ПоПути)) - .ШагИнициализации( - Новый ШагИнициализации(СтрШаблон(" - | - | Поделка = Новый Поделка(Новый СоветДругогоМастера() - | .ДополнительныйКаталогПоискаФайлаСоЗначениямиДеталек(""%2"")); - | Поделка.ЗапуститьПриложение(); - | - | МенеджерТестирования = Поделка.НайтиЖелудь(""МенеджерТестирования""); - | - | %1 - |", - ВыполняемаяКоманда, - МенеджерВременныхФайлов.БазовыйКаталог - )) - ) - .ТекстСценария(); - - Лог.Отладка("Текст сценария запуска тестирования: %1", ТекстСценария); - - ИмяВременногоФайла = МенеджерВременныхФайлов.НовоеИмяФайла("os"); - ЗаписьТекста = Новый ЗаписьТекста(); - ЗаписьТекста.Открыть(ИмяВременногоФайла); - ЗаписьТекста.Записать(ТекстСценария); - ЗаписьТекста.Закрыть(); - - ТочкаВхода = Новый ПостроительДекоратора(); - - Импорты.ЗаменитьВсе( - "Импорт, ПоПути -> - | Результат = Новый Импорт("""" + Импорт + """"); - | Если ПоПути Тогда - | Результат.ТипПодключения(ТипыПодключенияБиблиотек.ПоПути); - | КонецЕсли; - | - | Возврат Результат; - |" - ); - - Импорты.Значения() - .ДляКаждого("Импорт -> ТочкаВхода.Импорт(Импорт)", Новый Структура("ТочкаВхода", ТочкаВхода)); - - ТекстСценария = ТочкаВхода - .ШагИнициализации( - Новый ШагИнициализации(СтрШаблон(" - | - | // После импортов из тестов загружаем ядро и запускаем тесты - | ЗагрузитьСценарий(""%1""); - | - |", - ИмяВременногоФайла - )) - ) - .ТекстСценария(); - - Лог.Отладка("Текст сценария точки входа для запуска тестирования: %1", ТекстСценария); - - ИмяВременногоФайла = МенеджерВременныхФайлов.НовоеИмяФайла("os"); - ЗаписьТекста = Новый ЗаписьТекста(); - ЗаписьТекста.Открыть(ИмяВременногоФайла); - ЗаписьТекста.Записать(ТекстСценария); - ЗаписьТекста.Закрыть(); - - КонфигурационныйФайл(Детальки, МенеджерВременныхФайлов, Лог); - ЗаписатьДетальки(Детальки, МенеджерВременныхФайлов); - - Возврат ИмяВременногоФайла; - -КонецФункции - -Функция ЗаписатьДетальки(Детальки, МенеджерВременныхФайлов) - - ЗначенияДеталек = Новый Соответствие; - - Для Каждого Деталька Из Детальки Цикл - - Если ТипЗнч(Деталька.Значение) = Тип("Массив") - Или ТипЗнч(Деталька.Значение) = Тип("ФиксированныйМассив") Тогда - - // СтрСоединить не принимает ФиксированныйМассив - Значение = СтрСоединить(Массивы.Скопировать(Деталька.Значение), ","); - - Иначе - Значение = Деталька.Значение; - КонецЕсли; - - ЗначенияДеталек.Вставить(Деталька.Ключ, Значение); - - КонецЦикла; - - ПутьКФайлуДеталек = ОбъединитьПути(МенеджерВременныхФайлов.БазовыйКаталог, "autumn-properties.json"); - - Настройки = Новый Соответствие(НастройкиЛогоса("core", "JSONРаскладкаСообщения")); - Настройки.Вставить("OneUnit", ЗначенияДеталек); - - ЗаписьJSON = Новый ЗаписьJSON(); - ЗаписьJSON.ОткрытьФайл(ПутьКФайлуДеталек); - ЗаписатьJSON(ЗаписьJSON, Настройки); - ЗаписьJSON.Закрыть(); - - Возврат ПутьКФайлуДеталек; - -КонецФункции - -Функция КонфигурационныйФайл(Детальки, МенеджерВременныхФайлов, Лог) - - ПутиККонфигурационнымФайлам = Новый СписокМассив; - ПутиККонфигурационнымФайлам.Добавить(ТекущийКаталог()); - - Для каждого Путь Из Детальки.Получить("КаталогиТестов") Цикл - ПутиККонфигурационнымФайлам.Добавить(Путь); - КонецЦикла; - - Для каждого Путь Из Детальки.Получить("ФайлыТестов") Цикл - ПутиККонфигурационнымФайлам.Добавить(Новый Файл(Путь).Путь); - КонецЦикла; - - ДанныеКонфигурационногоФайла = ПутиККонфигурационнымФайлам.ПроцессорКоллекции() - .Различные() - .Обработать("Путь -> ОбъединитьПути(Путь, ""oscript.cfg"")") - .Фильтровать("Путь -> ФС.ФайлСуществует(Путь)") - .Обработать(Новый Действие(ЭтотОбъект, "ПрочитатьКонфигурационныйФайл")) - .Развернуть("ДанныеФайла -> ДанныеФайла.КлючиИЗначения().ПроцессорКоллекции()") - .Сократить( - "Результат, Настройка -> - | - | Результат.Слить( - | Настройка.Ключ, - | Настройка.Значение, - | "" - | |Старое, Новое -> - | | Если ТипЗнч(Старое) = Тип(""""МножествоСоответствие"""") Тогда - | | Старое.ДобавитьВсе(Новое); - | | КонецЕсли; - | | - | | Возврат Старое; - | |"" - | ); - | - | Возврат Результат; - |", - Новый КартаСоответствие() - ); - - ДанныеКонфигурационногоФайла.Слить( - "lib.additional", - Новый МножествоСоответствие( - Множества.ИзЭлементов(ПолучитьЗначениеСистемнойНастройки("lib.additional")) - ), - "Старое, Новое -> Старое.ДобавитьВсе(Новое); Возврат Старое;" - ); - - ДанныеКонфигурационногоФайла.ВычислитьЕслиПрисутствует( - "lib.additional", - "Ключ, Значение -> Значение.Удалить(СистемныйКаталог); Возврат Значение;", - Новый Структура("СистемныйКаталог", ДанныеКонфигурационногоФайла.Получить("lib.system").Иначе_("")) - ); - - Лог.Отладка("Данные конфигурационного файла: %1", ДанныеКонфигурационногоФайла); - - ПутьККонфигурационномуФайлу = ОбъединитьПути(МенеджерВременныхФайлов.БазовыйКаталог, "oscript.cfg"); - - ЗаписьТекста = Новый ЗаписьТекста(); - ЗаписьТекста.Открыть(ПутьККонфигурационномуФайлу); - - ДанныеКонфигурационногоФайла.ДляКаждого( - "Ключ, Значение -> ЗаписьТекста.ЗаписатьСтроку( - | Ключ + ""="" + - | ?(ТипЗнч(Значение) = Тип(""МножествоСоответствие"") Или ТипЗнч(Значение) = Тип(""ФиксированноеМножество""), - | СтрСоединить(Значение.ВМассив(), "";""), - | Значение - | ));", - Новый Структура("ЗаписьТекста", ЗаписьТекста) - ); - - ЗаписьТекста.Закрыть(); - - Возврат ПутьККонфигурационномуФайлу; - -КонецФункции - -Функция ПрочитатьКонфигурационныйФайл(Путь) Экспорт - - ТекущийКаталог = ТекущийКаталог(); - - Результат = Новый КартаСоответствие(); - - ЧтениеТекста = Новый ЧтениеТекста(Путь); - - ПрочитаннаяСтрока = ЧтениеТекста.ПрочитатьСтроку(); - - Пока ПрочитаннаяСтрока <> Неопределено Цикл - - ПрочитаннаяСтрока = СтрРазделить(ПрочитаннаяСтрока, "="); - - Ключ = ПрочитаннаяСтрока[0]; - - УстановитьТекущийКаталог(Новый Файл(Путь).Путь); - - Если СтрНачинаетсяС(Ключ, "lib.") Тогда - - Если СтрНачинаетсяС(Ключ, "lib.additional") Тогда - - Значение = ПроцессорыКоллекций.ИзКоллекции(СтрРазделить(ПрочитаннаяСтрока[1], ";", Ложь)) - .Фильтровать("Путь -> ФС.КаталогСуществует(Путь)") - .Обработать("Путь -> ФС.НормализоватьПуть(Путь)") - .Получить("МножествоСоответствие"); - - Иначе - Значение = ?(ФС.КаталогСуществует(ПрочитаннаяСтрока[1]), ФС.НормализоватьПуть(ПрочитаннаяСтрока[1]), ""); - КонецЕсли; - - Иначе - Значение = ПрочитаннаяСтрока[1]; - КонецЕсли; - - УстановитьТекущийКаталог(ТекущийКаталог); - - Если ЗначениеЗаполнено(Значение) Тогда - Результат.Вставить(Ключ, Значение); - КонецЕсли; - - ПрочитаннаяСтрока = ЧтениеТекста.ПрочитатьСтроку(); - - КонецЦикла; - - ЧтениеТекста.Закрыть(); - - Возврат Результат; - -КонецФункции - Функция РежимВыводаЛога(Режим) Экспорт Если НРег(Режим) = "none" Тогда @@ -317,19 +16,6 @@ КонецФункции -Функция НастройкиЛогоса(Компонент, Раскладка) Экспорт - - Возврат Соответствия.ИзЭлементов( - "logos.logger", Соответствия.ИзЭлементов( - "oscript.lib.oneunit." + Компонент, Соответствия.ИзЭлементов( - "level", "DEFAULT", - "classlayout", Раскладка - ) - ) - ); - -КонецФункции - &Желудь Процедура ПриСозданииОбъекта() КонецПроцедуры diff --git a/src/cli/main.os b/src/cli/main.os index 15771ac..f7c6662 100644 --- a/src/cli/main.os +++ b/src/cli/main.os @@ -9,7 +9,7 @@ // Поделки еще нет - желудь настроек создается точкой сборки напрямую СоветДругогоМастера = Новый СоветДругогоМастера() .ЗначенияДеталек(Новый Соответствие( - (Новый КонсольноеПриложениеТестированиеСлужебный()).НастройкиЛогоса("cli", "РаскладкаЛогOneUnit") + (Новый ПостроительТочкиВхода()).НастройкиЛогоса("cli", "РаскладкаЛогOneUnit") )); Поделка = Новый Поделка(СоветДругогоМастера); diff --git "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\321\201\320\270\320\275\321\205\321\200\320\276\320\275\320\275\321\213\320\271\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\276\320\262.os" "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\321\201\320\270\320\275\321\205\321\200\320\276\320\275\320\275\321\213\320\271\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\276\320\262.os" index decb6f7..e8e73e5 100644 --- "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\321\201\320\270\320\275\321\205\321\200\320\276\320\275\320\275\321\213\320\271\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\276\320\262.os" +++ "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\321\201\320\270\320\275\321\205\321\200\320\276\320\275\320\275\321\213\320\271\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\276\320\262.os" @@ -2,8 +2,9 @@ #Использовать collectionos #Использовать "../../../shared" -Перем _Поделка; -Перем _ПубликаторСобытий; +Перем _Поделка; // Поделка - Поделка текущего прогона +Перем _ПубликаторСобытий; // ПубликаторСобытий - Публикатор событий исполнения +Перем _ИсполнительИзоляции; // ИсполнительИзоляции - Исполнитель изолированных наборов и тестов Процедура Исполнить(ТестПлан) Экспорт @@ -13,6 +14,11 @@ Для Каждого Набор Из Наборы Цикл + Если Набор.Изоляция() <> Неопределено Тогда + _ИсполнительИзоляции.ИсполнитьНабор(Набор); + Продолжить; + КонецЕсли; + _ПубликаторСобытий.ОпубликоватьСобытие(Набор, "ИсполнениеТестНаборНачало", Новый Массив); РезультатПропустить = ВыполнитьМетод( @@ -126,6 +132,11 @@ Возврат; КонецЕсли; + Если Тест.Изоляция() <> Неопределено Тогда + _ИсполнительИзоляции.ИсполнитьТест(Тест, ОпределениеТестНабора); + Возврат; + КонецЕсли; + Если Тест.ТипОпределения() = ТипыОпределенийТестов.Контейнер Тогда Для Каждого ВложенныйТест Из Тест.Дети() Цикл @@ -427,9 +438,11 @@ &Прозвище("ИсполнительТестов") Процедура ПриСозданииОбъекта( &Пластилин Поделка, - &Пластилин ПубликаторСобытий) + &Пластилин ПубликаторСобытий, + &Пластилин ИсполнительИзоляции) - _Поделка = Поделка; - _ПубликаторСобытий = ПубликаторСобытий; + _Поделка = Поделка; + _ПубликаторСобытий = ПубликаторСобытий; + _ИсполнительИзоляции = ИсполнительИзоляции; КонецПроцедуры diff --git "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\263\320\276\321\202\320\276\320\262\320\272\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265.os" "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\263\320\276\321\202\320\276\320\262\320\272\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265.os" index 89ba54f..e9318a8 100644 --- "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\263\320\276\321\202\320\276\320\262\320\272\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265.os" +++ "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\263\320\276\321\202\320\276\320\262\320\272\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265.os" @@ -2,10 +2,9 @@ #Использовать decorator #Использовать "../../../shared" -Перем _КаталогиТестов; -Перем _ФайлыТестов; -Перем _ИскатьВПодкаталогах; -Перем РегулярноеВыражениеИмяКласса; +Перем _КаталогиТестов; // Массив из Строка - Каталоги поиска файлов тестовых наборов +Перем _ФайлыТестов; // Массив из Строка - Пути к файлам тестовых наборов +Перем _ИскатьВПодкаталогах; // Булево - Искать файлы наборов в подкаталогах Процедура ПриИнициализацииПоделки(Поделка) Экспорт @@ -25,7 +24,7 @@ Для каждого Тест Из ФайлыТестов Цикл Путь = Тест.ПолноеИмя; - ТестовыйНабор = "oneunit_testsuite_" + РегулярноеВыражениеИмяКласса.Заменить(Тест.ИмяБезРасширения, "_"); + ТестовыйНабор = ТестированиеСлужебный.ИмяКлассаТестовогоНабора(Тест.ИмяБезРасширения); ДобавитьЗавязь = Ложь; @@ -106,6 +105,4 @@ _ИскатьВПодкаталогах = ИскатьВПодкаталогах; _ФайлыТестов = СтрРазделить(ФайлыТестов, ",", Ложь); - РегулярноеВыражениеИмяКласса = Новый РегулярноеВыражение("((?<=^)[^a-zA-Z_а-яА-ЯёЁ]|[^a-zA-Z0-9_а-яА-ЯёЁ])"); - КонецПроцедуры diff --git "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" new file mode 100644 index 0000000..98e0d9d --- /dev/null +++ "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" @@ -0,0 +1,1155 @@ +// Выполняет изолированные наборы и тесты и вклеивает их результаты в общий прогон. +// Изолированный набор прогоняется в собственной среде - свежей поделке или дочернем +// процессе - а его результаты переэмитятся событиями исполнения на публикатор +// родителя, так что для репортеров и статистики это обычные тесты. +// Для чистого обнаружения (discover) состав изолированного набора обнаруживается +// в его среде без исполнения тестов и вклеивается в план зеркалами. + +#Использовать autumn +#Использовать collectionos +#Использовать lambdas +#Использовать tempfiles +#Использовать fs +#Использовать "../../../shared" + +Перем _Поделка; // Поделка - Поделка текущего прогона +Перем _ПубликаторСобытий; // ПубликаторСобытий - Публикатор событий исполнения текущего прогона +Перем _Запускатель; // ЗапускательТестирования - Запускатель дочерних прогонов +Перем _КаталогСтатистикиПокрытия; // Строка - Каталог статистики покрытия прогона, пустая строка - без покрытия +Перем _Лог; // Лог - Лог ядра + +// Выполняет изолированный тестовый набор в отдельной среде и публикует его +// результаты в текущий прогон. Отказ изолированной среды не прерывает прогон - +// набор помечается сломанным. +// +// Параметры: +// ОпределениеНабора - ОпределениеТестНабора - Плейсхолдер изолированного набора +// +Процедура ИсполнитьНабор(ОпределениеНабора) Экспорт + + _ПубликаторСобытий.ОпубликоватьСобытие( + ОпределениеНабора, + "ИсполнениеТестНаборНачало", + Новый Массив + ); + + Если ОпределениеНабора.Изоляция().Уровень = УровниИзоляции.Процесс Тогда + ИсполнитьВПроцессе(ОпределениеНабора); + Иначе + ИсполнитьВПоделке(ОпределениеНабора); + КонецЕсли; + +КонецПроцедуры + +// Выполняет изолированный тест в отдельной среде и публикует его результаты +// в текущий прогон: дочерняя среда прогоняет набор, отфильтрованный до этого +// теста, - так у теста сохраняется полный жизненный цикл набора. +// Событие начала исполнения теста уже опубликовано исполнителем тестов +// +// Параметры: +// Тест - ОпределениеТеста - Определение изолированного теста или контейнера +// ОпределениеНабора - ОпределениеТестНабора - Набор изолированного теста +// +Процедура ИсполнитьТест(Тест, ОпределениеНабора) Экспорт + + Если Тест.Изоляция().Уровень = УровниИзоляции.Процесс Тогда + ИсполнитьТестВПроцессе(Тест, ОпределениеНабора); + Иначе + ИсполнитьТестВПоделке(Тест, ОпределениеНабора); + КонецЕсли; + +КонецПроцедуры + +// Обнаруживает состав изолированного набора в его среде, не исполняя тесты, +// и наполняет набор-плейсхолдер зеркалами обнаруженных определений - для чистого +// обнаружения (discover), на состав которого опирается внешний тулинг. +// Отказ изолированной среды не прерывает обнаружение - набор остается пустым +// +// Параметры: +// ОпределениеНабора - ОпределениеТестНабора - Плейсхолдер изолированного набора +// +Процедура ОбнаружитьНабор(ОпределениеНабора) Экспорт + + Попытка + + Если ОпределениеНабора.Изоляция().Уровень = УровниИзоляции.Процесс Тогда + ОбнаружитьВПроцессе(ОпределениеНабора); + Иначе + ОбнаружитьВПоделке(ОпределениеНабора); + КонецЕсли; + + Исключение + + _Лог.Предупреждение(СтрШаблон( + "Не удалось обнаружить состав изолированного набора %1:%2%3", + ОпределениеНабора.Имя(), + Символы.ПС, + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) + )); + + КонецПопытки; + +КонецПроцедуры + +Процедура ОбнаружитьВПоделке(ОпределениеНабора) + + Прогон = ВыполнитьВПоделке(ОпределениеНабора, ОпределениеНабора, "", Истина); + + ФрешНабор = НайтиНаборВПлане(Прогон.План, ОпределениеНабора.Имя()); + + Если ФрешНабор = Неопределено Тогда + Возврат; + КонецЕсли; + + Для каждого ФрешДитя Из ФрешНабор.Дети() Цикл + ОпределениеНабора.ДобавитьТест(ПостроитьЗеркало(ФрешДитя, ОпределениеНабора)); + КонецЦикла; + +КонецПроцедуры + +Процедура ОбнаружитьВПроцессе(ОпределениеНабора) + + МенеджерВременныхФайлов = Новый МенеджерВременныхФайлов(); + + Попытка + План = ПланДочернегоОбнаружения(ОпределениеНабора, МенеджерВременныхФайлов); + Исключение + МенеджерВременныхФайлов.Удалить(); + ВызватьИсключение; + КонецПопытки; + + МенеджерВременныхФайлов.Удалить(); + + Для каждого ЗаписьНабора Из План.suites Цикл + + Если ЗаписьНабора.suite <> ОпределениеНабора.Имя() Тогда + Продолжить; + КонецЕсли; + + Для каждого ЗаписьТеста Из ЗаписьНабора.tests Цикл + ОпределениеНабора.ДобавитьТест(ЗеркалоЗаписиПлана(ЗаписьТеста, ОпределениеНабора)); + КонецЦикла; + + КонецЦикла; + +КонецПроцедуры + +// Запускает дочерний процесс обнаружения файла изолированного набора с записью +// состава плана в JSON-файл и возвращает разобранный план +// +// Параметры: +// ОпределениеНабора - ОпределениеТестНабора - Плейсхолдер изолированного набора +// МенеджерВременныхФайлов - МенеджерВременныхФайлов - Менеджер временных файлов прогона +// +// Возвращаемое значение: +// Структура - Состав плана дочернего обнаружения, см. РепортерПланJSON.ЗаписатьПлан +// +Функция ПланДочернегоОбнаружения(ОпределениеНабора, МенеджерВременныхФайлов) + + ПутьКПлану = МенеджерВременныхФайлов.НовоеИмяФайла("json"); + + Детальки = Новый Соответствие; + Детальки.Вставить("КаталогиТестов", Новый Массив); + Детальки.Вставить("ФайлыТестов", Массивы.ИзЭлементов(ОпределениеНабора.Путь())); + Детальки.Вставить("ИскатьВПодкаталогах", Ложь); + Детальки.Вставить("РежимВыводаЛога", РежимыВыводаЛога.Ничего); + Детальки.Вставить("ВыполняетсяВИзоляции", Истина); + + ПараметрыЗапуска = _Запускатель.ПараметрыЗапуска(); + + ПараметрыЗапуска.Импорты = Импорты(ОпределениеНабора, ОпределениеНабора); + ПараметрыЗапуска.ЛямбдаСовета = ОпределениеНабора.Изоляция().ЛямбдаСовета; + ПараметрыЗапуска.РаскладкаЛога = "РаскладкаЛогOneUnit"; + + Результат = _Запускатель.ВыполнитьОбнаружениеПлана( + Детальки, + ПутьКПлану, + _Лог, + ПараметрыЗапуска + ); + + План = _Запускатель.ПрочитатьОтчет(ПутьКПлану); + + Если План = Неопределено Тогда + ВызватьИсключение СтрШаблон( + "Дочернее обнаружение завершилось аварийно, не сформировав план:%1%2", + Символы.ПС, + РазвернутьЗаписиЛога(Результат.Вывод) + ); + КонецЕсли; + + Возврат План; + +КонецФункции + +// Строит узел-зеркало по записи состава плана дочернего обнаружения +// +// Параметры: +// Запись - Структура - Запись теста состава плана, см. РепортерПланJSON.ЗаписатьПлан +// Родитель - ОпределениеТестНабора, ОпределениеТеста - Родитель зеркала в плане +// +// Возвращаемое значение: +// ОпределениеТеста - Узел-зеркало +// +Функция ЗеркалоЗаписиПлана(Запись, Родитель) + + Дети = Новый СписокМассив; + + ЭтоКонтейнер = Запись.Свойство("children"); + + Зеркало = _Поделка.НайтиЖелудь( + "ОпределениеТеста", + Массивы.ИзЭлементов( + ?(ЭтоКонтейнер, ТипыОпределенийТестов.Контейнер, ТипыОпределенийТестов.Тест), + Запись.test, + Дети, + Родитель, + ЗначениеСвойства(Запись, "method", Запись.test), + Новый СписокМассив, + 0, + Новый СписокМассив, + Новый СписокМассив, + 0, + Неопределено + ) + ); + + Если ЭтоКонтейнер Тогда + Для каждого ЗаписьДитя Из Запись.children Цикл + Дети.Добавить(ЗеркалоЗаписиПлана(ЗаписьДитя, Зеркало)); + КонецЦикла; + КонецЕсли; + + Возврат Зеркало; + +КонецФункции + +Процедура ИсполнитьТестВПоделке(Тест, ОпределениеНабора) + + Попытка + Прогон = ВыполнитьВПоделке(Тест, ОпределениеНабора, Тест.ИмяМетода()); + Исключение + ЗавершитьТест(Тест, РезультатСломан(ИнформацияОбОшибке())); + Возврат; + КонецПопытки; + + ФрешНабор = НайтиНаборВПлане(Прогон.План, ОпределениеНабора.Имя()); + + Если ФрешНабор = Неопределено Тогда + ЗавершитьТест(Тест, РезультатСломан(ОшибкаТестНеВыполнен( + Тест, + СтрШаблон("дочерний прогон не обнаружил набор %1", ОпределениеНабора.Имя()), + Прогон.Вывод + ))); + Возврат; + КонецЕсли; + + РезультатНабора = Прогон.Статистика.Результат(ФрешНабор); + + Если РезультатНабора.Состояние() <> СостоянияВыполненияТестов.Успех Тогда + // Набор в изолированной среде не выполнился (например, упал ПередВсеми) - + // тест наследует его состояние и причины + ЗавершитьТест(Тест, РезультатНабора); + Возврат; + КонецЕсли; + + ФрешТест = НайтиТестВУзле(ФрешНабор, Тест.Имя()); + + Если ФрешТест = Неопределено Тогда + ЗавершитьТест(Тест, РезультатСломан(ОшибкаТестНеВыполнен( + Тест, + "в дочернем прогоне нет результата этого теста", + Прогон.Вывод + ))); + Возврат; + КонецЕсли; + + ЗавершитьТест(Тест, РезультатПоддерева(ФрешТест, Тест, Прогон.Статистика, Прогон.Вывод)); + +КонецПроцедуры + +Процедура ИсполнитьТестВПроцессе(Тест, ОпределениеНабора) + + МенеджерВременныхФайлов = Новый МенеджерВременныхФайлов(); + + Попытка + Прогон = ВыполнитьВДочернемПроцессе( + Тест, + ОпределениеНабора, + МенеджерВременныхФайлов, + Тест.ИмяМетода() + ); + Исключение + МенеджерВременныхФайлов.Удалить(); + ЗавершитьТест(Тест, РезультатСломан(ИнформацияОбОшибке())); + Возврат; + КонецПопытки; + + МенеджерВременныхФайлов.Удалить(); + + Если Прогон.Отчет = Неопределено Тогда + ЗавершитьТест(Тест, РезультатСломан(ОшибкаДочернегоПрогона(Прогон.Вывод))); + Возврат; + КонецЕсли; + + РезультатНабора = РезультатНабораИзОтчета(ОпределениеНабора.Имя(), Прогон.Отчет); + + Если РезультатНабора.Состояние() <> СостоянияВыполненияТестов.Успех Тогда + ЗавершитьТест(Тест, РезультатНабора); + Возврат; + КонецЕсли; + + ЗавершитьТест(Тест, РезультатТестаИзОтчета(Тест, Прогон.Отчет, Прогон.Вывод)); + +КонецПроцедуры + +// Возвращает результат изолированного теста по записям отчета дочернего прогона. +// Для контейнера дополнительно публикует события исполнения его итераций +// +// Параметры: +// Тест - ОпределениеТеста - Определение изолированного теста или контейнера +// Отчет - см. ЗапускательТестирования.ПрочитатьОтчет +// Вывод - Строка - Захваченный вывод дочернего прогона +// +// Возвращаемое значение: +// РезультатВыполненияТеста - Результат теста +// +Функция РезультатТестаИзОтчета(Тест, Отчет, Знач Вывод) + + Если Тест.ТипОпределения() = ТипыОпределенийТестов.Контейнер Тогда + + Для каждого Дитя Из Тест.Дети() Цикл + + _ПубликаторСобытий.ОпубликоватьСобытие(Дитя, "ИсполнениеТестНачало", Новый Массив); + + Запись = НайтиЗаписьТеста(Отчет, Дитя.Имя()); + + Если Запись = Неопределено Тогда + РезультатДитя = РезультатСломан(ОшибкаТестНеВыполнен( + Дитя, + "в отчете дочернего прогона нет записи этого теста", + Вывод + )); + Иначе + РезультатДитя = РезультатЗаписиТеста(Запись); + КонецЕсли; + + _ПубликаторСобытий.ОпубликоватьСобытие( + Дитя, + "ИсполнениеТестКонец", + Массивы.ИзЭлементов(РезультатДитя) + ); + + КонецЦикла; + + Возврат РезультатУспех(); + + КонецЕсли; + + Запись = НайтиЗаписьТеста(Отчет, Тест.Имя()); + + Если Запись = Неопределено Тогда + Возврат РезультатСломан(ОшибкаТестНеВыполнен( + Тест, + "в отчете дочернего прогона нет записи этого теста", + Вывод + )); + КонецЕсли; + + Возврат РезультатЗаписиТеста(Запись); + +КонецФункции + +// Возвращает результат узла изолированного прогона. Для контейнера дополнительно +// публикует события исполнения итераций, сопоставляя их с детьми родного +// определения по именам +// +// Параметры: +// ФрешУзел - ОпределениеТеста - Узел изолированного прогона +// Родное - ОпределениеТеста - Соответствующее определение в плане родителя +// ФрешСтат - РепортерСтатистика - Статистика изолированного прогона +// Вывод - Строка - Захваченный вывод дочернего прогона +// +// Возвращаемое значение: +// РезультатВыполненияТеста - Результат узла +// +Функция РезультатПоддерева(ФрешУзел, Родное, ФрешСтат, Знач Вывод) + + Если Родное.ТипОпределения() <> ТипыОпределенийТестов.Контейнер Тогда + Возврат ФрешСтат.Результат(ФрешУзел); + КонецЕсли; + + Для каждого Дитя Из Родное.Дети() Цикл + + _ПубликаторСобытий.ОпубликоватьСобытие(Дитя, "ИсполнениеТестНачало", Новый Массив); + + ФрешДитя = НайтиТестВУзле(ФрешУзел, Дитя.Имя()); + + Если ФрешДитя = Неопределено Тогда + РезультатДитя = РезультатСломан(ОшибкаТестНеВыполнен( + Дитя, + "в дочернем прогоне нет результата этой итерации", + Вывод + )); + Иначе + РезультатДитя = РезультатПоддерева(ФрешДитя, Дитя, ФрешСтат, Вывод); + КонецЕсли; + + _ПубликаторСобытий.ОпубликоватьСобытие( + Дитя, + "ИсполнениеТестКонец", + Массивы.ИзЭлементов(РезультатДитя) + ); + + КонецЦикла; + + Возврат РезультатУспех(); + +КонецФункции + +Процедура ИсполнитьВПоделке(ОпределениеНабора) + + Попытка + Прогон = ВыполнитьВПоделке(ОпределениеНабора, ОпределениеНабора, ""); + Исключение + // Сдерживание отказов: изолированная среда не поднялась или упало + // обнаружение - набор сломан, но общий прогон продолжается + ЗавершитьНабор(ОпределениеНабора, РезультатСломан(ИнформацияОбОшибке())); + Возврат; + КонецПопытки; + + ФрешНабор = НайтиНаборВПлане(Прогон.План, ОпределениеНабора.Имя()); + + Если ФрешНабор = Неопределено Тогда + // Родитель набор обнаружил, а дочерний прогон - нет: изолированная среда + // собралась не так, как ожидалось (например, совет подменил детальки прогона) + ЗавершитьНабор(ОпределениеНабора, РезультатСломан(ОшибкаНаборНеОбнаружен(ОпределениеНабора, Прогон.Вывод))); + Возврат; + КонецЕсли; + + Для каждого ФрешДитя Из ФрешНабор.Дети() Цикл + + Зеркало = ПостроитьЗеркало(ФрешДитя, ОпределениеНабора); + ОпределениеНабора.ДобавитьТест(Зеркало); + + ОпубликоватьПоддерево(ФрешДитя, Зеркало, Прогон.Статистика); + + КонецЦикла; + + ЗавершитьНабор(ОпределениеНабора, Прогон.Статистика.Результат(ФрешНабор)); + +КонецПроцедуры + +Процедура ИсполнитьВПроцессе(ОпределениеНабора) + + МенеджерВременныхФайлов = Новый МенеджерВременныхФайлов(); + + Попытка + Прогон = ВыполнитьВДочернемПроцессе( + ОпределениеНабора, + ОпределениеНабора, + МенеджерВременныхФайлов, + "" + ); + Исключение + // Сдерживание отказов: дочерний прогон не удалось запустить - + // набор сломан, но общий прогон продолжается + МенеджерВременныхФайлов.Удалить(); + ЗавершитьНабор(ОпределениеНабора, РезультатСломан(ИнформацияОбОшибке())); + Возврат; + КонецПопытки; + + МенеджерВременныхФайлов.Удалить(); + + Если Прогон.Отчет = Неопределено Тогда + // Отчета нет - дочерний процесс умер, не дописав его + ЗавершитьНабор(ОпределениеНабора, РезультатСломан(ОшибкаДочернегоПрогона(Прогон.Вывод))); + Возврат; + КонецЕсли; + + ВклеитьОтчет(ОпределениеНабора, Прогон.Отчет); + +КонецПроцедуры + +// Запускает дочерний прогон файла изолированного набора с формированием +// JSON-отчета и возвращает разобранный отчет +// +// Параметры: +// Источник - ОпределениеТестНабора, ОпределениеТеста - Определение с описанием изоляции +// ОпределениеНабора - ОпределениеТестНабора - Набор изолированного прогона +// МенеджерВременныхФайлов - МенеджерВременныхФайлов - Менеджер временных файлов прогона +// ФильтрМетода - Строка - Имя тестового метода для изоляции одного теста, +// пустая строка - прогон всего набора +// +// Возвращаемое значение: +// Структура - Результаты дочернего прогона: +// * Отчет - см. ЗапускательТестирования.ПрочитатьОтчет +// * Вывод - Строка - Захваченный вывод прогона +// +Функция ВыполнитьВДочернемПроцессе(Источник, ОпределениеНабора, МенеджерВременныхФайлов, Знач ФильтрМетода) + + ПутьКОтчету = МенеджерВременныхФайлов.НовоеИмяФайла("json"); + + Детальки = Новый Соответствие; + Детальки.Вставить("КаталогиТестов", Новый Массив); + Детальки.Вставить("ФайлыТестов", Массивы.ИзЭлементов(ОпределениеНабора.Путь())); + Детальки.Вставить("ИскатьВПодкаталогах", Ложь); + Детальки.Вставить("Таймаут", Источник.Таймаут()); + Детальки.Вставить("РежимВыводаЛога", РежимыВыводаЛога.Ничего); + Детальки.Вставить("ПутьКОтчетуJSON", ПутьКОтчету); + Детальки.Вставить("ВыполняетсяВИзоляции", Истина); + Детальки.Вставить("ТестовыеМетодыВключаемые", ФильтрМетода); + + // Вывод дочернего прогона не транслируется в консоль родителя, чтобы не + // разрывать его собственный вывод: пер-тестовые выводы и причины падений + // переносятся из отчета внутрь результатов тестов, а целиком захваченный + // вывод используется как причина при аварийном завершении прогона + ПараметрыЗапуска = _Запускатель.ПараметрыЗапуска(); + + ПараметрыЗапуска.Импорты = Импорты(Источник, ОпределениеНабора); + ПараметрыЗапуска.ЛямбдаСовета = Источник.Изоляция().ЛямбдаСовета; + ПараметрыЗапуска.РаскладкаЛога = "РаскладкаЛогOneUnit"; + ПараметрыЗапуска.ПутьКСтатистикеПокрытия = ПутьКСтатистикеПокрытия(); + + Результат = _Запускатель.ВыполнитьТестирование( + Детальки, + _Лог, + ПараметрыЗапуска + ); + + Возврат Новый Структура( + "Отчет, Вывод", + _Запускатель.ПрочитатьОтчет(ПутьКОтчету), + Результат.Вывод + ); + +КонецФункции + +// Возвращает полные пути импортов изолированного прогона: относительные пути +// аннотации разрешаются от каталога файла набора +// +// Параметры: +// Источник - ОпределениеТестНабора, ОпределениеТеста - Определение с описанием изоляции +// ОпределениеНабора - ОпределениеТестНабора - Набор изолированного прогона +// +// Возвращаемое значение: +// Массив из Строка - Имена библиотек и полные пути импортов +// +Функция Импорты(Источник, ОпределениеНабора) + + Импорты = Новый Массив; + + ИсходныйКаталог = ТекущийКаталог(); + УстановитьТекущийКаталог(Новый Файл(ОпределениеНабора.Путь()).Путь); + + Для каждого Импорт Из Источник.Изоляция().Импорты Цикл + + Если СтрНайти(Импорт, "/") > 0 Или СтрНайти(Импорт, "\") > 0 Тогда + Импорты.Добавить(ФС.НормализоватьПуть(Импорт)); + Иначе + Импорты.Добавить(Импорт); + КонецЕсли; + + КонецЦикла; + + УстановитьТекущийКаталог(ИсходныйКаталог); + + Возврат Импорты; + +КонецФункции + +// Вклеивает результаты дочернего прогона из JSON-отчета в план родителя: +// каждый тест отчета публикуется событиями исполнения как обычный тест +// +// Параметры: +// ОпределениеНабора - ОпределениеТестНабора - Плейсхолдер изолированного набора +// Отчет - см. ЗапускательТестирования.ПрочитатьОтчет +// +Процедура ВклеитьОтчет(ОпределениеНабора, Отчет) + + Для каждого ЗаписьТеста Из Отчет.tests Цикл + + Зеркало = ЗеркалоЗаписиТеста(ЗаписьТеста, ОпределениеНабора); + ОпределениеНабора.ДобавитьТест(Зеркало); + + _ПубликаторСобытий.ОпубликоватьСобытие(Зеркало, "ИсполнениеТестНачало", Новый Массив); + _ПубликаторСобытий.ОпубликоватьСобытие( + Зеркало, + "ИсполнениеТестКонец", + Массивы.ИзЭлементов(РезультатЗаписиТеста(ЗаписьТеста)) + ); + + КонецЦикла; + + ЗавершитьНабор(ОпределениеНабора, РезультатНабораИзОтчета(ОпределениеНабора.Имя(), Отчет)); + +КонецПроцедуры + +Функция ЗеркалоЗаписиТеста(ЗаписьТеста, ОпределениеНабора) + + Возврат _Поделка.НайтиЖелудь( + "ОпределениеТеста", + Массивы.ИзЭлементов( + ТипыОпределенийТестов.Тест, + ЗаписьТеста.test, + Новый СписокМассив, + ОпределениеНабора, + ЗаписьТеста.test, + Новый СписокМассив, + 0, + Новый СписокМассив, + Новый СписокМассив, + 0, + Неопределено + ) + ); + +КонецФункции + +Функция РезультатЗаписиТеста(ЗаписьТеста) + + Состояние = СостояниеПоСтатусу(ЗаписьТеста.status); + + Причины = Новый СписокМассив; + + Если Состояние = СостоянияВыполненияТестов.Ошибка + Или Состояние = СостоянияВыполненияТестов.Сломан Тогда + + Причины.Добавить(ПричинаИзОтчета(ЗаписьТеста)); + + ИначеЕсли Состояние = СостоянияВыполненияТестов.Пропущен + Или Состояние = СостоянияВыполненияТестов.Прерван Тогда + + Причины.Добавить(ЗначениеСвойства(ЗаписьТеста, "message", "")); + + КонецЕсли; // BSLLS:IfElseIfEndsWithElse-off + + Возврат Результат( + Состояние, + Причины, + ЗначениеСвойства(ЗаписьТеста, "output", "") + ); + +КонецФункции + +Функция РезультатНабораИзОтчета(Знач ИмяНабора, Отчет) + + // Проблема или пропуск уровня набора - запись без имени теста + Для каждого Проблема Из Отчет.problems Цикл + Если Не Проблема.Свойство("test") И Проблема.suite = ИмяНабора Тогда + Возврат Результат( + СостояниеПоСтатусу(Проблема.status), + Списки.ИзЭлементов(ПричинаИзОтчета(Проблема)), + "" + ); + КонецЕсли; + КонецЦикла; + + Для каждого Пропуск Из Отчет.skipped_tests Цикл + Если Не Пропуск.Свойство("test") И Пропуск.suite = ИмяНабора Тогда + Возврат Результат( + СостояниеПоСтатусу(Пропуск.status), + Списки.ИзЭлементов(ЗначениеСвойства(Пропуск, "reason", "")), + "" + ); + КонецЕсли; + КонецЦикла; + + // Наихудшее состояние тестов родитель агрегирует по событиям сам + Возврат РезультатУспех(); + +КонецФункции + +// Заворачивает сообщение и подробности записи отчета в ИнформациюОбОшибке: +// репортеры родителя ожидают у причин проблем интерфейс информации об ошибке. +// Подробности передаются как готовое описание - без служебного стека обертки +// +// Параметры: +// Запись - Структура - Запись отчета с полями message и details +// +// Возвращаемое значение: +// ИнформацияОбОшибке - Причина с сообщением и подробностями дочернего прогона +// +Функция ПричинаИзОтчета(Запись) + + Сообщение = ЗначениеСвойства(Запись, "message", "Дочерний прогон завершился с ошибкой"); + Описание = ЗначениеСвойства(Запись, "details", ""); + + Если Не ЗначениеЗаполнено(Описание) Тогда + Описание = СтрШаблон( + " + |Причина: + |%1 + |", + Сообщение + ); + КонецЕсли; + + Возврат ПричинаСГотовымОписанием(Сообщение, Описание); + +КонецФункции + +Функция ОшибкаДочернегоПрогона(Знач Вывод) + + Сообщение = "Дочерний прогон изолированного набора завершился аварийно, не сформировав отчет"; + + Возврат ПричинаСГотовымОписанием(Сообщение, ОписаниеПричиныСВыводом(Сообщение, Вывод)); + +КонецФункции + +// Составляет подробное описание причины, дополняя сообщение захваченным выводом +// дочернего прогона - единственным свидетельством того, что происходило +// в изолированной среде +// +// Параметры: +// Сообщение - Строка - Краткое описание причины +// Вывод - Строка - Захваченный вывод дочернего прогона +// +// Возвращаемое значение: +// Строка - Подробное описание причины +// +Функция ОписаниеПричиныСВыводом(Знач Сообщение, Знач Вывод) + + Вывод = РазвернутьЗаписиЛога(Вывод); + + Если Не ЗначениеЗаполнено(СокрЛП(Вывод)) Тогда + Вывод = "<дочерний прогон ничего не вывел>"; + КонецЕсли; + + Возврат СтрШаблон( + " + |Причина: + |%1 + | + |Вывод дочернего прогона: + |%2 + |", + Сообщение, + Вывод + ); + +КонецФункции + +// Разворачивает записи JSON-раскладки лога в захваченном выводе дочернего прогона +// в их сообщения: лог изолированной среды мог быть настроен не так, как у родителя, +// а сырые JSON-записи в описании причины нечитаемы +// +// Параметры: +// Вывод - Строка - Захваченный вывод дочернего прогона +// +// Возвращаемое значение: +// Строка - Вывод с сообщениями вместо JSON-записей лога +// +Функция РазвернутьЗаписиЛога(Знач Вывод) + + Строки = Новый Массив; + + Для каждого СтрокаВывода Из СтрРазделить(Вывод, Символы.ПС) Цикл + Строки.Добавить(СообщениеЗаписиЛога(СтрокаВывода)); + КонецЦикла; + + Возврат СтрСоединить(Строки, Символы.ПС); + +КонецФункции + +Функция СообщениеЗаписиЛога(Знач СтрокаВывода) + + Если Не СтрНачинаетсяС(СокрЛП(СтрокаВывода), "{") Тогда + Возврат СтрокаВывода; + КонецЕсли; + + Попытка + Чтение = Новый ЧтениеJSON(); + Чтение.УстановитьСтроку(СтрокаВывода); + Запись = ПрочитатьJSON(Чтение, Истина); + Чтение.Закрыть(); + Исключение + // Не запись лога, а просто строка с фигурной скобкой - оставляем как есть + Возврат СтрокаВывода; + КонецПопытки; + + Если ТипЗнч(Запись) <> Тип("Соответствие") Тогда + Возврат СтрокаВывода; + КонецЕсли; + + Сообщение = Запись.Получить("msg"); + + Возврат ?(ТипЗнч(Сообщение) = Тип("Строка"), Сообщение, СтрокаВывода); + +КонецФункции + +// Создает причину, подробное описание которой уже сформировано дочерним прогоном: +// ТестированиеСлужебный.ПодробноеОписаниеОшибки вернет его как есть, не подмешивая +// модуль, строку и стек этой обертки +// +// Параметры: +// Сообщение - Строка - Краткое описание причины +// ГотовоеОписание - Строка - Подробное описание, сформированное дочерним прогоном +// +// Возвращаемое значение: +// ИнформацияОбОшибке - Причина с готовым подробным описанием +// +Функция ПричинаСГотовымОписанием(Знач Сообщение, Знач ГотовоеОписание) + + Попытка + ВызватьИсключение Новый ИнформацияОбОшибке( + Сообщение, + Новый Структура("ПеренесенноеОписание", ГотовоеОписание) + ); + Исключение + Возврат ИнформацияОбОшибке(); + КонецПопытки; + +КонецФункции + +Функция СостояниеПоСтатусу(Знач Статус) + + Если Статус = "failed" Тогда + Возврат СостоянияВыполненияТестов.Ошибка; + ИначеЕсли Статус = "broken" Тогда + Возврат СостоянияВыполненияТестов.Сломан; + ИначеЕсли Статус = "skipped" Тогда + Возврат СостоянияВыполненияТестов.Пропущен; + ИначеЕсли Статус = "aborted" Тогда + Возврат СостоянияВыполненияТестов.Прерван; + Иначе + Возврат СостоянияВыполненияТестов.Успех; + КонецЕсли; + +КонецФункции + +Функция ЗначениеСвойства(Запись, Знач ИмяСвойства, Знач Умолчание) + + Значение = Умолчание; + Запись.Свойство(ИмяСвойства, Значение); + + Возврат Значение; + +КонецФункции + +// Создает свежую поделку по описанию изоляции, прогоняет в ней файл набора и +// возвращает результаты прогона +// +// Параметры: +// Источник - ОпределениеТестНабора, ОпределениеТеста - Определение с описанием изоляции +// ОпределениеНабора - ОпределениеТестНабора - Набор изолированного прогона +// ФильтрМетода - Строка - Имя тестового метода для изоляции одного теста, +// пустая строка - прогон всего набора +// ТолькоОбнаружение - Булево - Составить план, не исполняя тесты +// +// Возвращаемое значение: +// Структура - Результаты изолированного прогона: +// * Поделка - Поделка - Поделка изолированного прогона +// * План - ТестПлан - Выполненный тест-план +// * Статистика - РепортерСтатистика - Статистика прогона +// * Вывод - Строка - Захваченный консольный вывод прогона +// +Функция ВыполнитьВПоделке(Источник, ОпределениеНабора, Знач ФильтрМетода, Знач ТолькоОбнаружение = Ложь) + + Совет = ПостроитьСовет(Источник, ОпределениеНабора); + + // Копируем в изменяемое соответствие: совет мог отдать фиксированное + Если Совет.ЗначенияДеталек() = Неопределено Тогда + Детальки = Новый Соответствие; + Иначе + Детальки = Соответствия.Скопировать(Совет.ЗначенияДеталек()); + КонецЕсли; + Совет.ЗначенияДеталек(Детальки); + + Детальки.Вставить("OneUnit.КаталогиТестов", ""); + Детальки.Вставить("OneUnit.ФайлыТестов", ОпределениеНабора.Путь()); + Детальки.Вставить("OneUnit.Таймаут", Источник.Таймаут()); + Детальки.Вставить("OneUnit.ВыполняетсяВИзоляции", Истина); + Детальки.Вставить("OneUnit.РежимВыводаЛога", РежимыВыводаЛога.Ничего); + Детальки.Вставить("OneUnit.ТестовыеМетодыВключаемые", ФильтрМетода); + + // Консольный вывод дочернего прогона (свой лог ошибок и статистики) + // перехватывается, чтобы не разрывать вывод родителя: причины и выводы + // тестов переносятся в результаты структурно + ПотокВывода = Новый ПотокВПамяти(); + ТестированиеСлужебный.НачатьПерехватВывода(ПотокВывода); + + Попытка + + Поделка = Новый Поделка(Совет); + Поделка.ЗапуститьПриложение(); + + Если ТолькоОбнаружение Тогда + План = Поделка.НайтиЖелудь("МенеджерТестирования").Обнаружить(); + Иначе + План = Поделка.НайтиЖелудь("МенеджерТестирования").Тестировать(); + КонецЕсли; + + Исключение + ТестированиеСлужебный.ЗавершитьПерехватВывода(); + ПотокВывода.Закрыть(); + ВызватьИсключение; + КонецПопытки; + + ТестированиеСлужебный.ЗавершитьПерехватВывода(); + + Прогон = Новый Структура; + Прогон.Вставить("Поделка", Поделка); + Прогон.Вставить("План", План); + Прогон.Вставить("Статистика", Поделка.НайтиЖелудь("РепортерСтатистика")); + Прогон.Вставить("Вывод", ТестированиеСлужебный.ПрочитатьПоток(ПотокВывода)); + + ПотокВывода.Закрыть(); + + Возврат Прогон; + +КонецФункции + +// Строит совет создания изолированной поделки. При наличии лямбды совета +// исполняет ее в контексте набора, иначе возвращает совет по умолчанию. +// +// Параметры: +// Источник - ОпределениеТестНабора, ОпределениеТеста - Определение с описанием изоляции +// ОпределениеНабора - ОпределениеТестНабора - Набор изолированного прогона +// +// Возвращаемое значение: +// СоветДругогоМастера - Совет создания изолированной поделки +// +Функция ПостроитьСовет(Источник, ОпределениеНабора) + + ЛямбдаСовета = Источник.Изоляция().ЛямбдаСовета; + + Если Не ЗначениеЗаполнено(ЛямбдаСовета) Тогда + Возврат Новый СоветДругогоМастера(); + КонецЕсли; + + Возврат Лямбда.Выражение(ЛямбдаСовета) + .Интерфейс(ФункциональныеИнтерфейсы.Вызываемый()) + .ЗахватитьОбъект(ОпределениеНабора.ТестНабор()) + .ВДействие() + .Выполнить(); + +КонецФункции + +// Ищет набор в тест-плане по имени +// +// Параметры: +// План - ТестПлан - Тест-план изолированного прогона +// Имя - Строка - Имя набора +// +// Возвращаемое значение: +// ОпределениеТестНабора - Найденный набор +// Неопределено - набор не найден +// +Функция НайтиНаборВПлане(План, Знач Имя) + + Для каждого Набор Из План.Дети() Цикл + Если Набор.Имя() = Имя Тогда + Возврат Набор; + КонецЕсли; + КонецЦикла; + + Возврат Неопределено; + +КонецФункции + +Функция НайтиТестВУзле(Узел, Знач Имя) + + Для каждого Дитя Из Узел.Дети() Цикл + Если Дитя.Имя() = Имя Тогда + Возврат Дитя; + КонецЕсли; + КонецЦикла; + + Возврат Неопределено; + +КонецФункции + +Функция НайтиЗаписьТеста(Отчет, Знач ИмяТеста) + + Для каждого Запись Из Отчет.tests Цикл + Если Запись.test = ИмяТеста Тогда + Возврат Запись; + КонецЕсли; + КонецЦикла; + + Возврат Неопределено; + +КонецФункции + +// Возвращает путь к файлу статистики покрытия изолированного дочернего процесса: +// исполнение кода в нем тоже попадает в покрытие - статистика пишется в каталог +// родительского прогона и мержится им в общие отчеты +// +// Возвращаемое значение: +// Строка - Путь к файлу статистики, пустая строка - покрытие не собирается +// +Функция ПутьКСтатистикеПокрытия() + + Если Не ЗначениеЗаполнено(_КаталогСтатистикиПокрытия) Тогда + Возврат ""; + КонецЕсли; + + Возврат ОбъединитьПути( + _КаталогСтатистикиПокрытия, + СтрШаблон("stat_%1.json", Новый УникальныйИдентификатор) + ); + +КонецФункции + +Процедура ЗавершитьТест(Тест, Результат) + + _ПубликаторСобытий.ОпубликоватьСобытие( + Тест, + "ИсполнениеТестКонец", + Массивы.ИзЭлементов(Результат) + ); + +КонецПроцедуры + +Процедура ЗавершитьНабор(ОпределениеНабора, Результат) + + _ПубликаторСобытий.ОпубликоватьСобытие( + ОпределениеНабора, + "ИсполнениеТестНаборКонец", + Массивы.ИзЭлементов(Результат) + ); + +КонецПроцедуры + +// Создает причину для теста, результата которого нет в дочернем прогоне. +// Пояснение и вывод дочернего прогона показывают, что происходило +// в изолированной среде +// +// Параметры: +// Тест - ОпределениеТеста - Определение теста или итерации контейнера +// Пояснение - Строка - Чего именно не хватает в результатах дочернего прогона +// Вывод - Строка - Захваченный вывод дочернего прогона +// +// Возвращаемое значение: +// ИнформацияОбОшибке - Причина с готовым подробным описанием +// +Функция ОшибкаТестНеВыполнен(Тест, Знач Пояснение, Знач Вывод) + + Сообщение = СтрШаблон( + "Тест %1 не был выполнен в изолированной среде: %2", + Тест.Имя(), + Пояснение + ); + + Возврат ПричинаСГотовымОписанием(Сообщение, ОписаниеПричиныСВыводом(Сообщение, Вывод)); + +КонецФункции + +Функция ОшибкаНаборНеОбнаружен(ОпределениеНабора, Знач Вывод) + + Сообщение = СтрШаблон( + "Изолированный набор %1 не был обнаружен в дочернем прогоне", + ОпределениеНабора.Имя() + ); + + Возврат ПричинаСГотовымОписанием(Сообщение, ОписаниеПричиныСВыводом(Сообщение, Вывод)); + +КонецФункции + +// Строит узел-зеркало определения теста, повторяющий структуру узла +// изолированного прогона, для публикации событий в родительский план +// +// Параметры: +// ФрешУзел - ОпределениеТеста - Узел изолированного прогона +// РодительЗеркала - ОпределениеТестНабора, ОпределениеТеста - Родитель зеркала в плане +// +// Возвращаемое значение: +// ОпределениеТеста - Узел-зеркало +// +Функция ПостроитьЗеркало(ФрешУзел, РодительЗеркала) + + Дети = Новый СписокМассив; + + Зеркало = _Поделка.НайтиЖелудь( + "ОпределениеТеста", + Массивы.ИзЭлементов( + ФрешУзел.ТипОпределения(), + ФрешУзел.Имя(), + Дети, + РодительЗеркала, + ФрешУзел.ИмяМетода(), + Новый СписокМассив, + 0, + Новый СписокМассив, + Новый СписокМассив, + ФрешУзел.Порядок(), + Неопределено + ) + ); + + Если ФрешУзел.ТипОпределения() = ТипыОпределенийТестов.Контейнер Тогда + Для каждого ФрешДитя Из ФрешУзел.Дети() Цикл + Дети.Добавить(ПостроитьЗеркало(ФрешДитя, Зеркало)); + КонецЦикла; + КонецЕсли; + + Возврат Зеркало; + +КонецФункции + +// Публикует события исполнения для поддерева зеркала, перенося в текущий прогон +// результаты, накопленные статистикой изолированного прогона +// +// Параметры: +// ФрешУзел - ОпределениеТеста - Узел изолированного прогона +// Зеркало - ОпределениеТеста - Соответствующий узел-зеркало +// ФрешСтат - РепортерСтатистика - Статистика изолированного прогона +// +Процедура ОпубликоватьПоддерево(ФрешУзел, Зеркало, ФрешСтат) + + _ПубликаторСобытий.ОпубликоватьСобытие(Зеркало, "ИсполнениеТестНачало", Новый Массив); + + Если ФрешУзел.ТипОпределения() = ТипыОпределенийТестов.Контейнер Тогда + + ФрешДети = ФрешУзел.Дети(); + ДетиЗеркал = Зеркало.Дети(); + + Для Индекс = 0 По ФрешДети.Количество() - 1 Цикл + ОпубликоватьПоддерево(ФрешДети.Получить(Индекс), ДетиЗеркал.Получить(Индекс), ФрешСтат); + КонецЦикла; + + Результат = РезультатУспех(); + + Иначе + Результат = ФрешСтат.Результат(ФрешУзел); + КонецЕсли; + + _ПубликаторСобытий.ОпубликоватьСобытие( + Зеркало, + "ИсполнениеТестКонец", + Массивы.ИзЭлементов(Результат) + ); + +КонецПроцедуры + +Функция РезультатСломан(ИнформацияОбОшибке) + Возврат Результат(СостоянияВыполненияТестов.Сломан, Списки.ИзЭлементов(ИнформацияОбОшибке)); +КонецФункции + +Функция РезультатУспех() + Возврат Результат(СостоянияВыполненияТестов.Успех, Списки.ИзЭлементов()); +КонецФункции + +Функция Результат(Знач Состояние, Причины, Знач Вывод = "") + Возврат _Поделка.НайтиЖелудь( + "РезультатВыполненияТеста", + Массивы.ИзЭлементов(Состояние, Причины, Вывод) + ); +КонецФункции + +&Желудь +Процедура ПриСозданииОбъекта( + &Пластилин Поделка, + &Пластилин ПубликаторСобытий, + &Пластилин ЗапускательТестирования, + &Деталька("OneUnit.КаталогСтатистикиПокрытия", ЗначениеПоУмолчанию = "") КаталогСтатистикиПокрытия, + &Лог("oscript.lib.oneunit.core") Лог) + + _Поделка = Поделка; + _ПубликаторСобытий = ПубликаторСобытий; + _Запускатель = ЗапускательТестирования; + _КаталогСтатистикиПокрытия = КаталогСтатистикиПокрытия; + _Лог = Лог; + +КонецПроцедуры diff --git "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\320\275\320\260\321\200\321\203\320\266\320\270\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\276\320\262.os" "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\320\275\320\260\321\200\321\203\320\266\320\270\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\276\320\262.os" index 2dd337b..10e7d54 100644 --- "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\320\275\320\260\321\200\321\203\320\266\320\270\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\276\320\262.os" +++ "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\320\275\320\260\321\200\321\203\320\266\320\270\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\276\320\262.os" @@ -2,11 +2,24 @@ #Использовать collectionos #Использовать "../../../shared" -Перем _Поделка; -Перем _Фильтры; -Перем _КорневыеАннотацииУсловий; - -Функция Обнаружить() Экспорт +Перем _Поделка; // Поделка - Поделка текущего прогона +Перем _Фильтры; // Массив из Фильтр - Фильтры тестов и наборов +Перем _КорневыеАннотацииУсловий; // СписокМассив из Строка - Имена корневых аннотаций условий +Перем _ВыполняетсяВИзоляции; // Булево - Признак прогона внутри изолированной среды +Перем _ПутиКТестовымНаборам; // КартаСоответствие - Пути к файлам наборов по именам классов +Перем _ИсполнительИзоляции; // ИсполнительИзоляции - Обнаружитель состава изолированных наборов + +// Составляет тест-план по окружению поделки +// +// Параметры: +// РазворачиватьИзолированные - Булево - Разворачивать состав изолированных наборов +// в их средах (для чистого обнаружения без исполнения); +// при исполнении состав вклеивает исполнитель изоляции +// +// Возвращаемое значение: +// ТестПлан - Составленный тест-план +// +Функция Обнаружить(Знач РазворачиватьИзолированные = Ложь) Экспорт ТестПлан = _Поделка.НайтиЖелудь("ТестПлан"); @@ -27,6 +40,12 @@ Для Каждого ОпределениеТестовогоНабора Из ОпределенияТестовыхНаборов Цикл + Если Не _ПутиКТестовымНаборам.СодержитКлюч(ОпределениеТестовогоНабора.Имя()) Тогда + // Аннотированные типы видны всем поделкам процесса: набор, файл которого + // не подключала заготовка этой поделки, принадлежит чужому прогону + Продолжить; + КонецЕсли; + ТестНабор = ЗарегистрироватьТестовыйНабор(ОпределениеТестовогоНабора, ТестПлан); Если Пропустить(ТестНабор) Тогда @@ -35,6 +54,19 @@ ТестПлан.ДобавитьТестовыйНабор(ТестНабор); + Если ТестНабор.Изоляция() <> Неопределено Тогда + + // Изолированный набор разворачивается в собственной среде: при чистом + // обнаружении - обнаружением состава без исполнения, при исполнении - + // исполнителем изоляции, который вклеивает фактические результаты + Если РазворачиватьИзолированные Тогда + _ИсполнительИзоляции.ОбнаружитьНабор(ТестНабор); + КонецЕсли; + + Продолжить; + + КонецЕсли; + Тесты = РаботаСАннотациями.НайтиМетодыСАннотацией( ОпределениеТестовогоНабора.Методы(), "Тестируемый" @@ -69,6 +101,8 @@ Условия = УсловияМетода(ТестМетод); + ИзоляцияТеста = ОписаниеИзоляции(ТестМетод); + Если РаботаСАннотациями.ПолучитьАннотацию(ТестМетод, "Тест") <> Неопределено Тогда Определение = _Поделка.НайтиЖелудь( @@ -83,7 +117,8 @@ Таймаут, Теги, Условия, - Порядок + Порядок, + ИзоляцияТеста ) ); @@ -107,7 +142,8 @@ Таймаут, Теги, Условия, - Порядок + Порядок, + ИзоляцияТеста ) ); @@ -147,7 +183,8 @@ Таймаут, Теги, Условия, - Порядок + Порядок, + Неопределено ) ) ); @@ -199,7 +236,8 @@ Таймаут, Теги, УсловияМетода(Конструктор), - ЖизненныйЦикл + ЖизненныйЦикл, + ОписаниеИзоляции(Конструктор) ) ); @@ -207,6 +245,63 @@ КонецФункции +// Строит описание изоляции по аннотации &Изолированный на методе +// +// Параметры: +// Метод - ДанныеМетода - Метод теста или конструктор набора +// +// Возвращаемое значение: +// Структура - Описание изоляции: +// * Уровень - Строка - Уровень изоляции, см. УровниИзоляции +// * Импорты - Массив из Строка - Пути импортов для уровня Процесс +// * ЛямбдаСовета - Строка - Лямбда-выражение совета создания поделки, пустая строка - без совета +// Неопределено - метод не изолирован +// +Функция ОписаниеИзоляции(Метод) + + Если _ВыполняетсяВИзоляции Тогда + // Внутри изолированной среды аннотация игнорируется, иначе изоляция + // заворачивалась бы в изоляцию бесконечно + Возврат Неопределено; + КонецЕсли; + + Аннотация = РаботаСАннотациями.ПолучитьАннотацию(Метод, "Изолированный"); + + Если Аннотация = Неопределено Тогда + Возврат Неопределено; + КонецЕсли; + + Уровень = РаботаСАннотациями.ПолучитьЗначениеПараметраАннотации(Аннотация, "Уровень", УровниИзоляции.Поделка); + Импорты = РаботаСАннотациями.ПолучитьЗначениеПараметраАннотации(Аннотация, "Импорты", ""); + ЛямбдаСовета = РаботаСАннотациями.ПолучитьЗначениеПараметраАннотации(Аннотация, "Совет", ""); + + Если Уровень <> УровниИзоляции.Поделка И Уровень <> УровниИзоляции.Процесс Тогда + ВызватьИсключение СтрШаблон( + "В аннотации Изолированный неизвестный уровень изоляции: %1", Уровень + ); + КонецЕсли; + + СписокИмпортов = Новый Массив; + Для каждого Импорт Из СтрРазделить(Импорты, ",", Ложь) Цикл + Если ЗначениеЗаполнено(СокрЛП(Импорт)) Тогда + СписокИмпортов.Добавить(СокрЛП(Импорт)); + КонецЕсли; + КонецЦикла; + + Если СписокИмпортов.Количество() > 0 И Уровень <> УровниИзоляции.Процесс Тогда + ВызватьИсключение + "Параметр Импорты аннотации Изолированный доступен только для уровня Процесс"; + КонецЕсли; + + Возврат Новый Структура( + "Уровень, Импорты, ЛямбдаСовета", + Уровень, + СписокИмпортов, + ЛямбдаСовета + ); + +КонецФункции + Функция УсловияМетода(Метод) Условия = Новый СписокМассив; @@ -246,10 +341,16 @@ &Желудь Процедура ПриСозданииОбъекта( &Пластилин Поделка, - &Пластилин("Фильтр", Тип = "Список") Фильтры) + &Пластилин ПутиКТестовымНаборам, + &Пластилин ИсполнительИзоляции, + &Пластилин("Фильтр", Тип = "Список") Фильтры, + &Деталька("OneUnit.ВыполняетсяВИзоляции", ЗначениеПоУмолчанию = Ложь) ВыполняетсяВИзоляции) _Поделка = Поделка; _Фильтры = Фильтры; + _ВыполняетсяВИзоляции = ВыполняетсяВИзоляции; + _ПутиКТестовымНаборам = ПутиКТестовымНаборам; + _ИсполнительИзоляции = ИсполнительИзоляции; _КорневыеАннотацииУсловий = Новый СписокМассив; _КорневыеАннотацииУсловий.Добавить("ВыключенЕсли"); diff --git "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\235\320\260\320\261\320\276\321\200\320\260.os" "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\235\320\260\320\261\320\276\321\200\320\260.os" index 77a6377..01b546e 100644 --- "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\235\320\260\320\261\320\276\321\200\320\260.os" +++ "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\235\320\260\320\261\320\276\321\200\320\260.os" @@ -11,6 +11,7 @@ Перем _Путь; Перем _Представление; Перем _Условия; +Перем _Изоляция; Перем ДетиОтсортированы; @@ -68,6 +69,16 @@ Возврат Новый ФиксированныйСписок(_Условия); КонецФункции +// Описание изоляции набора, если набор изолированный +// +// Возвращаемое значение: +// Структура - Описание изоляции, см. ОбнаружительТестов.ОписаниеИзоляции +// Неопределено - набор выполняется обычным образом +// +Функция Изоляция() Экспорт + Возврат _Изоляция; +КонецФункции + Функция ПередВсеми() Экспорт Возврат _ЖизненныйЦикл.ПолучитьИлиУмолчание("ПередВсеми", Списки.ИзЭлементов()); КонецФункции @@ -104,7 +115,8 @@ &Блестяшка Таймаут, &Блестяшка Теги, &Блестяшка Условия, - &Блестяшка ЖизненныйЦикл) + &Блестяшка ЖизненныйЦикл, + &Блестяшка Изоляция) _Дети = Новый СписокМассив; _Определение = Определение; @@ -114,6 +126,7 @@ _Теги = Теги; _Условия = Условия; _ЖизненныйЦикл = ЖизненныйЦикл; + _Изоляция = Изоляция; _Путь = ПутиКТестовымНаборам.ПолучитьИлиУмолчание( Определение.Имя(), "Не удалось определить путь к тестовому набору" diff --git "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\260.os" "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\260.os" index 789530a..3482e60 100644 --- "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\260.os" +++ "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\321\200\320\265\320\264\320\265\320\273\320\265\320\275\320\270\320\265\320\242\320\265\321\201\321\202\320\260.os" @@ -10,6 +10,7 @@ Перем _Теги; Перем _Условия; Перем _Порядок; +Перем _Изоляция; Функция ТипОпределения() Экспорт Возврат _Тип; @@ -59,6 +60,16 @@ Возврат _Порядок; КонецФункции +// Описание изоляции теста, если тест изолированный +// +// Возвращаемое значение: +// Структура - Описание изоляции, см. ОбнаружительТестов.ОписаниеИзоляции +// Неопределено - тест выполняется обычным образом +// +Функция Изоляция() Экспорт + Возврат _Изоляция; +КонецФункции + Процедура ОбработкаПолученияПредставления(Представление, СтандартнаяОбработка) // BSLLS:UnusedLocalMethod-off СтандартнаяОбработка = Ложь; Представление = Имя(); @@ -76,7 +87,8 @@ &Блестяшка Таймаут, &Блестяшка Теги, &Блестяшка Условия, - &Блестяшка Порядок) + &Блестяшка Порядок, + &Блестяшка Изоляция) _Тип = Тип; _Имя = Имя; @@ -88,5 +100,6 @@ _Теги = Списки.Скопировать(Теги); _Условия = Списки.Скопировать(Условия); _Порядок = Порядок; + _Изоляция = Изоляция; КонецПроцедуры diff --git "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\265\320\277\320\276\321\200\321\202\320\265\321\200JSON.os" "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\265\320\277\320\276\321\200\321\202\320\265\321\200JSON.os" index 671242b..ffa4466 100644 --- "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\265\320\277\320\276\321\200\321\202\320\265\321\200JSON.os" +++ "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\265\320\277\320\276\321\200\321\202\320\265\321\200JSON.os" @@ -1,13 +1,14 @@ -// Формирует отчет о прогоне в формате JSON: статистика тестов, проблемы -// (упавшие и сломанные тесты и наборы) и пропущенные тесты с причинами. -// Отчет машиночитаемый, используется MCP-инструментами (src/mcp). +// Формирует отчет о прогоне в формате JSON: статистика тестов, полный список +// тестов, проблемы (упавшие и сломанные тесты и наборы) и пропущенные тесты +// с причинами. Отчет машиночитаемый, используется MCP-инструментами (src/mcp) +// и исполнителем изоляции для вклейки результатов дочерних прогонов. #Использовать collectionos #Использовать "../../../shared" -Перем _ПутьКОтчету; +Перем _ПутьКОтчету; // Строка - Путь к формируемому файлу отчета -Перем _РепортерСтатистика; +Перем _РепортерСтатистика; // РепортерСтатистика - Статистика текущего прогона &ПодпискаНаСобытие("ИсполнениеТестПланКонец") Процедура ИсполнениеТестПланКонец(ТестПлан) Экспорт @@ -24,15 +25,17 @@ Проблемы = Новый Массив(); Пропущенные = Новый Массив(); + Тесты = Новый Массив(); Для Каждого Набор Из ТестПлан.Дети() Цикл - ОбработатьНабор(Набор, Набор.Имя(), Проблемы, Пропущенные); + ОбработатьНабор(Набор, Набор.Имя(), Проблемы, Пропущенные, Тесты); КонецЦикла; Отчет = Новый Структура(); Отчет.Вставить("statistics", Статистика()); Отчет.Вставить("problems", Проблемы); Отчет.Вставить("skipped_tests", Пропущенные); + Отчет.Вставить("tests", Тесты); ЗаписьJSON = Новый ЗаписьJSON(); ЗаписьJSON.ОткрытьФайл(_ПутьКОтчету); @@ -41,7 +44,7 @@ КонецПроцедуры -Процедура ОбработатьНабор(Определение, Знач ИмяНабора, Проблемы, Пропущенные) +Процедура ОбработатьНабор(Определение, Знач ИмяНабора, Проблемы, Пропущенные, Тесты) Результат = _РепортерСтатистика.Результат(Определение); Состояние = Результат.Состояние(); @@ -62,9 +65,9 @@ Для Каждого Дитя Из Определение.Дети() Цикл Если Дитя.ТипОпределения() = ТипыОпределенийТестов.Контейнер Тогда - ОбработатьНабор(Дитя, ИмяНабора, Проблемы, Пропущенные); + ОбработатьНабор(Дитя, ИмяНабора, Проблемы, Пропущенные, Тесты); Иначе - ОбработатьТест(Дитя, ИмяНабора, Проблемы, Пропущенные); + ОбработатьТест(Дитя, ИмяНабора, Проблемы, Пропущенные, Тесты); КонецЕсли; КонецЦикла; @@ -73,11 +76,13 @@ КонецПроцедуры -Процедура ОбработатьТест(Тест, Знач ИмяНабора, Проблемы, Пропущенные) +Процедура ОбработатьТест(Тест, Знач ИмяНабора, Проблемы, Пропущенные, Тесты) Результат = _РепортерСтатистика.Результат(Тест); Состояние = Результат.Состояние(); + Тесты.Добавить(ЗаписьТеста(ИмяНабора, Тест, Результат)); + Если Состояние = СостоянияВыполненияТестов.Ошибка Или Состояние = СостоянияВыполненияТестов.Сломан Тогда @@ -92,6 +97,35 @@ КонецПроцедуры +Функция ЗаписьТеста(Знач ИмяНабора, Тест, Результат) + + Запись = ЗаписьОтчета(ИмяНабора, Тест.Имя(), Результат); + + Запись.Вставить( + "duration_ms", + _РепортерСтатистика.СтатистикаТеста(Тест).ПолучитьИлиУмолчание("ВремяВыполнения", 0) + ); + + Если Результат.Состояние() = СостоянияВыполненияТестов.Ошибка + Или Результат.Состояние() = СостоянияВыполненияТестов.Сломан Тогда + + // Подробности со стеками есть только у проблем - + // причины пропусков и прерываний описываются строками + Запись.Вставить("message", ОписанияПричин(Результат)); + Запись.Вставить("details", ПодробныеПричины(Результат)); + + ИначеЕсли Результат.Состояние() <> СостоянияВыполненияТестов.Успех Тогда + Запись.Вставить("message", ОписанияПричин(Результат)); + КонецЕсли; // BSLLS:IfElseIfEndsWithElse-off + + Если Не ПустаяСтрока(Результат.Вывод()) Тогда + Запись.Вставить("output", Результат.Вывод()); + КонецЕсли; + + Возврат Запись; + +КонецФункции + Функция Статистика() СтатистикаПлана = _РепортерСтатистика.СтатистикаТестПлана(); diff --git "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\265\320\277\320\276\321\200\321\202\320\265\321\200\320\237\320\273\320\260\320\275JSON.os" "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\265\320\277\320\276\321\200\321\202\320\265\321\200\320\237\320\273\320\260\320\275JSON.os" new file mode 100644 index 0000000..f22c9e4 --- /dev/null +++ "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\240\320\265\320\277\320\276\321\200\321\202\320\265\321\200\320\237\320\273\320\260\320\275JSON.os" @@ -0,0 +1,62 @@ +// Записывает состав тест-плана в JSON-файл без результатов исполнения: +// канал передачи состава изолированного набора из дочернего процесса +// обнаружения родителю. + +// Записывает состав тест-плана в файл +// +// Параметры: +// ТестПлан - ТестПлан - Составленный тест-план +// Путь - Строка - Путь к формируемому файлу +// +Процедура ЗаписатьПлан(ТестПлан, Знач Путь) Экспорт + + Наборы = Новый Массив; + + Для каждого Набор Из ТестПлан.Дети() Цикл + + Тесты = Новый Массив; + + Для каждого Дитя Из Набор.Дети() Цикл + Тесты.Добавить(ЗаписьУзла(Дитя)); + КонецЦикла; + + ЗаписьНабора = Новый Структура; + ЗаписьНабора.Вставить("suite", Набор.Имя()); + ЗаписьНабора.Вставить("tests", Тесты); + + Наборы.Добавить(ЗаписьНабора); + + КонецЦикла; + + ЗаписьJSON = Новый ЗаписьJSON(); + ЗаписьJSON.ОткрытьФайл(Путь); + ЗаписатьJSON(ЗаписьJSON, Новый Структура("suites", Наборы)); + ЗаписьJSON.Закрыть(); + +КонецПроцедуры + +Функция ЗаписьУзла(Узел) + + Запись = Новый Структура; + Запись.Вставить("test", Узел.Имя()); + Запись.Вставить("method", Узел.ИмяМетода()); + + Если Узел.ТипОпределения() = ТипыОпределенийТестов.Контейнер Тогда + + Дети = Новый Массив; + + Для каждого Дитя Из Узел.Дети() Цикл + Дети.Добавить(ЗаписьУзла(Дитя)); + КонецЦикла; + + Запись.Вставить("children", Дети); + + КонецЕсли; + + Возврат Запись; + +КонецФункции + +&Желудь +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/src/core/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\243\321\200\320\276\320\262\320\275\320\270\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" "b/src/core/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\243\321\200\320\276\320\262\320\275\320\270\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" new file mode 100644 index 0000000..2d37de3 --- /dev/null +++ "b/src/core/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\243\321\200\320\276\320\262\320\275\320\270\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" @@ -0,0 +1,5 @@ +Перем Поделка Экспорт; +Перем Процесс Экспорт; + +Поделка = "Поделка"; +Процесс = "Процесс"; diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\321\217\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\321\217\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271.os" new file mode 100644 index 0000000..c92c111 --- /dev/null +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\275\320\275\320\276\321\202\320\260\321\206\320\270\321\217\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271.os" @@ -0,0 +1,45 @@ +// Аннотация выполняет тест или весь тестовый набор в изоляции: так, как будто +// набор прогнали только с этим тестом - в свежей поделке или в отдельном процессе. +// Снаружи это обычный тест: его статус, сообщение и вывод попадают в общий план. +// +// Нужна для тестов, которые иначе отравляют общий прогон: проверяют ошибки +// обнаружения, конфликты желудей, падающие импорты, либо требуют собственной +// поделки с отдельной конфигурацией. +// +// Вешается на тест или на конструктор `&ТестовыйНабор`. На наборе изолируется весь +// набор одним контекстом, на тесте - каждый такой тест своим. При изоляции и набора, +// и теста выигрывает набор. +// +// Параметры: +// Уровень - Строка - Уровень изоляции, см. УровниИзоляции: +// "Поделка" (по умолчанию) - тот же процесс, но свежая поделка; +// "Процесс" - отдельный дочерний процесс. +// Импорты - Строка - Только для уровня "Процесс": пути импортов через запятую, +// подключаемые в изолированном процессе перед файлом набора. Позволяет проверять +// импорты, которые отравляют текущий процесс уже при подключении. На уровне +// "Поделка" запрещены - импорт глобален для процесса и поделкой не изолируется. +// Совет - Строка - [Лямбда-выражение](https://github.com/oscript-library/lambdas) +// без параметров, должно вернуть [СоветДругогоМастера](https://github.com/autumn-library/autumn) +// для создания изолированной поделки. Лямбда захватывает объект тестового набора: +// ей доступны поля объекта и его экспортные методы. На уровне "Процесс" лямбда +// исполняется в дочернем процессе, поэтому значения деталек совета не ограничены +// сериализуемостью; экземпляр набора для захвата поднимается там напрямую +// через Новый - если конструктор набора требует параметров, лямбда исполняется +// без захвата (модули, импортированные файлом набора, доступны в любом случае). +// +// Пример: +// 1. +// // Набор целиком выполняется в свежей поделке, сконфигурированной советом +// &ТестовыйНабор +// &Изолированный(Совет = "() -> ГенераторДанных.СоветСОднимКоннектором()") +// Процедура ПриСозданииОбъекта(&Пластилин Поделка) Экспорт +// +// 2. +// // Тест выполняется в отдельном процессе с ядовитым импортом +// &Тест +// &Изолированный(Уровень = "Процесс", Импорты = "../ОшибочныеКлассы/ДваЖелудяСОднимИменем") +// Процедура ДваЖелудяСОднимИменемКонфликтуют() Экспорт +// +&Аннотация("Изолированный") +Процедура ПриСозданииОбъекта(Уровень = "Поделка", Импорты = "", Совет = "") +КонецПроцедуры diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" index 24d3489..dfb8896 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" @@ -15,7 +15,9 @@ // Функция Тестировать() Экспорт - ТестПлан = Обнаружить(); + // Состав изолированных наборов при исполнении не разворачивается заранее: + // исполнитель изоляции вклеивает в план фактические результаты их прогонов + ТестПлан = _ОбнаружительТестов.Обнаружить(); _ИсполнительТестов.Исполнить(ТестПлан); @@ -23,13 +25,14 @@ КонецФункции -// Составляет и возвращает тест план +// Составляет и возвращает тест план. Изолированные наборы разворачиваются +// в собственных средах - план показывает их фактический состав // // Возвращаемое значение: // ТестПлан - Составленный по окружению тест план // Функция Обнаружить() Экспорт - Возврат _ОбнаружительТестов.Обнаружить(); + Возврат _ОбнаружительТестов.Обнаружить(Истина); КонецФункции &Желудь diff --git "a/src/mcp/internal/\320\232\320\273\320\260\321\201\321\201\321\213/MCP\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\321\213\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" "b/src/mcp/internal/\320\232\320\273\320\260\321\201\321\201\321\213/MCP\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\321\213\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" index 181e8d4..89b553d 100644 --- "a/src/mcp/internal/\320\232\320\273\320\260\321\201\321\201\321\213/MCP\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\321\213\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" +++ "b/src/mcp/internal/\320\232\320\273\320\260\321\201\321\201\321\213/MCP\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\321\213\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" @@ -5,7 +5,6 @@ #Использовать fluent #Использовать collectionos -#Использовать "../../../cli/internal" #Использовать "../../../shared" Перем _Запускатель; // ЗапускательТестирования @@ -130,47 +129,11 @@ // ПутьКОтчету - Строка - Путь к файлу отчета // // Возвращаемое значение: -// Структура - Содержимое отчета: -// * statistics - Структура - Статистика прогона: -// ** total - Число - Тестов обнаружено -// ** passed - Число - Тестов успешно -// ** failed - Число - Тестов с ошибками утверждений -// ** broken - Число - Тестов сломано -// ** skipped - Число - Тестов пропущено -// ** aborted - Число - Тестов прервано -// ** duration_ms - Число - Длительность прогона в миллисекундах -// * problems - Массив из Структура - Упавшие и сломанные тесты и наборы: -// ** status - Строка - Статус: "failed" или "broken" -// ** suite - Строка - Имя тестового набора -// ** test - Строка - Имя теста, отсутствует для проблемы всего набора -// ** message - Строка - Краткие причины -// ** details - Строка - Подробные причины со стеками -// * skipped_tests - Массив из Структура - Пропущенные тесты и наборы: -// ** status - Строка - Статус: "skipped" или "aborted" -// ** suite - Строка - Имя тестового набора -// ** test - Строка - Имя теста, отсутствует для пропуска всего набора -// ** reason - Строка - Причины пропуска -// Неопределено - Файл отчета не существует или не разобран (прогон не состоялся -// или завершился аварийно, не дописав отчет) +// см. ЗапускательТестирования.ПрочитатьОтчет // Функция ПрочитатьОтчет(Знач ПутьКОтчету) Экспорт - Если НЕ (Новый Файл(ПутьКОтчету)).Существует() Тогда - Возврат Неопределено; - КонецЕсли; - - ЧтениеJSON = Новый ЧтениеJSON(); - ЧтениеJSON.ОткрытьФайл(ПутьКОтчету); - - Попытка - Отчет = ПрочитатьJSON(ЧтениеJSON); - Исключение - Отчет = Неопределено; - КонецПопытки; - - ЧтениеJSON.Закрыть(); - - Возврат Отчет; + Возврат _Запускатель.ПрочитатьОтчет(ПутьКОтчету); КонецФункции diff --git "a/src/cli/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" "b/src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" similarity index 55% rename from "src/cli/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" rename to "src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" index 21f77b9..3c2a2a4 100644 --- "a/src/cli/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" +++ "b/src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\321\217.os" @@ -1,14 +1,14 @@ // Общая логика запуска прогонов дочерним процессом oscript через точку входа // ядра: тестирование и обнаружение тестов, сбор покрытия кода. Используется -// командами консольного приложения (execute, discover) и MCP-инструментами. +// командами консольного приложения (execute, discover), MCP-инструментами +// и исполнителем изоляции ядра (процессный уровень изоляции). #Использовать 1commands #Использовать tempfiles #Использовать coverage #Использовать fs -#Использовать "../../../shared" -Перем _Служебный; // КонсольноеПриложениеТестированиеСлужебный +Перем _ПостроительТочкиВхода; // ПостроительТочкиВхода - Сборщик точки входа дочернего прогона // Возвращает параметры запуска прогона со значениями по умолчанию // @@ -19,6 +19,15 @@ // * ПортОтладки - Число - Порт сервера отладки прогона, 0 - без отладки // * ПутьКОтчетуGenericCoverage - Строка - Путь к формируемому отчету покрытия GenericCoverage // * ПутьКОтчетуCobertura - Строка - Путь к формируемому отчету покрытия Cobertura +// * ПутьКСтатистикеПокрытия - Строка - Путь к формируемому файлу статистики покрытия +// (для изолированных прогонов: файл пишется в каталог статистики +// родительского прогона и мержится им в общие отчеты покрытия) +// * Импорты - Массив из Строка - Имена библиотек или полные пути, подключаемые +// в прогоне после старта его поделки (для изолированных прогонов) +// * ЛямбдаСовета - Строка - Лямбда-выражение совета создания поделки прогона, +// исполняется в прогоне с захватом экземпляра набора, поднятого +// напрямую через Новый, пустая строка - совет по умолчанию +// * РаскладкаЛога - Строка - Раскладка лога прогона, по умолчанию JSONРаскладкаСообщения // Функция ПараметрыЗапуска() Экспорт @@ -29,6 +38,10 @@ Параметры.Вставить("ПортОтладки", 0); Параметры.Вставить("ПутьКОтчетуGenericCoverage", ""); Параметры.Вставить("ПутьКОтчетуCobertura", ""); + Параметры.Вставить("ПутьКСтатистикеПокрытия", ""); + Параметры.Вставить("Импорты", Новый Массив); + Параметры.Вставить("ЛямбдаСовета", ""); + Параметры.Вставить("РаскладкаЛога", "JSONРаскладкаСообщения"); Возврат Параметры; @@ -104,6 +117,93 @@ КонецФункции +// Выполняет обнаружение тестов дочерним прогоном с записью состава плана +// в JSON-файл (РепортерПланJSON) - без вывода в консоль и без исполнения тестов +// +// Параметры: +// Детальки - Соответствие из КлючИЗначение - Детальки поделки прогона (фильтры тестов): +// * Ключ - Строка - Имя детальки поделки, например "ФайлыТестов" +// * Значение - Строка, Булево, Массив из Строка - Значение детальки +// ПутьКПлану - Строка - Путь к формируемому файлу состава плана +// Лог - Лог - Лог вызывающего компонента +// Параметры - см. ПараметрыЗапуска, Неопределено - значения по умолчанию +// +// Возвращаемое значение: +// Структура - Результат прогона: +// * КодВозврата - Число - Код возврата дочернего процесса +// * Вывод - Строка - Захваченный вывод прогона +// +Функция ВыполнитьОбнаружениеПлана(Детальки, Знач ПутьКПлану, Лог, Знач Параметры = Неопределено) Экспорт + + ВыполняемаяКоманда = СтрШаблон( + " ТестПлан = МенеджерТестирования.Обнаружить(); + | Поделка.НайтиЖелудь(""РепортерПланJSON"").ЗаписатьПлан(ТестПлан, ""%1""); + |", + СтрЗаменить(ПутьКПлану, "\", "/") + ); + + Возврат ВыполнитьПрогон(Детальки, ВыполняемаяКоманда, Лог, Параметры); + +КонецФункции + +// Читает JSON-отчет прогона (РепортерJSON) +// +// Параметры: +// ПутьКОтчету - Строка - Путь к файлу отчета +// +// Возвращаемое значение: +// Структура - Содержимое отчета: +// * statistics - Структура - Статистика прогона: +// ** total - Число - Тестов обнаружено +// ** passed - Число - Тестов успешно +// ** failed - Число - Тестов с ошибками утверждений +// ** broken - Число - Тестов сломано +// ** skipped - Число - Тестов пропущено +// ** aborted - Число - Тестов прервано +// ** duration_ms - Число - Длительность прогона в миллисекундах +// * problems - Массив из Структура - Упавшие и сломанные тесты и наборы: +// ** status - Строка - Статус: "failed" или "broken" +// ** suite - Строка - Имя тестового набора +// ** test - Строка - Имя теста, отсутствует для проблемы всего набора +// ** message - Строка - Краткие причины +// ** details - Строка - Подробные причины со стеками +// * skipped_tests - Массив из Структура - Пропущенные тесты и наборы: +// ** status - Строка - Статус: "skipped" или "aborted" +// ** suite - Строка - Имя тестового набора +// ** test - Строка - Имя теста, отсутствует для пропуска всего набора +// ** reason - Строка - Причины пропуска +// * tests - Массив из Структура - Все выполненные тесты в порядке плана: +// ** status - Строка - Статус: "passed", "failed", "broken", "skipped" или "aborted" +// ** suite - Строка - Имя тестового набора +// ** test - Строка - Имя теста +// ** duration_ms - Число - Длительность теста в миллисекундах +// ** message - Строка - Краткие причины, отсутствует у успешных +// ** details - Строка - Подробные причины со стеками, отсутствует у успешных +// ** output - Строка - Захваченный вывод теста, отсутствует при пустом выводе +// Неопределено - Файл отчета не существует или не разобран (прогон не состоялся +// или завершился аварийно, не дописав отчет) +// +Функция ПрочитатьОтчет(Знач ПутьКОтчету) Экспорт + + Если НЕ (Новый Файл(ПутьКОтчету)).Существует() Тогда + Возврат Неопределено; + КонецЕсли; + + ЧтениеJSON = Новый ЧтениеJSON(); + ЧтениеJSON.ОткрытьФайл(ПутьКОтчету); + + Попытка + Отчет = ПрочитатьJSON(ЧтениеJSON); + Исключение + Отчет = Неопределено; + КонецПопытки; + + ЧтениеJSON.Закрыть(); + + Возврат Отчет; + +КонецФункции + // Возвращает существующий путь в каноническом регистре файловой системы. // Также разворачивает короткие имена формата 8.3 в полные // @@ -153,6 +253,15 @@ МенеджерВременныхФайлов = Новый МенеджерВременныхФайлов(); МенеджерВременныхФайлов.БазовыйКаталог = МенеджерВременныхФайлов.СоздатьКаталог(); + Если ФормироватьПокрытие Тогда + // Общий каталог статистики покрытия: основной прогон пишет туда main.json, + // его изолированные дочерние процессы - stat_*.json (путь к каталогу + // прогон получает деталькой), после прогона статистика мержится в отчеты + КаталогСтатистики = МенеджерВременныхФайлов.НовоеИмяФайла(); + ФС.ОбеспечитьПустойКаталог(КаталогСтатистики); + Детальки.Вставить("КаталогСтатистикиПокрытия", КаталогСтатистики); + КонецЕсли; + ИсходныйКаталог = ТекущийКаталог(); // Регистр пути важен: отчет покрытия фильтруется по каталогу исходников @@ -164,11 +273,12 @@ Попытка - ИмяТочкиВхода = _Служебный.СоздатьТочкуВхода( + ИмяТочкиВхода = _ПостроительТочкиВхода.СоздатьТочкуВхода( Детальки, МенеджерВременныхФайлов, Лог, - ВыполняемаяКоманда + ВыполняемаяКоманда, + Параметры ); Команда = Новый Команда(); @@ -177,8 +287,10 @@ Команда.УстановитьРабочийКаталог(ТекущийКаталог()); Если ФормироватьПокрытие Тогда - ПутьКСтатистике = МенеджерВременныхФайлов.НовоеИмяФайла("json"); - Команда.ДобавитьПараметр("-codestat=" + ПутьКСтатистике); + Команда.ДобавитьПараметр("-codestat=" + ОбъединитьПути(КаталогСтатистики, "main.json")); + ИначеЕсли ЗначениеЗаполнено(Параметры.ПутьКСтатистикеПокрытия) Тогда + // Изолированный прогон: статистика уходит в каталог родительского прогона + Команда.ДобавитьПараметр("-codestat=" + Параметры.ПутьКСтатистикеПокрытия); КонецЕсли; Если Параметры.ПортОтладки > 0 Тогда @@ -199,7 +311,7 @@ Вывод = Команда.ПолучитьВывод(); Если ФормироватьПокрытие Тогда - СформироватьОтчетыПокрытия(ПутьКСтатистике, Параметры, МенеджерВременныхФайлов); + СформироватьОтчетыПокрытия(КаталогСтатистики, Параметры); КонецЕсли; Исключение @@ -215,26 +327,26 @@ КонецФункции -Процедура СформироватьОтчетыПокрытия(Знач ПутьКСтатистике, Знач Параметры, МенеджерВременныхФайлов) +Процедура СформироватьОтчетыПокрытия(Знач КаталогСтатистики, Знач Параметры) - Если НЕ (Новый Файл(ПутьКСтатистике)).Существует() Тогда + ГлавныйФайлСтатистики = ОбъединитьПути(КаталогСтатистики, "main.json"); + + Если НЕ (Новый Файл(ГлавныйФайлСтатистики)).Существует() Тогда Возврат; КонецЕсли; - ВременныйКаталог = МенеджерВременныхФайлов.НовоеИмяФайла(); - ФС.ОбеспечитьПустойКаталог(ВременныйКаталог); - ПроцессорГенерации = Новый ГенераторОтчетаПокрытия(); - ПроцессорГенерации.ФайлСтатистики(ПутьКСтатистике) - .РабочийКаталог(ВременныйКаталог) - .ИмяФайлаСтатистики("*") + // Шаблон stat_*.json собирает статистику изолированных дочерних процессов + ПроцессорГенерации.ФайлСтатистики(ГлавныйФайлСтатистики) + .РабочийКаталог(КаталогСтатистики) + .ИмяФайлаСтатистики("stat_*.json") .GenericCoverage("genericCoverage.xml") .Cobertura("cobertura.xml") .Сформировать(); - СкопироватьОтчет(ОбъединитьПути(ВременныйКаталог, "genericCoverage.xml"), Параметры.ПутьКОтчетуGenericCoverage); - СкопироватьОтчет(ОбъединитьПути(ВременныйКаталог, "cobertura.xml"), Параметры.ПутьКОтчетуCobertura); + СкопироватьОтчет(ОбъединитьПути(КаталогСтатистики, "genericCoverage.xml"), Параметры.ПутьКОтчетуGenericCoverage); + СкопироватьОтчет(ОбъединитьПути(КаталогСтатистики, "cobertura.xml"), Параметры.ПутьКОтчетуCobertura); КонецПроцедуры @@ -250,6 +362,6 @@ КонецПроцедуры &Желудь -Процедура ПриСозданииОбъекта(&Пластилин КонсольноеПриложениеТестированиеСлужебный) - _Служебный = КонсольноеПриложениеТестированиеСлужебный; +Процедура ПриСозданииОбъекта(&Пластилин ПостроительТочкиВхода) + _ПостроительТочкиВхода = ПостроительТочкиВхода; КонецПроцедуры diff --git "a/src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\201\321\202\321\200\320\276\320\270\321\202\320\265\320\273\321\214\320\242\320\276\321\207\320\272\320\270\320\222\321\205\320\276\320\264\320\260.os" "b/src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\201\321\202\321\200\320\276\320\270\321\202\320\265\320\273\321\214\320\242\320\276\321\207\320\272\320\270\320\222\321\205\320\276\320\264\320\260.os" new file mode 100644 index 0000000..3f8fd5e --- /dev/null +++ "b/src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\201\321\202\321\200\320\276\320\270\321\202\320\265\320\273\321\214\320\242\320\276\321\207\320\272\320\270\320\222\321\205\320\276\320\264\320\260.os" @@ -0,0 +1,504 @@ +// Сборка точки входа дочернего прогона: временный сценарий с импортами тестов, +// загрузкой ядра и запуском тестирования, а также конфигурационный файл oscript +// и файл значений деталек поделки прогона. + +#Использовать fs +#Использовать logos +#Использовать decorator +#Использовать fluent +#Использовать collectionos + +Функция СоздатьТочкуВхода( + Детальки, + МенеджерВременныхФайлов, + Лог, + ВыполняемаяКоманда, + Знач ПараметрыЗапуска = Неопределено) Экспорт + + Если ПараметрыЗапуска = Неопределено Тогда + ПараметрыЗапуска = Новый Структура(); + КонецЕсли; + + ДополнитьПараметрыУмолчаниями(ПараметрыЗапуска); + + Если Детальки.Получить("КаталогиТестов").Количество() = 0 + И Детальки.Получить("ФайлыТестов").Количество() = 0 Тогда + Детальки.Получить("КаталогиТестов").Добавить("./tests"); + КонецЕсли; + + ФайлыТестовРазвернутые = ТестированиеСлужебный.РазвернутьСписокТестовыхНаборов( + Детальки.Получить("КаталогиТестов"), + Детальки.Получить("ФайлыТестов"), + Детальки.Получить("ИскатьВПодкаталогах") + ); + + Импорты = СобратьИмпортыТестов(ФайлыТестовРазвернутые); + + Лог.Отладка("Импорты считанные из тестов (Импорт=ПодключаетсяПоПути): %1", Импорты); + + КаталогПриложения = ФС.НормализоватьПуть( + ОбъединитьПути(ОбъединитьПути(ТекущийСценарий().Каталог, "..", ".."), "core") + ); + + ПостроительСценария = Новый ПостроительДекоратора() + .Импорт(Новый Импорт(КаталогПриложения).ТипПодключения(ТипыПодключенияБиблиотек.ПоПути)); + + Если ЗначениеЗаполнено(ПараметрыЗапуска.ЛямбдаСовета) Тогда + ПостроительСценария.Импорт(Новый Импорт("lambdas")); + КонецЕсли; + + ТекстСценария = ПостроительСценария + .ШагИнициализации( + Новый ШагИнициализации(СтрШаблон(" + |%4 + | Совет.ДополнительныйКаталогПоискаФайлаСоЗначениямиДеталек(""%2""); + | + | Поделка = Новый Поделка(Совет); + | Поделка.ЗапуститьПриложение(); + |%3 + | МенеджерТестирования = Поделка.НайтиЖелудь(""МенеджерТестирования""); + | + | %1 + |", + ВыполняемаяКоманда, + МенеджерВременныхФайлов.БазовыйКаталог, + ЗагрузкаОтложенныхИмпортов(ПараметрыЗапуска.Импорты, Лог), + СозданиеСовета(ПараметрыЗапуска.ЛямбдаСовета, Детальки) + )) + ) + .ТекстСценария(); + + Лог.Отладка("Текст сценария запуска тестирования: %1", ТекстСценария); + + ИмяВременногоФайла = МенеджерВременныхФайлов.НовоеИмяФайла("os"); + ЗаписьТекста = Новый ЗаписьТекста(); + ЗаписьТекста.Открыть(ИмяВременногоФайла); + ЗаписьТекста.Записать(ТекстСценария); + ЗаписьТекста.Закрыть(); + + ТочкаВхода = Новый ПостроительДекоратора(); + + Импорты.ЗаменитьВсе( + "Импорт, ПоПути -> + | Результат = Новый Импорт("""" + Импорт + """"); + | Если ПоПути Тогда + | Результат.ТипПодключения(ТипыПодключенияБиблиотек.ПоПути); + | КонецЕсли; + | + | Возврат Результат; + |" + ); + + Импорты.Значения() + .ДляКаждого("Импорт -> ТочкаВхода.Импорт(Импорт)", Новый Структура("ТочкаВхода", ТочкаВхода)); + + ТекстСценария = ТочкаВхода + .ШагИнициализации( + Новый ШагИнициализации(СтрШаблон(" + | + | // После импортов из тестов загружаем ядро и запускаем тесты + | ЗагрузитьСценарий(""%1""); + | + |", + ИмяВременногоФайла + )) + ) + .ТекстСценария(); + + Лог.Отладка("Текст сценария точки входа для запуска тестирования: %1", ТекстСценария); + + ИмяВременногоФайла = МенеджерВременныхФайлов.НовоеИмяФайла("os"); + ЗаписьТекста = Новый ЗаписьТекста(); + ЗаписьТекста.Открыть(ИмяВременногоФайла); + ЗаписьТекста.Записать(ТекстСценария); + ЗаписьТекста.Закрыть(); + + КонфигурационныйФайл(Детальки, МенеджерВременныхФайлов, Лог); + ЗаписатьДетальки(Детальки, МенеджерВременныхФайлов, ПараметрыЗапуска); + + Возврат ИмяВременногоФайла; + +КонецФункции + +// Собирает импорты из шапок файлов тестов: импорты по пути нормализуются +// относительно каталога файла теста, импорт самого oneunit пропускается +// +// Параметры: +// ФайлыТестов - СписокМассив из Файл - Файлы тестовых наборов +// +// Возвращаемое значение: +// КартаСоответствие - Импорты тестов: +// * Ключ - Строка - Имя библиотеки или полный путь импорта +// * Значение - Булево - Истина, если импорт подключается по пути +// +Функция СобратьИмпортыТестов(ФайлыТестов) + + Импорты = Новый КартаСоответствие(); + + ТекущийКаталог = ТекущийКаталог(); + + Для каждого Тест Из ФайлыТестов Цикл + + ЧтениеТекста = Новый ЧтениеТекста(Тест.ПолноеИмя); + + УстановитьТекущийКаталог(Тест.Путь); + + Строка = ЧтениеТекста.ПрочитатьСтроку(); + + Пока Строка <> Неопределено Цикл + + Если СтрНачинаетсяС(Строка, "#Использовать ") Тогда + + ПоПути = Ложь; + + Импорт = СтрЗаменить(Строка, "#Использовать ", ""); + + Если СокрЛП(НРег(Импорт)) = "oneunit" Тогда + Строка = ЧтениеТекста.ПрочитатьСтроку(); + Продолжить; + КонецЕсли; + + Если СтрНайти(Импорт, """") > 0 Тогда + Импорт = ФС.НормализоватьПуть(СтрЗаменить(Импорт, """", "")); + ПоПути = Истина; + КонецЕсли; + + Импорты.Вставить(Импорт, ПоПути); + + КонецЕсли; + + Строка = ЧтениеТекста.ПрочитатьСтроку(); + + КонецЦикла; + + ЧтениеТекста.Закрыть(); + + УстановитьТекущийКаталог(ТекущийКаталог); + + КонецЦикла; + + Возврат Импорты; + +КонецФункции + +// Формирует фрагмент сценария создания совета поделки прогона: совет по умолчанию +// либо исполнение лямбды совета изолированного прогона. Через границу процесса +// переезжает только текст лямбды, поэтому детальки совета не ограничены +// сериализуемостью. Лямбда исполняется в контексте экземпляра набора, поднятого +// напрямую через Новый - без поделки; если конструктор набора требует параметров, +// лямбда исполняется без захвата (модули, импортированные файлами тестов, +// доступны в любом случае - их подключает сама точка входа) +// +// Параметры: +// ЛямбдаСовета - Строка - Лямбда-выражение совета создания поделки прогона, +// пустая строка - совет по умолчанию +// Детальки - Соответствие из КлючИЗначение - Детальки поделки прогона +// +// Возвращаемое значение: +// Строка - Фрагмент сценария, объявляющий переменную Совет +// +Функция СозданиеСовета(Знач ЛямбдаСовета, Детальки) + + Если Не ЗначениеЗаполнено(ЛямбдаСовета) Тогда + Возврат " + | Совет = Новый СоветДругогоМастера(); + |"; + КонецЕсли; + + ЛитералЛямбды = ЛитералСтроки(ЛямбдаСовета); + + ФайлНабора = Новый Файл(Детальки.Получить("ФайлыТестов").Получить(0)); + ПутьНабора = СтрЗаменить(ФайлНабора.ПолноеИмя, "\", "/"); + ИмяКлассаНабора = ТестированиеСлужебный.ИмяКлассаТестовогоНабора(ФайлНабора.ИмяБезРасширения); + + Возврат СтрШаблон(" + | // Захватываем экземпляр набора: лямбда совета видит его поля и методы. + | // Конструктор с параметрами напрямую не создается - тогда без захвата + | Попытка + | ПодключитьСценарий(""%1"", ""%2""); + | ЭкземплярНабора = Новый %2; + | Исключение + | ЭкземплярНабора = Неопределено; + | КонецПопытки; + | + | ПостроительСовета = Лямбда.Выражение(""%3"") + | .Интерфейс(ФункциональныеИнтерфейсы.Вызываемый()); + | + | Если ЭкземплярНабора <> Неопределено Тогда + | ПостроительСовета = ПостроительСовета.ЗахватитьОбъект(ЭкземплярНабора); + | КонецЕсли; + | + | Совет = ПостроительСовета.ВДействие().Выполнить(); + | + | // Копируем в изменяемое соответствие: совет мог отдать фиксированное + | Если Совет.ЗначенияДеталек() <> Неопределено Тогда + | Совет.ЗначенияДеталек(Соответствия.Скопировать(Совет.ЗначенияДеталек())); + | КонецЕсли; + |", + ПутьНабора, + ИмяКлассаНабора, + ЛитералЛямбды + ); + +КонецФункции + +// Экранирует текст для вставки строковым литералом в генерируемый сценарий: +// кавычки удваиваются, переносы строк продолжаются символом | +// +// Параметры: +// Текст - Строка - Вставляемый текст +// +// Возвращаемое значение: +// Строка - Содержимое строкового литерала +// +Функция ЛитералСтроки(Знач Текст) + + Возврат СтрЗаменить( + СтрЗаменить(Текст, """", """"""), + Символы.ПС, + Символы.ПС + " |" + ); + +КонецФункции + +// Формирует фрагмент сценария загрузки отложенных импортов изолированного прогона. +// Импорты подключаются после старта поделки прогона: библиотека, ломающая +// сканирование типов, не должна помешать подняться самой поделке +// +// Параметры: +// Импорты - Массив из Строка - Имена библиотек или полные пути импортов +// Лог - Лог - Лог вызывающего компонента +// +// Возвращаемое значение: +// Строка - Строка вызова ЗагрузитьСценарийИзСтроки или пустая строка без импортов +// +Функция ЗагрузкаОтложенныхИмпортов(Знач Импорты, Лог) + + Если Импорты.Количество() = 0 Тогда + Возврат ""; + КонецЕсли; + + ПостроительИмпортов = Новый ПостроительДекоратора(); + + Для каждого Импорт Из Импорты Цикл + + ОбъектИмпорта = Новый Импорт(Импорт); + + Если СтрНайти(Импорт, "/") > 0 Или СтрНайти(Импорт, "\") > 0 Тогда + ОбъектИмпорта.ТипПодключения(ТипыПодключенияБиблиотек.ПоПути); + КонецЕсли; + + ПостроительИмпортов.Импорт(ОбъектИмпорта); + + КонецЦикла; + + ТекстИмпортов = ПостроительИмпортов.ТекстСценария(); + + Лог.Отладка("Отложенные импорты изолированного прогона: %1", ТекстИмпортов); + + ЛитералИмпортов = ЛитералСтроки(ТекстИмпортов); + + Возврат СтрШаблон( + " + | // Отложенные импорты изолированного прогона + | ЗагрузитьСценарийИзСтроки(""%1""); + |", + ЛитералИмпортов + ); + +КонецФункции + +Функция ЗаписатьДетальки(Детальки, МенеджерВременныхФайлов, ПараметрыЗапуска) + + ЗначенияДеталек = Новый Соответствие; + + Для Каждого Деталька Из Детальки Цикл + + Если ТипЗнч(Деталька.Значение) = Тип("Массив") + Или ТипЗнч(Деталька.Значение) = Тип("ФиксированныйМассив") Тогда + + // СтрСоединить не принимает ФиксированныйМассив + Значение = СтрСоединить(Массивы.Скопировать(Деталька.Значение), ","); + + Иначе + Значение = Деталька.Значение; + КонецЕсли; + + ЗначенияДеталек.Вставить(Деталька.Ключ, Значение); + + КонецЦикла; + + ПутьКФайлуДеталек = ОбъединитьПути(МенеджерВременныхФайлов.БазовыйКаталог, "autumn-properties.json"); + + Настройки = Новый Соответствие(НастройкиЛогоса("core", ПараметрыЗапуска.РаскладкаЛога)); + Настройки.Вставить("OneUnit", ЗначенияДеталек); + + ЗаписьJSON = Новый ЗаписьJSON(); + ЗаписьJSON.ОткрытьФайл(ПутьКФайлуДеталек); + ЗаписатьJSON(ЗаписьJSON, Настройки); + ЗаписьJSON.Закрыть(); + + Возврат ПутьКФайлуДеталек; + +КонецФункции + +Процедура ДополнитьПараметрыУмолчаниями(ПараметрыЗапуска) + + Если Не ПараметрыЗапуска.Свойство("Импорты") Тогда + ПараметрыЗапуска.Вставить("Импорты", Новый Массив); + КонецЕсли; + + Если Не ПараметрыЗапуска.Свойство("ЛямбдаСовета") Тогда + ПараметрыЗапуска.Вставить("ЛямбдаСовета", ""); + КонецЕсли; + + Если Не ПараметрыЗапуска.Свойство("РаскладкаЛога") Тогда + ПараметрыЗапуска.Вставить("РаскладкаЛога", "JSONРаскладкаСообщения"); + КонецЕсли; + +КонецПроцедуры + +Функция КонфигурационныйФайл(Детальки, МенеджерВременныхФайлов, Лог) + + ПутиККонфигурационнымФайлам = Новый СписокМассив; + ПутиККонфигурационнымФайлам.Добавить(ТекущийКаталог()); + + Для каждого Путь Из Детальки.Получить("КаталогиТестов") Цикл + ПутиККонфигурационнымФайлам.Добавить(Путь); + КонецЦикла; + + Для каждого Путь Из Детальки.Получить("ФайлыТестов") Цикл + ПутиККонфигурационнымФайлам.Добавить(Новый Файл(Путь).Путь); + КонецЦикла; + + ДанныеКонфигурационногоФайла = ПутиККонфигурационнымФайлам.ПроцессорКоллекции() + .Различные() + .Обработать("Путь -> ОбъединитьПути(Путь, ""oscript.cfg"")") + .Фильтровать("Путь -> ФС.ФайлСуществует(Путь)") + .Обработать(Новый Действие(ЭтотОбъект, "ПрочитатьКонфигурационныйФайл")) + .Развернуть("ДанныеФайла -> ДанныеФайла.КлючиИЗначения().ПроцессорКоллекции()") + .Сократить( + "Результат, Настройка -> + | + | Результат.Слить( + | Настройка.Ключ, + | Настройка.Значение, + | "" + | |Старое, Новое -> + | | Если ТипЗнч(Старое) = Тип(""""МножествоСоответствие"""") Тогда + | | Старое.ДобавитьВсе(Новое); + | | КонецЕсли; + | | + | | Возврат Старое; + | |"" + | ); + | + | Возврат Результат; + |", + Новый КартаСоответствие() + ); + + ДанныеКонфигурационногоФайла.Слить( + "lib.additional", + Новый МножествоСоответствие( + Множества.ИзЭлементов(ПолучитьЗначениеСистемнойНастройки("lib.additional")) + ), + "Старое, Новое -> Старое.ДобавитьВсе(Новое); Возврат Старое;" + ); + + ДанныеКонфигурационногоФайла.ВычислитьЕслиПрисутствует( + "lib.additional", + "Ключ, Значение -> Значение.Удалить(СистемныйКаталог); Возврат Значение;", + Новый Структура("СистемныйКаталог", ДанныеКонфигурационногоФайла.Получить("lib.system").Иначе_("")) + ); + + Лог.Отладка("Данные конфигурационного файла: %1", ДанныеКонфигурационногоФайла); + + ПутьККонфигурационномуФайлу = ОбъединитьПути(МенеджерВременныхФайлов.БазовыйКаталог, "oscript.cfg"); + + ЗаписьТекста = Новый ЗаписьТекста(); + ЗаписьТекста.Открыть(ПутьККонфигурационномуФайлу); + + ДанныеКонфигурационногоФайла.ДляКаждого( + "Ключ, Значение -> ЗаписьТекста.ЗаписатьСтроку( + | Ключ + ""="" + + | ?(ТипЗнч(Значение) = Тип(""МножествоСоответствие"") Или ТипЗнч(Значение) = Тип(""ФиксированноеМножество""), + | СтрСоединить(Значение.ВМассив(), "";""), + | Значение + | ));", + Новый Структура("ЗаписьТекста", ЗаписьТекста) + ); + + ЗаписьТекста.Закрыть(); + + Возврат ПутьККонфигурационномуФайлу; + +КонецФункции + +Функция ПрочитатьКонфигурационныйФайл(Путь) Экспорт + + ТекущийКаталог = ТекущийКаталог(); + + Результат = Новый КартаСоответствие(); + + ЧтениеТекста = Новый ЧтениеТекста(Путь); + + ПрочитаннаяСтрока = ЧтениеТекста.ПрочитатьСтроку(); + + Пока ПрочитаннаяСтрока <> Неопределено Цикл + + ПрочитаннаяСтрока = СтрРазделить(ПрочитаннаяСтрока, "="); + + Ключ = ПрочитаннаяСтрока[0]; + + УстановитьТекущийКаталог(Новый Файл(Путь).Путь); + + Если СтрНачинаетсяС(Ключ, "lib.") Тогда + + Если СтрНачинаетсяС(Ключ, "lib.additional") Тогда + + Значение = ПроцессорыКоллекций.ИзКоллекции(СтрРазделить(ПрочитаннаяСтрока[1], ";", Ложь)) + .Фильтровать("Путь -> ФС.КаталогСуществует(Путь)") + .Обработать("Путь -> ФС.НормализоватьПуть(Путь)") + .Получить("МножествоСоответствие"); + + Иначе + Значение = ?(ФС.КаталогСуществует(ПрочитаннаяСтрока[1]), ФС.НормализоватьПуть(ПрочитаннаяСтрока[1]), ""); + КонецЕсли; + + Иначе + Значение = ПрочитаннаяСтрока[1]; + КонецЕсли; + + УстановитьТекущийКаталог(ТекущийКаталог); + + Если ЗначениеЗаполнено(Значение) Тогда + Результат.Вставить(Ключ, Значение); + КонецЕсли; + + ПрочитаннаяСтрока = ЧтениеТекста.ПрочитатьСтроку(); + + КонецЦикла; + + ЧтениеТекста.Закрыть(); + + Возврат Результат; + +КонецФункции + +Функция НастройкиЛогоса(Компонент, Раскладка) Экспорт + + Возврат Соответствия.ИзЭлементов( + "logos.logger", Соответствия.ИзЭлементов( + "oscript.lib.oneunit." + Компонент, Соответствия.ИзЭлементов( + "level", "DEFAULT", + "classlayout", Раскладка + ) + ) + ); + +КонецФункции + +&Желудь +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/src/shared/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" "b/src/shared/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" index 9acf38a..8578711 100644 --- "a/src/shared/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" +++ "b/src/shared/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\320\275\320\270\320\265\320\241\320\273\321\203\320\266\320\265\320\261\320\275\321\213\320\271.os" @@ -4,6 +4,8 @@ Перем _СтандартныйПотокВывода; // Поток - системный stdout, открывается лениво Перем _СтандартныйПотокОшибок; // Поток - системный stderr, открывается лениво +Перем _РегулярноеВыражениеИмяКласса; // РегулярноеВыражение - Недопустимые символы имени класса, создается лениво + // Перенаправляет консольный вывод и вывод ошибок в переданный поток. // Перехваты могут быть вложенными: модуль ведёт единый на процесс стек потоков, // поэтому вложенный перехват по завершении вернёт вывод в объемлющий поток, @@ -41,6 +43,49 @@ КонецПроцедуры +// Возвращает имя класса, под которым файл тестового набора регистрируется +// в системе типов: недопустимые для идентификатора символы имени файла +// заменяются подчеркиванием +// +// Параметры: +// ИмяФайлаБезРасширения - Строка - Имя файла набора без расширения +// +// Возвращаемое значение: +// Строка - Имя класса тестового набора +// +Функция ИмяКлассаТестовогоНабора(Знач ИмяФайлаБезРасширения) Экспорт + + Если _РегулярноеВыражениеИмяКласса = Неопределено Тогда + _РегулярноеВыражениеИмяКласса = Новый РегулярноеВыражение("((?<=^)[^a-zA-Z_а-яА-ЯёЁ]|[^a-zA-Z0-9_а-яА-ЯёЁ])"); + КонецЕсли; + + Возврат "oneunit_testsuite_" + _РегулярноеВыражениеИмяКласса.Заменить(ИмяФайлаБезРасширения, "_"); + +КонецФункции + +// Возвращает содержимое потока перехваченного вывода, прочитанное с начала +// в кодировке выходного потока консоли +// +// Параметры: +// Поток - ПотокВПамяти - Поток перехваченного вывода +// +// Возвращаемое значение: +// Строка - Содержимое потока +// +Функция ПрочитатьПоток(Поток) Экспорт + + Поток.СброситьБуферы(); + Поток.Перейти(0); + + ЧтениеТекста = Новый ЧтениеТекста(); + ЧтениеТекста.Открыть(Поток, Консоль.КодировкаВыходногоПотока); + Результат = ЧтениеТекста.Прочитать(); + ЧтениеТекста.Закрыть(); + + Возврат ?(Результат = Неопределено, "", Результат); + +КонецФункции + Функция РазвернутьСписокТестовыхНаборов(Знач КаталогиТестов, Знач ФайлыТестов, Знач ИскатьВПодкаталогах) Экспорт Результат = Новый СписокМассив; @@ -79,6 +124,12 @@ Функция ПодробноеОписаниеОшибки(Префикс, ИнформацияОбОшибке) Экспорт + Если ЭтоПеренесеннаяПричина(ИнформацияОбОшибке) Тогда + // Причина перенесена из дочернего прогона: описание уже сформировано там, + // модуль, строка и стек обертки - служебные, показывать их незачем + Возврат ИнформацияОбОшибке.Параметры.ПеренесенноеОписание; + КонецЕсли; + ОписаниеОшибки = СтрШаблон( "%1 (%2:%3)", КраткоеОписаниеОшибки(ИнформацияОбОшибке), @@ -109,6 +160,14 @@ КонецФункции +Функция ЭтоПеренесеннаяПричина(ИнформацияОбОшибке) + + Возврат ЗначениеЗаполнено(ИнформацияОбОшибке.Параметры) + И ТипЗнч(ИнформацияОбОшибке.Параметры) = Тип("Структура") + И ИнформацияОбОшибке.Параметры.Свойство("ПеренесенноеОписание"); + +КонецФункции + Функция СтандартныйПотокВывода() Если _СтандартныйПотокВывода = Неопределено Тогда diff --git "a/tests/MCP\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\321\213.os" "b/tests/MCP\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\321\213.os" index 03bfbc2..bb8022f 100644 --- "a/tests/MCP\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\321\213.os" +++ "b/tests/MCP\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\321\213.os" @@ -9,7 +9,7 @@ #Использовать collectionos #Использовать logos #Использовать "./support" -#Использовать "../src/cli/internal" +#Использовать "../src/shared" #Использовать "../src/mcp/internal" &ПослеВсех @@ -460,7 +460,7 @@ // Вне поделки MCP-сервера желудь собирается вручную Возврат Новый MCPИнструментыСлужебный( - Новый ЗапускательТестирования(Новый КонсольноеПриложениеТестированиеСлужебный()), + Новый ЗапускательТестирования(Новый ПостроительТочкиВхода()), Логирование.ПолучитьЛог("oscript.lib.oneunit.тесты") ); diff --git "a/tests/fixtures/broken/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\262\320\265\321\200\320\275\320\276\320\271\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\265\320\271.os" "b/tests/fixtures/broken/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\262\320\265\321\200\320\275\320\276\320\271\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\265\320\271.os" new file mode 100644 index 0000000..0f4d71d --- /dev/null +++ "b/tests/fixtures/broken/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\262\320\265\321\200\320\275\320\276\320\271\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\265\320\271.os" @@ -0,0 +1,7 @@ +// Фикстура с некорректной изоляцией: импорты допустимы только на уровне "Процесс". +// Обнаружение набора должно завершаться исключением + +&Тест +&Изолированный(Импорты = "../isolation/ОшибочныеКлассы/ДваЖелудяСОднимИменем") +Процедура ТестСИмпортамиНаУровнеПоделки() Экспорт +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\221\320\270\321\202\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\221\320\270\321\202\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..d380e4f --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\221\320\270\321\202\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,13 @@ +// Изолированный набор, падающий на обнаружении (ключ JSON не совпадает с именем +// параметра). Благодаря изоляции падение не рушит прогон - набор помечается +// сломанным. + +&ПараметризованныйТест +&ИсточникJSON("[ {""НеТотПараметр"": 1} ]") +Процедура ТестСНевернымКлючом(Параметр) Экспорт +КонецПроцедуры + +&ТестовыйНабор +&Изолированный +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\227\320\265\320\273\320\265\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\227\320\265\320\273\320\265\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..878cddd --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\227\320\265\320\273\320\265\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,16 @@ +#Использовать asserts + +&Тест +Процедура ПервыйЗеленый() Экспорт + Ожидаем.Что(2 + 2).Равно(4); +КонецПроцедуры + +&Тест +Процедура ВторойЗеленый() Экспорт + Ожидаем.Что("a" + "b").Равно("ab"); +КонецПроцедуры + +&ТестовыйНабор +&Изолированный +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" new file mode 100644 index 0000000..b17deb5 --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" @@ -0,0 +1,14 @@ +// Изолированный на уровне поделки набор с контейнером: итерации повторяемого +// теста вклеиваются в план родителя зеркалами поддерева. + +#Использовать asserts + +&ПовторяемыйТест(2) +Процедура ПовторяемыйВИзолированномНаборе() Экспорт + Ожидаем.Что(1 + 1).Равно(2); +КонецПроцедуры + +&ТестовыйНабор +&Изолированный +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\203\321\201\321\202\321\213\320\274\320\241\320\276\320\262\320\265\321\202\320\276\320\274.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\203\321\201\321\202\321\213\320\274\320\241\320\276\320\262\320\265\321\202\320\276\320\274.os" new file mode 100644 index 0000000..c328d4f --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\203\321\201\321\202\321\213\320\274\320\241\320\276\320\262\320\265\321\202\320\276\320\274.os" @@ -0,0 +1,14 @@ +// Изолированный набор с советом без значений деталек: изолированная среда +// создается со свежим соответствием деталек прогона. + +#Использовать asserts + +&Тест +Процедура ЗеленыйПриПустомСовете() Экспорт + Ожидаем.Что(2 + 2).Равно(4); +КонецПроцедуры + +&ТестовыйНабор +&Изолированный(Совет = "() -> Новый СоветДругогоМастера()") +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\241\320\276\320\262\320\265\321\202\320\276\320\274\320\230\320\267\320\234\320\265\321\202\320\276\320\264\320\260.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\241\320\276\320\262\320\265\321\202\320\276\320\274\320\230\320\267\320\234\320\265\321\202\320\276\320\264\320\260.os" new file mode 100644 index 0000000..fb0e2d5 --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\241\320\276\320\262\320\265\321\202\320\276\320\274\320\230\320\267\320\234\320\265\321\202\320\276\320\264\320\260.os" @@ -0,0 +1,27 @@ +// Набор, чья лямбда совета строит совет вызовом экспортного метода самого набора. +// На уровне "Процесс" экземпляр набора для захвата поднимается в дочернем процессе +// напрямую через Новый - это возможно, пока конструктор набора без параметров. + +#Использовать asserts + +&Деталька("Изоляция.Значение", ЗначениеПоУмолчанию = "не-задано") +Перем Значение; + +&ТестовыйНабор +&Изолированный(Уровень = "Процесс", Совет = "() -> { + | ЗначенияДеталек = Новый Соответствие(); + | ЗначенияДеталек.Вставить(""Изоляция.Значение"", ЗначениеИзМетода()); + | + | Возврат Новый СоветДругогоМастера().ЗначенияДеталек(ЗначенияДеталек); + |}") +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +Функция ЗначениеИзМетода() Экспорт + Возврат "из-метода-набора"; +КонецФункции + +&Тест +Процедура ЗначениеПриходитИзМетодаНабора() Экспорт + Ожидаем.Что(Значение).Равно("из-метода-набора"); +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\265\320\262\320\270\320\264\320\270\320\274\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\265\320\262\320\270\320\264\320\270\320\274\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..778b921 --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\235\320\265\320\262\320\270\320\264\320\270\320\274\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,12 @@ +#Использовать asserts +#Использовать "Модули" + +&Тест +Процедура ТестКоторыйНеВыполнится() Экспорт + Ожидаем.Что(Истина).ЭтоИстина(); +КонецПроцедуры + +&ТестовыйНабор +&Изолированный(Совет = "() -> ГенераторИзоляции.СоветНеВидящийНаборы()") +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\236\320\261\321\212\320\265\320\272\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\236\320\261\321\212\320\265\320\272\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..7934b1b --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\236\320\261\321\212\320\265\320\272\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,19 @@ +// Набор с советом, значение детальки которого - объект. Лямбда совета исполняется +// в дочернем процессе по месту, поэтому объект не пересекает границу процесса +// и доступен набору как обычная деталька. + +#Использовать asserts +#Использовать "Модули" + +Перем _Объект; + +&ТестовыйНабор +&Изолированный(Уровень = "Процесс", Совет = "() -> ГенераторИзоляции.СоветСОбъектом()") +Процедура ПриСозданииОбъекта(&Деталька("Изоляция.Объект", ЗначениеПоУмолчанию = Неопределено) Объект) Экспорт + _Объект = Объект; +КонецПроцедуры + +&Тест +Процедура ОбъектСоветаДоступенВПроцессе() Экспорт + Ожидаем.Что(ТипЗнч(_Объект)).Равно(Тип("СписокМассив")); +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\320\276\320\264\320\265\320\273\320\276\321\207\320\275\321\213\320\271\320\232\321\200\320\260\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\320\276\320\264\320\265\320\273\320\276\321\207\320\275\321\213\320\271\320\232\321\200\320\260\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..25d7c1d --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\320\276\320\264\320\265\320\273\320\276\321\207\320\275\321\213\320\271\320\232\321\200\320\260\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,11 @@ +#Использовать asserts + +&Тест +Процедура КрасныйТест() Экспорт + Ожидаем.Что(1 + 1, "Специально падающее утверждение").Равно(3); +КонецПроцедуры + +&ТестовыйНабор +&Изолированный +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\221\320\270\321\202\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\221\320\270\321\202\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..33196f7 --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\221\320\270\321\202\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,13 @@ +// Изолированный на уровне процесса набор, падающий на обнаружении (неверный +// ключ JSON-источника): дочерний прогон завершается аварийно, не сформировав +// ни отчета, ни плана. + +&ПараметризованныйТест +&ИсточникJSON("[ {""НеТотПараметр"": 1} ]") +Процедура ТестСНевернымКлючом(Параметр) Экспорт +КонецПроцедуры + +&ТестовыйНабор +&Изолированный(Уровень = "Процесс") +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\232\321\200\320\260\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\232\321\200\320\260\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..463276b --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\232\321\200\320\260\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,16 @@ +#Использовать asserts + +&Тест +Процедура ЗеленыйТест() Экспорт + Ожидаем.Что(1 + 1).Равно(2); +КонецПроцедуры + +&Тест +Процедура КрасныйТест() Экспорт + Ожидаем.Что(1 + 1, "Специально падающее утверждение").Равно(3); +КонецПроцедуры + +&ТестовыйНабор +&Изолированный(Уровень = "Процесс") +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..03c8d84 --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,29 @@ +// Набор с ядовитым импортом: библиотека регистрирует два желудя с одним именем, +// после чего создание любой поделки в процессе падает. Изоляция уровня "Процесс" +// уводит импорт в дочерний процесс - он подключается там после старта поделки +// прогона, поэтому сам прогон остается работоспособным. + +#Использовать asserts +#Использовать autumn + +&Тест +Процедура ДваЖелудяСОднимИменемКонфликтуют() Экспорт + + БылаОшибка = Ложь; + + Попытка + Поделка = Новый Поделка(); // BSLLS:UnusedLocalVariable-off + Исключение + БылаОшибка = Истина; + Ожидаем.Что(ИнформацияОбОшибке().Описание) + .Содержит("Определение желудя с именем ""ИзоляционныйКонфликтныйЖелудь"" уже существует"); + КонецПопытки; + + Ожидаем.Что(БылаОшибка).ЭтоИстина(); + +КонецПроцедуры + +&ТестовыйНабор +&Изолированный(Уровень = "Процесс", Импорты = "asserts, ./ОшибочныеКлассы/ДваЖелудяСОднимИменем") +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\276\320\277\321\203\321\201\320\272\320\276\320\274.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\276\320\277\321\203\321\201\320\272\320\276\320\274.os" new file mode 100644 index 0000000..0c39264 --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\276\320\277\321\203\321\201\320\272\320\276\320\274.os" @@ -0,0 +1,20 @@ +// Изолированный на уровне процесса набор с выключенным тестом: пропуск +// с причиной переносится в план родителя из отчета дочернего прогона. + +#Использовать asserts + +&Тест +Процедура ЗеленыйРядомСПропущенным() Экспорт + Ожидаем.Что(Истина).ЭтоИстина(); +КонецПроцедуры + +&Тест +&Выключен("Пропуск переносится из дочернего прогона") +Процедура ВыключенныйВПроцессе() Экспорт + ВызватьИсключение "Я не должен был выполниться"; +КонецПроцедуры + +&ТестовыйНабор +&Изолированный(Уровень = "Процесс") +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..d4255a5 --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,14 @@ +// Изолированный на уровне процесса набор с контейнером: итерации повторяемого +// теста возвращаются в план родителя из отчета и плана дочернего прогона. + +#Использовать asserts + +&ПовторяемыйТест(2) +Процедура ПовторяемыйВПроцессномНаборе() Экспорт + Ожидаем.Что(1 + 1).Равно(2); +КонецПроцедуры + +&ТестовыйНабор +&Изолированный(Уровень = "Процесс") +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\241\320\276\320\262\320\265\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\241\320\276\320\262\320\265\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..a13dce4 --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\271\320\241\320\276\320\262\320\265\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,15 @@ +#Использовать asserts +#Использовать "Модули" + +Перем _Значение; + +&ТестовыйНабор +&Изолированный(Уровень = "Процесс", Совет = "() -> ГенераторИзоляции.СоветСоЗначением()") +Процедура ПриСозданииОбъекта(&Деталька("Изоляция.Значение", ЗначениеПоУмолчанию = "не-задано") Значение) Экспорт + _Значение = Значение; +КонецПроцедуры + +&Тест +Процедура ЗначениеПриходитИзСоветаВПроцессе() Экспорт + Ожидаем.Что(_Значение).Равно("из-совета"); +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\241\320\276\320\262\320\265\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\241\320\276\320\262\320\265\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" new file mode 100644 index 0000000..6048102 --- /dev/null +++ "b/tests/fixtures/isolation/\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\271\320\241\320\276\320\262\320\265\321\202\320\275\321\213\320\271\320\235\320\260\320\261\320\276\321\200.os" @@ -0,0 +1,15 @@ +#Использовать asserts +#Использовать "Модули" + +Перем _Значение; + +&ТестовыйНабор +&Изолированный(Совет = "() -> ГенераторИзоляции.СоветСоЗначением()") +Процедура ПриСозданииОбъекта(&Деталька("Изоляция.Значение", ЗначениеПоУмолчанию = "не-задано") Значение) Экспорт + _Значение = Значение; +КонецПроцедуры + +&Тест +Процедура ЗначениеПриходитИзСовета() Экспорт + Ожидаем.Что(_Значение).Равно("из-совета"); +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\234\320\276\320\264\321\203\320\273\320\270/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" "b/tests/fixtures/isolation/\320\234\320\276\320\264\321\203\320\273\320\270/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" new file mode 100644 index 0000000..b81a8ef --- /dev/null +++ "b/tests/fixtures/isolation/\320\234\320\276\320\264\321\203\320\273\320\270/\320\223\320\265\320\275\320\265\321\200\320\260\321\202\320\276\321\200\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" @@ -0,0 +1,46 @@ +#Использовать autumn +#Использовать collectionos + +// Возвращает совет создания поделки с заданной деталькой - имитация фабрики +// конфигураций из тестов, которым нужна собственная поделка (как ГенераторДанных +// в autumn-data) +// +// Возвращаемое значение: +// СоветДругогоМастера - Совет со значением детальки "Изоляция.Значение" +// +Функция СоветСоЗначением() Экспорт + + Возврат Новый СоветДругогоМастера().ЗначенияДеталек( + Соответствия.ИзЭлементов("Изоляция.Значение", "из-совета") + ); + +КонецФункции + +// Возвращает совет, подменяющий детальку фильтра наборов, - с ним дочерний +// прогон не обнаруживает ни одного набора +// +// Возвращаемое значение: +// СоветДругогоМастера - Совет с фильтром, не пропускающим ни один набор +// +Функция СоветНеВидящийНаборы() Экспорт + + Возврат Новый СоветДругогоМастера().ЗначенияДеталек( + Соответствия.ИзЭлементов("OneUnit.ТестовыеНаборыВключаемые", "НетТакогоНабора") + ); + +КонецФункции + +// Возвращает совет со значением детальки - объектом: лямбда совета исполняется +// в среде изолированного прогона, поэтому объекты в детальках допустимы +// на любом уровне изоляции +// +// Возвращаемое значение: +// СоветДругогоМастера - Совет со значением-объектом +// +Функция СоветСОбъектом() Экспорт + + Возврат Новый СоветДругогоМастера().ЗначенияДеталек( + Соответствия.ИзЭлементов("Изоляция.Объект", Новый СписокМассив) + ); + +КонецФункции diff --git "a/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\237\320\260\320\264\320\260\321\216\321\211\320\270\320\271\320\222\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\237\320\260\320\264\320\260\321\216\321\211\320\270\320\271\320\222\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" new file mode 100644 index 0000000..79df775 --- /dev/null +++ "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\237\320\260\320\264\320\260\321\216\321\211\320\270\320\271\320\222\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" @@ -0,0 +1,34 @@ +// Набор, чей ПередВсеми падает только в изолированной среде: изолированные +// тесты наследуют состояние и причины сломанного набора дочернего прогона, +// обычный тест в родительском прогоне остается зеленым. + +#Использовать asserts + +Перем _ВИзоляции; + +&ПередВсеми +Процедура ПадениеВИзоляции() Экспорт + Если _ВИзоляции Тогда + ВызватьИсключение "ПередВсеми падает в изолированной среде"; + КонецЕсли; +КонецПроцедуры + +&Тест +Процедура ОбычныйЗеленый() Экспорт + Ожидаем.Что(Истина).ЭтоИстина(); +КонецПроцедуры + +&Тест +&Изолированный +Процедура ПоделочныйНаследуетПадениеНабора() Экспорт +КонецПроцедуры + +&Тест +&Изолированный(Уровень = "Процесс") +Процедура ПроцессныйНаследуетПадениеНабора() Экспорт +КонецПроцедуры + +&ТестовыйНабор +Процедура ПриСозданииОбъекта(&Деталька("OneUnit.ВыполняетсяВИзоляции", ЗначениеПоУмолчанию = Ложь) ВИзоляции) Экспорт + _ВИзоляции = ВИзоляции; +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\237\321\200\320\276\320\277\321\203\321\201\320\272\320\260\320\265\320\274\321\213\320\271\320\222\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\237\321\200\320\276\320\277\321\203\321\201\320\272\320\260\320\265\320\274\321\213\320\271\320\222\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" new file mode 100644 index 0000000..c8587e9 --- /dev/null +++ "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\237\321\200\320\276\320\277\321\203\321\201\320\272\320\260\320\265\320\274\321\213\320\271\320\222\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" @@ -0,0 +1,19 @@ +// Изолированный на уровне процесса набор, выключенный условием только +// в изолированной среде: пропуск уровня набора переносится из отчета +// дочернего прогона. + +#Использовать asserts + +Перем _ВИзоляции; + +&Тест +Процедура ТестПропускаемогоНабора() Экспорт + Ожидаем.Что(Истина).ЭтоИстина(); +КонецПроцедуры + +&ТестовыйНабор +&Изолированный(Уровень = "Процесс") +&ВыключенЕсли("() -> _ВИзоляции", Причина = "Набор пропускается в изолированной среде") +Процедура ПриСозданииОбъекта(&Деталька("OneUnit.ВыполняетсяВИзоляции", ЗначениеПоУмолчанию = Ложь) ВИзоляции) Экспорт + _ВИзоляции = ВИзоляции; +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\221\320\270\321\202\320\276\320\271\320\233\321\217\320\274\320\261\320\264\320\276\320\271\320\241\320\276\320\262\320\265\321\202\320\260.os" "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\221\320\270\321\202\320\276\320\271\320\233\321\217\320\274\320\261\320\264\320\276\320\271\320\241\320\276\320\262\320\265\321\202\320\260.os" new file mode 100644 index 0000000..c3cd935 --- /dev/null +++ "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\221\320\270\321\202\320\276\320\271\320\233\321\217\320\274\320\261\320\264\320\276\320\271\320\241\320\276\320\262\320\265\321\202\320\260.os" @@ -0,0 +1,22 @@ +// Набор с изолированным тестом, чья лямбда совета бросает исключение: +// тест ломается с причиной, прогон продолжается. + +#Использовать asserts + +&Тест +&Изолированный(Совет = "() -> МетодаТакогоНет()") +Процедура ТестСБитымСоветом() Экспорт + Ожидаем.Что(Истина).ЭтоИстина(); +КонецПроцедуры + +&Тест +&Изолированный(Уровень = "Процесс", Совет = "() -> МетодаТакогоНет()") +Процедура ПроцессныйТестСБитымСоветом() Экспорт + // Лямбда падает в точке входа дочернего процесса - прогон завершается + // аварийно, не сформировав отчет + Ожидаем.Что(Истина).ЭтоИстина(); +КонецПроцедуры + +&ТестовыйНабор +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\274\320\270\320\242\320\265\321\201\321\202\320\260\320\274\320\270.os" "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\274\320\270\320\242\320\265\321\201\321\202\320\260\320\274\320\270.os" new file mode 100644 index 0000000..b56c406 --- /dev/null +++ "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\274\320\270\320\242\320\265\321\201\321\202\320\260\320\274\320\270.os" @@ -0,0 +1,37 @@ +// Набор с изоляцией на уровне отдельных тестов: обычные тесты выполняются +// в общем потоке, изолированные - каждый в своем контексте, как будто набор +// прогнали только с этим тестом. + +#Использовать asserts + +Перем ОбщееСостояние; + +&Тест +Процедура ОбычныйТестВидитОбщееСостояние() Экспорт + ОбщееСостояние = "изменено"; + Ожидаем.Что(ОбщееСостояние).Равно("изменено"); +КонецПроцедуры + +&Тест +&Изолированный +Процедура ИзолированныйВПоделкеНеВидитОбщееСостояние() Экспорт + // Свежая поделка - свежий экземпляр набора, без следов соседних тестов + Ожидаем.Что(ОбщееСостояние).Равно("из-конструктора"); +КонецПроцедуры + +&Тест +&Изолированный(Уровень = "Процесс") +Процедура ИзолированныйВПроцессеВыполняется() Экспорт + Ожидаем.Что(ОбщееСостояние).Равно("из-конструктора"); +КонецПроцедуры + +&ПовторяемыйТест(2) +&Изолированный +Процедура ИзолированныйПовторяемыйТест() Экспорт + Ожидаем.Что(ОбщееСостояние).Равно("из-конструктора"); +КонецПроцедуры + +&ТестовыйНабор +Процедура ПриСозданииОбъекта() + ОбщееСостояние = "из-конструктора"; +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\262\320\270\320\264\320\270\320\274\321\213\320\274\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\262\320\270\320\264\320\270\320\274\321\213\320\274\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" new file mode 100644 index 0000000..ed727e9 --- /dev/null +++ "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\262\320\270\320\264\320\270\320\274\321\213\320\274\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" @@ -0,0 +1,12 @@ +#Использовать asserts +#Использовать "Модули" + +&Тест +&Изолированный(Совет = "() -> ГенераторИзоляции.СоветНеВидящийНаборы()") +Процедура НевидимыйТест() Экспорт + Ожидаем.Что(Истина).ЭтоИстина(); +КонецПроцедуры + +&ТестовыйНабор +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\274\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\274\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" new file mode 100644 index 0000000..0ddb733 --- /dev/null +++ "b/tests/fixtures/isolation/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\275\321\213\320\274\320\237\320\276\320\262\321\202\320\276\321\200\321\217\320\265\320\274\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" @@ -0,0 +1,14 @@ +// Набор с контейнером, изолированным на уровне процесса: итерации повторяемого +// теста вклеиваются в план родителя по записям отчета дочернего прогона. + +#Использовать asserts + +&ПовторяемыйТест(2) +&Изолированный(Уровень = "Процесс") +Процедура ПовторяемыйВПроцессе() Экспорт + Ожидаем.Что(1 + 1).Равно(2); +КонецПроцедуры + +&ТестовыйНабор +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\236\321\210\320\270\320\261\320\276\321\207\320\275\321\213\320\265\320\232\320\273\320\260\321\201\321\201\321\213/\320\224\320\262\320\260\320\226\320\265\320\273\321\203\320\264\321\217\320\241\320\236\320\264\320\275\320\270\320\274\320\230\320\274\320\265\320\275\320\265\320\274/\320\232\320\273\320\260\321\201\321\201\321\213/\320\226\320\265\320\273\321\203\320\264\321\2141.os" "b/tests/fixtures/isolation/\320\236\321\210\320\270\320\261\320\276\321\207\320\275\321\213\320\265\320\232\320\273\320\260\321\201\321\201\321\213/\320\224\320\262\320\260\320\226\320\265\320\273\321\203\320\264\321\217\320\241\320\236\320\264\320\275\320\270\320\274\320\230\320\274\320\265\320\275\320\265\320\274/\320\232\320\273\320\260\321\201\321\201\321\213/\320\226\320\265\320\273\321\203\320\264\321\2141.os" new file mode 100644 index 0000000..637d9bf --- /dev/null +++ "b/tests/fixtures/isolation/\320\236\321\210\320\270\320\261\320\276\321\207\320\275\321\213\320\265\320\232\320\273\320\260\321\201\321\201\321\213/\320\224\320\262\320\260\320\226\320\265\320\273\321\203\320\264\321\217\320\241\320\236\320\264\320\275\320\270\320\274\320\230\320\274\320\265\320\275\320\265\320\274/\320\232\320\273\320\260\321\201\321\201\321\213/\320\226\320\265\320\273\321\203\320\264\321\2141.os" @@ -0,0 +1,3 @@ +&Желудь("ИзоляционныйКонфликтныйЖелудь") +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/fixtures/isolation/\320\236\321\210\320\270\320\261\320\276\321\207\320\275\321\213\320\265\320\232\320\273\320\260\321\201\321\201\321\213/\320\224\320\262\320\260\320\226\320\265\320\273\321\203\320\264\321\217\320\241\320\236\320\264\320\275\320\270\320\274\320\230\320\274\320\265\320\275\320\265\320\274/\320\232\320\273\320\260\321\201\321\201\321\213/\320\226\320\265\320\273\321\203\320\264\321\2142.os" "b/tests/fixtures/isolation/\320\236\321\210\320\270\320\261\320\276\321\207\320\275\321\213\320\265\320\232\320\273\320\260\321\201\321\201\321\213/\320\224\320\262\320\260\320\226\320\265\320\273\321\203\320\264\321\217\320\241\320\236\320\264\320\275\320\270\320\274\320\230\320\274\320\265\320\275\320\265\320\274/\320\232\320\273\320\260\321\201\321\201\321\213/\320\226\320\265\320\273\321\203\320\264\321\2142.os" new file mode 100644 index 0000000..637d9bf --- /dev/null +++ "b/tests/fixtures/isolation/\320\236\321\210\320\270\320\261\320\276\321\207\320\275\321\213\320\265\320\232\320\273\320\260\321\201\321\201\321\213/\320\224\320\262\320\260\320\226\320\265\320\273\321\203\320\264\321\217\320\241\320\236\320\264\320\275\320\270\320\274\320\230\320\274\320\265\320\275\320\265\320\274/\320\232\320\273\320\260\321\201\321\201\321\213/\320\226\320\265\320\273\321\203\320\264\321\2142.os" @@ -0,0 +1,3 @@ +&Желудь("ИзоляционныйКонфликтныйЖелудь") +Процедура ПриСозданииОбъекта() +КонецПроцедуры diff --git "a/tests/\320\230\320\267\320\276\320\273\321\217\321\206\320\270\321\217.os" "b/tests/\320\230\320\267\320\276\320\273\321\217\321\206\320\270\321\217.os" new file mode 100644 index 0000000..ff9b8f6 --- /dev/null +++ "b/tests/\320\230\320\267\320\276\320\273\321\217\321\206\320\270\321\217.os" @@ -0,0 +1,506 @@ +// Тесты изоляции прогонов на уровне поделки. +// +// Внимание: у этого сценария намеренно нет конструктора с аннотацией &ТестовыйНабор, +// см. пояснение в Исполнитель.os - вложенные прогоны создают поделки. + +#Использовать asserts +#Использовать "./support" +#Использовать ".." + +&ПослеВсех +Процедура УдалитьВременныеФайлы() Экспорт + ВременныеФайлы.Удалить(); +КонецПроцедуры + +&Тест +Процедура ИзолированныйНаборВыполняетсяВСвоейСреде() Экспорт + + Статистика = СтатистикаПрогона("ИзолированныйЗеленыйНабор"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовУспешно", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОбнаружено", 0)).Равно(2); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(2); + +КонецПроцедуры + +&Тест +Процедура ПадениеОбнаруженияИзолированногоНабораНеРушитПрогон() Экспорт + + // Набор падает на обнаружении (неверный ключ JSON), но изоляция сдерживает + // отказ: набор помечается сломанным, а прогон завершается штатно + Статистика = СтатистикаПрогона("ИзолированныйБитыйНабор"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовСломано", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовОбнаружено", 0)).Равно(1); + +КонецПроцедуры + +&Тест +Процедура ИзолированныйНаборКонфигурируетсяСоветом() Экспорт + + // Лямбда совета отдает детальку "Изоляция.Значение", которую набор инжектит + // и проверяет - так тест доказывает, что свежая поделка создана с советом + Статистика = СтатистикаПрогона("ИзолированныйСоветныйНабор"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовСломано", 0)).Равно(0); + +КонецПроцедуры + +&Тест +Процедура ЯдовитыйИмпортУводитсяВДочернийПроцесс() Экспорт + + // Импорт из аннотации регистрирует два желудя с одним именем и ломает + // создание любой поделки. Изоляция уровня "Процесс" подключает его только + // в дочернем процессе, где тест и проверяет ожидаемый конфликт + Статистика = СтатистикаПрогона("ИзолированныйПроцессныйНабор"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовУспешно", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(1); + +КонецПроцедуры + +&Тест +Процедура СоветПереноситсяВДочернийПроцесс() Экспорт + + // Через границу процесса переезжает только текст лямбды совета - она + // исполняется в дочернем процессе в контексте обнаруженного там набора. + // Тест доказывает, что деталька из совета доехала до дочернего прогона + Статистика = СтатистикаПрогона("ИзолированныйПроцессныйСоветныйНабор"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОбнаружено", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(1); + +КонецПроцедуры + +&Тест +Процедура РезультатыДочернегоПрогонаВклеиваютсяВПлан() Экспорт + + Прогон = ПрогонПроцессногоКрасногоНабора(); + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОбнаружено", 0)).Равно(2); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОшибка", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовОшибка", 0)).Равно(1); + +КонецПроцедуры + +&Тест +Процедура ПричиныКрасныхТестовПереносятсяИзДочернегоПрогона() Экспорт + + Прогон = ПрогонПроцессногоКрасногоНабора(); + + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "ИзолированныйПроцессныйКрасныйНабор"); + Тест = ПрогоныТестов.НайтиТест(Набор, "КрасныйТест"); + + Результат = Прогон.Статистика.Результат(Тест); + + Ожидаем.Что(Результат.Состояние()).Равно(СостоянияВыполненияТестов.Ошибка); + Ожидаем.Что(СтрНайти(Результат.ОписанияПричин().Получить(0), "Специально падающее утверждение")).Больше(0); + +КонецПроцедуры + +&Тест +Процедура ИзолированныеТестыВыполняютсяВСвоихКонтекстах() Экспорт + + // Фикстура сама проверяет изоляцию: изолированные тесты не видят следов + // соседних тестов, а обычный тест выполняется в общем потоке. Ассерты + // зашиты в тесты фикстуры, снаружи проверяется только зеленый итог + Статистика = СтатистикаПрогона("НаборСИзолированнымиТестами"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОбнаружено", 0)).Равно(5); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(5); + +КонецПроцедуры + +&Тест +Процедура ИмпортыНаУровнеПоделкиЛомаютОбнаружение() Экспорт + + Детальки = Новый Соответствие; + Детальки.Вставить("OneUnit.КаталогиТестов", ""); + Детальки.Вставить("OneUnit.ФайлыТестов", "./tests/fixtures/broken/НаборСНевернойИзоляцией.os"); + + Поделка = ПрогоныТестов.СоздатьПоделку(Детальки); + + Попытка + Поделка.НайтиЖелудь("МенеджерТестирования").Обнаружить(); + Исключение + ИнформацияОбОшибке = ИнформацияОбОшибке(); + КонецПопытки; + + Ожидаем.Что(ИнформацияОбОшибке).Не_().ЭтоНеопределено(); + Ожидаем.Что(СтрНайти(ИнформацияОбОшибке.Описание, "доступен только для уровня Процесс")).Больше(0); + +КонецПроцедуры + +&Тест +Процедура ЛямбдаСоветаВидитМетодыНабораВПроцессе() Экспорт + + // Экземпляр набора для захвата лямбдой поднимается в дочернем процессе + // напрямую через Новый: многострочная лямбда с кавычками строит совет + // вызовом экспортного метода набора + Статистика = СтатистикаПрогона("ИзолированныйНаборССоветомИзМетода"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОбнаружено", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(1); + +КонецПроцедуры + +&Тест +Процедура СоветСОбъектомРаботаетВПроцессе() Экспорт + + // Лямбда совета исполняется в дочернем процессе по месту, поэтому детальки + // совета не ограничены сериализуемостью - объект доступен набору как обычно + Статистика = СтатистикаПрогона("ИзолированныйОбъектныйНабор"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОбнаружено", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(1); + +КонецПроцедуры + +&Тест +Процедура НеобнаруженныйДочернимПрогономНаборЛомаетсяСПояснением() Экспорт + + // Совет фикстуры подменяет детальку фильтра наборов, и дочерний прогон + // не обнаруживает ни одного набора - причина набора объясняет, что произошло + Прогон = ПрогоныТестов.Прогнать("ИзолированныйНевидимыйНабор", "./tests/fixtures/isolation"); + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовСломано", 0)).Равно(1); + + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "ИзолированныйНевидимыйНабор"); + Результат = Прогон.Статистика.Результат(Набор); + + Ожидаем.Что(СтрНайти(Результат.ОписанияПричин().Получить(0), "не был обнаружен в дочернем прогоне")).Больше(0); + +КонецПроцедуры + +&Тест +Процедура НевыполненныйВДочернемПрогонеТестЛомаетсяСПояснением() Экспорт + + Прогон = ПрогоныТестов.Прогнать("НаборСНевидимымИзолированнымТестом", "./tests/fixtures/isolation"); + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовСломано", 0)).Равно(1); + + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "НаборСНевидимымИзолированнымТестом"); + Тест = ПрогоныТестов.НайтиТест(Набор, "НевидимыйТест"); + + Результат = Прогон.Статистика.Результат(Тест); + + Ожидаем.Что(СтрНайти(Результат.ОписанияПричин().Получить(0), "дочерний прогон не обнаружил набор")).Больше(0); + +КонецПроцедуры + +&Тест +Процедура ВыводДочернегоПроцессаНеРветВыводРодителя() Экспорт + + // Собственный лог дочернего прогона (детали упавших тестов, статистика) + // не транслируется в консоль посреди вывода родителя: причины переносятся + // структурно, а статистику выводит только сам родитель - один раз + Вхождения = ЧислоВхожденийСтатистикиВВыводеПрогона("ИзолированныйПроцессныйКрасныйНабор"); + + Ожидаем.Что(Вхождения).Равно(1); + +КонецПроцедуры + +&Тест +Процедура ВыводДочернейПоделкиНеРветВыводРодителя() Экспорт + + Вхождения = ЧислоВхожденийСтатистикиВВыводеПрогона("ИзолированныйПоделочныйКрасныйНабор"); + + Ожидаем.Что(Вхождения).Равно(1); + +КонецПроцедуры + +&Тест +Процедура ИзолированныйНаборВклеиваетИтерацииКонтейнера() Экспорт + + // Повторяемый тест изолированного набора: итерации вклеиваются зеркалами + Статистика = СтатистикаПрогона("ИзолированныйНаборСПовторяемымТестом"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовУспешно", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(2); + +КонецПроцедуры + +&Тест +Процедура ПроцессныйКонтейнерВклеиваетИтерацииИзОтчета() Экспорт + + // Изолированный на уровне процесса повторяемый тест: итерации контейнера + // вклеиваются в план родителя по записям отчета дочернего прогона + Статистика = СтатистикаПрогона("НаборСПроцесснымПовторяемымТестом"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОбнаружено", 0)).Равно(2); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(2); + +КонецПроцедуры + +&Тест +Процедура АварийныйДочернийПроцессЛомаетНаборСВыводом() Экспорт + + // Обнаружение в дочернем процессе падает, процесс умирает не сформировав + // отчет - набор ломается с выводом дочернего прогона в причине + Прогон = ПрогоныТестов.Прогнать("ИзолированныйПроцессныйБитыйНабор", "./tests/fixtures/isolation"); + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовСломано", 0)).Равно(1); + + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "ИзолированныйПроцессныйБитыйНабор"); + Результат = Прогон.Статистика.Результат(Набор); + + Ожидаем.Что(СтрНайти(Результат.ОписанияПричин().Получить(0), "завершился аварийно")).Больше(0); + +КонецПроцедуры + +&Тест +Процедура ОбнаружениеБитогоПроцессногоНабораНеПадает() Экспорт + + План = ПланОбнаружения("ИзолированныйПроцессныйБитыйНабор"); + Набор = ПрогоныТестов.НайтиНабор(План, "ИзолированныйПроцессныйБитыйНабор"); + + Ожидаем.Что(Набор).Не_().ЭтоНеопределено(); + Ожидаем.Что(Набор.Дети().Количество()).Равно(0); + +КонецПроцедуры + +&Тест +Процедура БитаяЛямбдаСоветаЛомаетТестНеРоняяПрогон() Экспорт + + // Поделочная лямбда падает в родителе, процессная - в точке входа ребенка: + // оба теста ломаются с причинами, прогон продолжается + Прогон = ПрогоныТестов.Прогнать("НаборСБитойЛямбдойСовета", "./tests/fixtures/isolation"); + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовСломано", 0)).Равно(2); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовОбнаружено", 0)).Равно(1); + +КонецПроцедуры + +&Тест +Процедура ПроцессныйНаборВклеиваетИтерацииКонтейнера() Экспорт + + // Итерации повторяемого теста изолированного процессного набора + // вклеиваются в план родителя из отчета дочернего прогона + Статистика = СтатистикаПрогона("ИзолированныйПроцессныйПовторяемыйНабор"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовУспешно", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(2); + +КонецПроцедуры + +&Тест +Процедура ОбнаружениеПоказываетКонтейнерПроцессногоНабора() Экспорт + + // Состав контейнера приезжает из плана дочернего обнаружения с итерациями + План = ПланОбнаружения("ИзолированныйПроцессныйПовторяемыйНабор"); + Набор = ПрогоныТестов.НайтиНабор(План, "ИзолированныйПроцессныйПовторяемыйНабор"); + + Ожидаем.Что(Набор.Дети().Количество()).Равно(1); + Ожидаем.Что(Набор.Дети().Получить(0).Дети().Количество()).Равно(2); + +КонецПроцедуры + +&Тест +Процедура ПропускНабораПереноситсяИзДочернегоПрогона() Экспорт + + // Условие выключает набор только в изолированной среде: пропуск уровня + // набора переносится из отчета дочернего прогона + Прогон = ПрогоныТестов.Прогнать("НаборПропускаемыйВИзоляции", "./tests/fixtures/isolation"); + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовПропущено", 0)).Равно(1); + + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "НаборПропускаемыйВИзоляции"); + Результат = Прогон.Статистика.Результат(Набор); + + Ожидаем.Что(СтрНайти(Результат.ОписанияПричин().Получить(0), "Набор пропускается в изолированной среде")).Больше(0); + +КонецПроцедуры + +&Тест +Процедура ИзолированныеТестыНаследуютПадениеНабораВИзоляции() Экспорт + + // ПередВсеми фикстуры падает только в изолированной среде: обычный тест + // зеленый, а изолированные наследуют состояние и причины сломанного набора + Прогон = ПрогоныТестов.Прогнать("НаборПадающийВИзоляции", "./tests/fixtures/isolation"); + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовСломано", 0)).Равно(2); + + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "НаборПадающийВИзоляции"); + Тест = ПрогоныТестов.НайтиТест(Набор, "ПроцессныйНаследуетПадениеНабора"); + + Результат = Прогон.Статистика.Результат(Тест); + + Ожидаем.Что(СтрНайти(Результат.ОписанияПричин().Получить(0), "ПередВсеми падает в изолированной среде")).Больше(0); + +КонецПроцедуры + +&Тест +Процедура ПустойСоветНеЛомаетИзолированныйНабор() Экспорт + + Статистика = СтатистикаПрогона("ИзолированныйНаборСПустымСоветом"); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(1); + +КонецПроцедуры + +&Тест +Процедура ПропускПереноситсяИзДочернегоПрогона() Экспорт + + Прогон = ПрогоныТестов.Прогнать("ИзолированныйПроцессныйНаборСПропуском", "./tests/fixtures/isolation"); + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0)).Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовПропущено", 0)).Равно(1); + + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "ИзолированныйПроцессныйНаборСПропуском"); + Тест = ПрогоныТестов.НайтиТест(Набор, "ВыключенныйВПроцессе"); + + Результат = Прогон.Статистика.Результат(Тест); + + Ожидаем.Что(СтрНайти(Результат.ОписанияПричин().Получить(0), "Пропуск переносится из дочернего прогона")).Больше(0); + +КонецПроцедуры + +&Тест +Процедура ОбнаружениеНевидимогоНабораОставляетЕгоПустым() Экспорт + + // Дочернее обнаружение не нашло набор (совет подменил фильтр) - + // обнаружение не падает, набор остается пустым плейсхолдером + План = ПланОбнаружения("ИзолированныйНевидимыйНабор"); + Набор = ПрогоныТестов.НайтиНабор(План, "ИзолированныйНевидимыйНабор"); + + Ожидаем.Что(Набор.Дети().Количество()).Равно(0); + +КонецПроцедуры + +&Тест +Процедура ИзолированныйПроцессПишетСтатистикуПокрытия() Экспорт + + // При прогоне с покрытием изолированный дочерний процесс получает -codestat + // и складывает статистику в каталог родительского прогона, который мержит + // ее в общие отчеты покрытия. + // Каталог - через собственный менеджер: глобальный модуль ВременныеФайлы + // зачищается построителем декоратора при каждом создании поделки + МенеджерВременныхФайлов = Новый МенеджерВременныхФайлов(); + КаталогСтатистики = МенеджерВременныхФайлов.СоздатьКаталог(); + + ДопДетальки = Новый Соответствие; + ДопДетальки.Вставить("OneUnit.КаталогСтатистикиПокрытия", КаталогСтатистики); + + ПрогоныТестов.Прогнать("ИзолированныйПроцессныйКрасныйНабор", "./tests/fixtures/isolation", "", ДопДетальки); + + КоличествоСтатистик = НайтиФайлы(КаталогСтатистики, "stat_*.json").Количество(); + МенеджерВременныхФайлов.Удалить(); + + Ожидаем.Что(КоличествоСтатистик).Больше(0); + +КонецПроцедуры + +&Тест +Процедура ОбнаружениеПоказываетСоставИзолированногоНабора() Экспорт + + // Чистое обнаружение (без исполнения) разворачивает изолированный набор + // в его среде - внешний тулинг видит фактический состав тестов + План = ПланОбнаружения("ИзолированныйЗеленыйНабор"); + Набор = ПрогоныТестов.НайтиНабор(План, "ИзолированныйЗеленыйНабор"); + + Ожидаем.Что(Набор.Дети().Количество()).Равно(2); + Ожидаем.Что(ПрогоныТестов.НайтиТест(Набор, "ПервыйЗеленый")).Не_().ЭтоНеопределено(); + Ожидаем.Что(ПрогоныТестов.НайтиТест(Набор, "ВторойЗеленый")).Не_().ЭтоНеопределено(); + +КонецПроцедуры + +&Тест +Процедура ОбнаружениеПоказываетСоставПроцессногоНабора() Экспорт + + // Состав приезжает из дочернего процесса обнаружения, тесты не исполняются - + // красный тест набора виден в плане, но прогон обнаружения зеленый + План = ПланОбнаружения("ИзолированныйПроцессныйКрасныйНабор"); + Набор = ПрогоныТестов.НайтиНабор(План, "ИзолированныйПроцессныйКрасныйНабор"); + + Ожидаем.Что(Набор.Дети().Количество()).Равно(2); + Ожидаем.Что(ПрогоныТестов.НайтиТест(Набор, "КрасныйТест")).Не_().ЭтоНеопределено(); + +КонецПроцедуры + +&Тест +Процедура ОбнаружениеБитогоИзолированногоНабораНеПадает() Экспорт + + План = ПланОбнаружения("ИзолированныйБитыйНабор"); + Набор = ПрогоныТестов.НайтиНабор(План, "ИзолированныйБитыйНабор"); + + Ожидаем.Что(Набор).Не_().ЭтоНеопределено(); + Ожидаем.Что(Набор.Дети().Количество()).Равно(0); + +КонецПроцедуры + +Функция ПланОбнаружения(Знач ВключаемыеНаборы) + + Детальки = Новый Соответствие; + Детальки.Вставить("OneUnit.КаталогиТестов", "./tests/fixtures/isolation"); + Детальки.Вставить("OneUnit.ФайлыТестов", ""); + Детальки.Вставить("OneUnit.ТестовыеНаборыВключаемые", ВключаемыеНаборы); + + Поделка = ПрогоныТестов.СоздатьПоделку(Детальки); + + Возврат Поделка.НайтиЖелудь("МенеджерТестирования").Обнаружить(); + +КонецФункции + +Функция СтатистикаПрогона(Знач ВключаемыеНаборы) + + Возврат ПрогоныТестов.Прогнать( + ВключаемыеНаборы, + "./tests/fixtures/isolation" + ).Статистика.СтатистикаТестПлана(); + +КонецФункции + +Функция ПрогонПроцессногоКрасногоНабора() + + Возврат ПрогоныТестов.ПрогнатьОднажды( + "ПроцессныйКрасныйНабор", + "ИзолированныйПроцессныйКрасныйНабор", + "./tests/fixtures/isolation" + ); + +КонецФункции + +Функция ЧислоВхожденийСтатистикиВВыводеПрогона(Знач ВключаемыеНаборы) + + ПотокВывода = Новый ПотокВПамяти(); + ТестированиеСлужебный.НачатьПерехватВывода(ПотокВывода); + + Попытка + ПрогоныТестов.Прогнать(ВключаемыеНаборы, "./tests/fixtures/isolation"); + Исключение + ТестированиеСлужебный.ЗавершитьПерехватВывода(); + ВызватьИсключение; + КонецПопытки; + + ТестированиеСлужебный.ЗавершитьПерехватВывода(); + + Вывод = ПрогоныТестов.ПрочитатьПоток(ПотокВывода); + + Возврат ЧислоВхождений(Вывод, "Запуск тестов завершился"); + +КонецФункции + +Функция ЧислоВхождений(Знач Текст, Знач Подстрока) + + Вхождения = 0; + Позиция = СтрНайти(Текст, Подстрока); + + Пока Позиция > 0 Цикл + Вхождения = Вхождения + 1; + Позиция = СтрНайти(Текст, Подстрока, НаправлениеПоиска.СНачала, Позиция + СтрДлина(Подстрока)); + КонецЦикла; + + Возврат Вхождения; + +КонецФункции diff --git "a/tests/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" "b/tests/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" index 3e16db4..3a34edd 100644 --- "a/tests/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" +++ "b/tests/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" @@ -1,4 +1,5 @@ // Тесты служебного кода консольного приложения (src/cli/internal) +// и запускателя дочерних прогонов (src/shared) // // Внимание: у этого сценария намеренно нет конструктора с аннотацией &ТестовыйНабор, // см. пояснение в Исполнитель.os @@ -9,6 +10,7 @@ #Использовать collectionos #Использовать "./support" #Использовать ".." +#Использовать "../src/shared" #Использовать "../src/cli/internal" #Использовать "../src/cli/internal/isolated" @@ -48,7 +50,7 @@ &Тест Процедура НастройкиЛогосаСтроятсяПоКомпонентуИРаскладке() Экспорт - Настройки = Служебный().НастройкиЛогоса("core", "МояРаскладка"); + Настройки = Построитель().НастройкиЛогоса("core", "МояРаскладка"); Логгер = Настройки.Получить("logos.logger").Получить("oscript.lib.oneunit.core"); @@ -75,7 +77,7 @@ ЗаписьТекста.ЗаписатьСтроку("custom.key=Значение"); ЗаписьТекста.Закрыть(); - Результат = Служебный().ПрочитатьКонфигурационныйФайл(ПутьКФайлу); + Результат = Построитель().ПрочитатьКонфигурационныйФайл(ПутьКФайлу); // Произвольные ключи читаются как есть Ожидаем.Что(Результат.ПолучитьИлиУмолчание("custom.key", "")).Равно("Значение"); @@ -109,7 +111,7 @@ // ФиксированныйМассив, чтобы закрепить поддержку обоих типов массивов в детальках Детальки = Детальки(Массивы.ИзЭлементов("./tests/fixtures"), ФайлыТестов); - ТочкаВхода = Служебный().СоздатьТочкуВхода( + ТочкаВхода = Построитель().СоздатьТочкуВхода( Детальки, МенеджерВременныхФайлов, Логирование.ПолучитьЛог("oscript.lib.oneunit.тесты"), @@ -156,7 +158,7 @@ Детальки = Детальки(Новый Массив, Новый Массив); - Служебный().СоздатьТочкуВхода( + Построитель().СоздатьТочкуВхода( Детальки, МенеджерВременныхФайлов, Логирование.ПолучитьЛог("oscript.lib.oneunit.тесты"), @@ -377,8 +379,12 @@ Возврат Новый КонсольноеПриложениеТестированиеСлужебный(); КонецФункции +Функция Построитель() + Возврат Новый ПостроительТочкиВхода(); +КонецФункции + Функция Запускатель() - Возврат Новый ЗапускательТестирования(Служебный()); + Возврат Новый ЗапускательТестирования(Построитель()); КонецФункции Функция Детальки(КаталогиТестов, ФайлыТестов, Знач РежимВыводаЛога = "") From e003e2f7f9fb74aa48938c07812818aea228143b Mon Sep 17 00:00:00 2001 From: sfaqer Date: Thu, 16 Jul 2026 09:20:48 +1000 Subject: [PATCH 2/2] =?UTF-8?q?=D0=A3=D1=87=D1=82=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=8C=D1=8E=20PR=20#28:=20=D0=BD=D0=B5=20=D1=80?= =?UTF-8?q?=D0=BE=D0=BD=D1=8F=D0=B5=D0=BC=20=D0=BD=D0=B0=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D1=8F=D1=85=20?= =?UTF-8?q?=D0=B2=20oscript.cfg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ПостроительТочкиВхода.ПрочитатьКонфигурационныйФайл: пропускает пустые строки и строки-комментарии (начинаются с "#") вместо падения на индексации несуществующего второго элемента расщепленной по "=" строки; запись разбирается по первому "=" с обрезкой пробелов вокруг ключа и значения - закрывает #25. - ИсполнительИзоляции.Импорты: восстанавливает текущий каталог процесса при исключении (например, из ФС.НормализоватьПуть на неверном пути импорта) перед тем как отдать его дальше - иначе каталог остается подмененным для остального прогона. - Тесты: регрессия на комментарии/пустые строки в oscript.cfg; проверка, что причина аварийного дочернего прогона содержит исходную ошибку из его вывода, а не только обёрточное сообщение. Co-Authored-By: Claude Sonnet 5 --- ...20\273\321\217\321\206\320\270\320\270.os" | 26 ++++++++++++----- ...20\222\321\205\320\276\320\264\320\260.os" | 24 ++++++++++++---- ...20\273\321\217\321\206\320\270\321\217.os" | 6 ++++ ...20\266\320\265\320\275\320\270\320\265.os" | 28 +++++++++++++++++++ 4 files changed, 72 insertions(+), 12 deletions(-) diff --git "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" index 98e0d9d..acad735 100644 --- "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" +++ "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" @@ -542,15 +542,27 @@ ИсходныйКаталог = ТекущийКаталог(); УстановитьТекущийКаталог(Новый Файл(ОпределениеНабора.Путь()).Путь); - Для каждого Импорт Из Источник.Изоляция().Импорты Цикл + Попытка - Если СтрНайти(Импорт, "/") > 0 Или СтрНайти(Импорт, "\") > 0 Тогда - Импорты.Добавить(ФС.НормализоватьПуть(Импорт)); - Иначе - Импорты.Добавить(Импорт); - КонецЕсли; + Для каждого Импорт Из Источник.Изоляция().Импорты Цикл - КонецЦикла; + Если СтрНайти(Импорт, "/") > 0 Или СтрНайти(Импорт, "\") > 0 Тогда + Импорты.Добавить(ФС.НормализоватьПуть(Импорт)); + Иначе + Импорты.Добавить(Импорт); + КонецЕсли; + + КонецЦикла; + + Исключение + + // Текущий каталог процесса общий с остальным прогоном - восстанавливаем + // его перед тем, как отдать исключение дальше (например, при ошибке + // ФС.НормализоватьПуть на неверном пути импорта) + УстановитьТекущийКаталог(ИсходныйКаталог); + ВызватьИсключение; + + КонецПопытки; УстановитьТекущийКаталог(ИсходныйКаталог); diff --git "a/src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\201\321\202\321\200\320\276\320\270\321\202\320\265\320\273\321\214\320\242\320\276\321\207\320\272\320\270\320\222\321\205\320\276\320\264\320\260.os" "b/src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\201\321\202\321\200\320\276\320\270\321\202\320\265\320\273\321\214\320\242\320\276\321\207\320\272\320\270\320\222\321\205\320\276\320\264\320\260.os" index 3f8fd5e..e36e3cc 100644 --- "a/src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\201\321\202\321\200\320\276\320\270\321\202\320\265\320\273\321\214\320\242\320\276\321\207\320\272\320\270\320\222\321\205\320\276\320\264\320\260.os" +++ "b/src/shared/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\201\321\202\321\200\320\276\320\270\321\202\320\265\320\273\321\214\320\242\320\276\321\207\320\272\320\270\320\222\321\205\320\276\320\264\320\260.os" @@ -447,9 +447,23 @@ Пока ПрочитаннаяСтрока <> Неопределено Цикл - ПрочитаннаяСтрока = СтрРазделить(ПрочитаннаяСтрока, "="); + СтрокаБезПробелов = СокрЛП(ПрочитаннаяСтрока); + ПозицияРавно = СтрНайти(СтрокаБезПробелов, "="); - Ключ = ПрочитаннаяСтрока[0]; + // Пустые строки и строки-комментарии (начинаются с "#", без "=") + // пропускаются - формат oscript.cfg допускает и то, и другое между + // записями "ключ=значение" + Если ПустаяСтрока(СтрокаБезПробелов) + Или СтрНачинаетсяС(СтрокаБезПробелов, "#") + Или ПозицияРавно = 0 Тогда + + ПрочитаннаяСтрока = ЧтениеТекста.ПрочитатьСтроку(); + Продолжить; + + КонецЕсли; + + Ключ = СокрЛП(Лев(СтрокаБезПробелов, ПозицияРавно - 1)); + ЗначениеСтрокой = СокрЛП(Сред(СтрокаБезПробелов, ПозицияРавно + 1)); УстановитьТекущийКаталог(Новый Файл(Путь).Путь); @@ -457,17 +471,17 @@ Если СтрНачинаетсяС(Ключ, "lib.additional") Тогда - Значение = ПроцессорыКоллекций.ИзКоллекции(СтрРазделить(ПрочитаннаяСтрока[1], ";", Ложь)) + Значение = ПроцессорыКоллекций.ИзКоллекции(СтрРазделить(ЗначениеСтрокой, ";", Ложь)) .Фильтровать("Путь -> ФС.КаталогСуществует(Путь)") .Обработать("Путь -> ФС.НормализоватьПуть(Путь)") .Получить("МножествоСоответствие"); Иначе - Значение = ?(ФС.КаталогСуществует(ПрочитаннаяСтрока[1]), ФС.НормализоватьПуть(ПрочитаннаяСтрока[1]), ""); + Значение = ?(ФС.КаталогСуществует(ЗначениеСтрокой), ФС.НормализоватьПуть(ЗначениеСтрокой), ""); КонецЕсли; Иначе - Значение = ПрочитаннаяСтрока[1]; + Значение = ЗначениеСтрокой; КонецЕсли; УстановитьТекущийКаталог(ТекущийКаталог); diff --git "a/tests/\320\230\320\267\320\276\320\273\321\217\321\206\320\270\321\217.os" "b/tests/\320\230\320\267\320\276\320\273\321\217\321\206\320\270\321\217.os" index ff9b8f6..2020f9d 100644 --- "a/tests/\320\230\320\267\320\276\320\273\321\217\321\206\320\270\321\217.os" +++ "b/tests/\320\230\320\267\320\276\320\273\321\217\321\206\320\270\321\217.os" @@ -252,6 +252,12 @@ Ожидаем.Что(СтрНайти(Результат.ОписанияПричин().Получить(0), "завершился аварийно")).Больше(0); + // Подробное описание несет вывод дочернего прогона - по нему видно, + // что именно пошло не так (неверный ключ JSON-источника) + ПодробноеОписание = ТестированиеСлужебный.ПодробноеОписаниеОшибки("Причина", Результат.Причины().Получить(0)); + + Ожидаем.Что(СтрНайти(ПодробноеОписание, "НеТотПараметр")).Больше(0); + КонецПроцедуры &Тест diff --git "a/tests/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" "b/tests/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" index 3a34edd..ea3eba7 100644 --- "a/tests/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" +++ "b/tests/\320\232\320\276\320\275\321\201\320\276\320\273\321\214\320\275\320\276\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.os" @@ -95,6 +95,34 @@ КонецПроцедуры +&Тест +Процедура ЧтениеКонфигурационногоФайлаПропускаетКомментарииИПустыеСтроки() Экспорт + + // Issue #25: строки-комментарии (начинаются с "#") и пустые строки + // роняли парсер попыткой обратиться ко второму элементу расщепленной + // по "=" строки, которого для таких строк не существует + МенеджерВременныхФайлов = Новый МенеджерВременныхФайлов(); + Каталог = МенеджерВременныхФайлов.СоздатьКаталог(); + + ПутьКФайлу = ОбъединитьПути(Каталог, "oscript.cfg"); + + ЗаписьТекста = Новый ЗаписьТекста(ПутьКФайлу); + ЗаписьТекста.ЗаписатьСтроку("#Конфигурационный файл OneScript"); + ЗаписьТекста.ЗаписатьСтроку(""); + ЗаписьТекста.ЗаписатьСтроку("# Произвольный ключ с пробелами вокруг ""="" - тоже валиден"); + ЗаписьТекста.ЗаписатьСтроку("custom.key = Значение"); + ЗаписьТекста.ЗаписатьСтроку("#custom.otherKey=Закомментировано"); + ЗаписьТекста.Закрыть(); + + Результат = Построитель().ПрочитатьКонфигурационныйФайл(ПутьКФайлу); + + Ожидаем.Что(Результат.ПолучитьИлиУмолчание("custom.key", "")).Равно("Значение"); + Ожидаем.Что(Результат.СодержитКлюч("custom.otherKey")).ЭтоЛожь(); + + МенеджерВременныхФайлов.Удалить(); + +КонецПроцедуры + #КонецОбласти #Область ТестыТочкиВхода