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..acad735
--- /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,1167 @@
+// Выполняет изолированные наборы и тесты и вклеивает их результаты в общий прогон.
+// Изолированный набор прогоняется в собственной среде - свежей поделке или дочернем
+// процессе - а его результаты переэмитятся событиями исполнения на публикатор
+// родителя, так что для репортеров и статистики это обычные тесты.
+// Для чистого обнаружения (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..e36e3cc
--- /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,518 @@
+// Сборка точки входа дочернего прогона: временный сценарий с импортами тестов,
+// загрузкой ядра и запуском тестирования, а также конфигурационный файл 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");
+
+ ЗаписьТекста = Новый ЗаписьТекста();
+ ЗаписьТекста.Открыть(ПутьККонфигурационномуФайлу);
+
+ ДанныеКонфигурационногоФайла.ДляКаждого(
+ "Ключ, Значение -> ЗаписьТекста.ЗаписатьСтроку(
+ | Ключ + ""="" +
+ | ?(ТипЗнч(Значение) = Тип(""МножествоСоответствие"") Или ТипЗнч(Значение) = Тип(""ФиксированноеМножество""),
+ | СтрСоединить(Значение.ВМассив(), "";""),
+ | Значение
+ | ));",
+ Новый Структура("ЗаписьТекста", ЗаписьТекста)
+ );
+
+ ЗаписьТекста.Закрыть();
+
+ Возврат ПутьККонфигурационномуФайлу;
+
+КонецФункции
+
+Функция ПрочитатьКонфигурационныйФайл(Путь) Экспорт
+
+ ТекущийКаталог = ТекущийКаталог();
+
+ Результат = Новый КартаСоответствие();
+
+ ЧтениеТекста = Новый ЧтениеТекста(Путь);
+
+ ПрочитаннаяСтрока = ЧтениеТекста.ПрочитатьСтроку();
+
+ Пока ПрочитаннаяСтрока <> Неопределено Цикл
+
+ СтрокаБезПробелов = СокрЛП(ПрочитаннаяСтрока);
+ ПозицияРавно = СтрНайти(СтрокаБезПробелов, "=");
+
+ // Пустые строки и строки-комментарии (начинаются с "#", без "=")
+ // пропускаются - формат oscript.cfg допускает и то, и другое между
+ // записями "ключ=значение"
+ Если ПустаяСтрока(СтрокаБезПробелов)
+ Или СтрНачинаетсяС(СтрокаБезПробелов, "#")
+ Или ПозицияРавно = 0 Тогда
+
+ ПрочитаннаяСтрока = ЧтениеТекста.ПрочитатьСтроку();
+ Продолжить;
+
+ КонецЕсли;
+
+ Ключ = СокрЛП(Лев(СтрокаБезПробелов, ПозицияРавно - 1));
+ ЗначениеСтрокой = СокрЛП(Сред(СтрокаБезПробелов, ПозицияРавно + 1));
+
+ УстановитьТекущийКаталог(Новый Файл(Путь).Путь);
+
+ Если СтрНачинаетсяС(Ключ, "lib.") Тогда
+
+ Если СтрНачинаетсяС(Ключ, "lib.additional") Тогда
+
+ Значение = ПроцессорыКоллекций.ИзКоллекции(СтрРазделить(ЗначениеСтрокой, ";", Ложь))
+ .Фильтровать("Путь -> ФС.КаталогСуществует(Путь)")
+ .Обработать("Путь -> ФС.НормализоватьПуть(Путь)")
+ .Получить("МножествоСоответствие");
+
+ Иначе
+ Значение = ?(ФС.КаталогСуществует(ЗначениеСтрокой), ФС.НормализоватьПуть(ЗначениеСтрокой), "");
+ КонецЕсли;
+
+ Иначе
+ Значение = ЗначениеСтрокой;
+ КонецЕсли;
+
+ УстановитьТекущийКаталог(ТекущийКаталог);
+
+ Если ЗначениеЗаполнено(Значение) Тогда
+ Результат.Вставить(Ключ, Значение);
+ КонецЕсли;
+
+ ПрочитаннаяСтрока = ЧтениеТекста.ПрочитатьСтроку();
+
+ КонецЦикла;
+
+ ЧтениеТекста.Закрыть();
+
+ Возврат Результат;
+
+КонецФункции
+
+Функция НастройкиЛогоса(Компонент, Раскладка) Экспорт
+
+ Возврат Соответствия.ИзЭлементов(
+ "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..2020f9d
--- /dev/null
+++ "b/tests/\320\230\320\267\320\276\320\273\321\217\321\206\320\270\321\217.os"
@@ -0,0 +1,512 @@
+// Тесты изоляции прогонов на уровне поделки.
+//
+// Внимание: у этого сценария намеренно нет конструктора с аннотацией &ТестовыйНабор,
+// см. пояснение в Исполнитель.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);
+
+ // Подробное описание несет вывод дочернего прогона - по нему видно,
+ // что именно пошло не так (неверный ключ JSON-источника)
+ ПодробноеОписание = ТестированиеСлужебный.ПодробноеОписаниеОшибки("Причина", Результат.Причины().Получить(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..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"
@@ -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", "")).Равно("Значение");
@@ -93,6 +95,34 @@
КонецПроцедуры
+&Тест
+Процедура ЧтениеКонфигурационногоФайлаПропускаетКомментарииИПустыеСтроки() Экспорт
+
+ // Issue #25: строки-комментарии (начинаются с "#") и пустые строки
+ // роняли парсер попыткой обратиться ко второму элементу расщепленной
+ // по "=" строки, которого для таких строк не существует
+ МенеджерВременныхФайлов = Новый МенеджерВременныхФайлов();
+ Каталог = МенеджерВременныхФайлов.СоздатьКаталог();
+
+ ПутьКФайлу = ОбъединитьПути(Каталог, "oscript.cfg");
+
+ ЗаписьТекста = Новый ЗаписьТекста(ПутьКФайлу);
+ ЗаписьТекста.ЗаписатьСтроку("#Конфигурационный файл OneScript");
+ ЗаписьТекста.ЗаписатьСтроку("");
+ ЗаписьТекста.ЗаписатьСтроку("# Произвольный ключ с пробелами вокруг ""="" - тоже валиден");
+ ЗаписьТекста.ЗаписатьСтроку("custom.key = Значение");
+ ЗаписьТекста.ЗаписатьСтроку("#custom.otherKey=Закомментировано");
+ ЗаписьТекста.Закрыть();
+
+ Результат = Построитель().ПрочитатьКонфигурационныйФайл(ПутьКФайлу);
+
+ Ожидаем.Что(Результат.ПолучитьИлиУмолчание("custom.key", "")).Равно("Значение");
+ Ожидаем.Что(Результат.СодержитКлюч("custom.otherKey")).ЭтоЛожь();
+
+ МенеджерВременныхФайлов.Удалить();
+
+КонецПроцедуры
+
#КонецОбласти
#Область ТестыТочкиВхода
@@ -109,7 +139,7 @@
// ФиксированныйМассив, чтобы закрепить поддержку обоих типов массивов в детальках
Детальки = Детальки(Массивы.ИзЭлементов("./tests/fixtures"), ФайлыТестов);
- ТочкаВхода = Служебный().СоздатьТочкуВхода(
+ ТочкаВхода = Построитель().СоздатьТочкуВхода(
Детальки,
МенеджерВременныхФайлов,
Логирование.ПолучитьЛог("oscript.lib.oneunit.тесты"),
@@ -156,7 +186,7 @@
Детальки = Детальки(Новый Массив, Новый Массив);
- Служебный().СоздатьТочкуВхода(
+ Построитель().СоздатьТочкуВхода(
Детальки,
МенеджерВременныхФайлов,
Логирование.ПолучитьЛог("oscript.lib.oneunit.тесты"),
@@ -377,8 +407,12 @@
Возврат Новый КонсольноеПриложениеТестированиеСлужебный();
КонецФункции
+Функция Построитель()
+ Возврат Новый ПостроительТочкиВхода();
+КонецФункции
+
Функция Запускатель()
- Возврат Новый ЗапускательТестирования(Служебный());
+ Возврат Новый ЗапускательТестирования(Построитель());
КонецФункции
Функция Детальки(КаталогиТестов, ФайлыТестов, Знач РежимВыводаЛога = "")