From 4d0cf47c059493b7b8f077353ae61d761a8ee356 Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Tue, 1 Sep 2026 11:38:27 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=D0=9E=D0=BF=D1=86=D0=B8=D1=8F=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B3=D0=BE=D0=BD=D0=B0=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B2=D0=BE=D0=BC=20=D1=83=D0=BF=D0=B0=D0=B2?= =?UTF-8?q?=D1=88=D0=B5=D0=BC=20=D1=82=D0=B5=D1=81=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Прогону, которому важен только ответ «зелено или нет», незачем доигрывать остаток после первого падения: проверка коммита, мутационное тестирование, локальный цикл правка-прогон. Опция --failFast и деталька OneUnit.ПрерыватьПриПервомПадении прекращают прогон на первом падении. Прогон именно прекращается, а не обрывается. Набор, в котором упал тест, сворачивается штатно: ПослеКаждого и ПослеВсех отрабатывают, и открытые ими файлы, соединения и временные каталоги закрываются. Падение отмечается подпиской на результат - и теста, и набора. Результаты приходят слишком разными путями, чтобы перечислять их по месту: пропуск, упавший в своём условии, изолированный тест, тесты изолированного набора, которые переэмитятся из дочернего прогона. Набор к тому же бывает красным без единого упавшего теста - со сломанным конструктором или ПередВсеми, а для мутационного тестирования это рядовой случай. То, до чего прогон не дошёл, получает результат Пропущен с причиной «Прогон прекращён на первом падении». Без этого репортёры, обходящие план, спрашивали результат у узла, у которого его нет: JUnit падал необработанным исключением и отчёт не писался вовсе, а изолированный набор терял результаты целиком - его дочерний прогон умирал на записи своего JSON-отчёта. Заодно счётчики перестают показывать план короче, чем он есть. Пропущенные и прерванные тесты прогон не останавливают: они ничего не говорят о работоспособности кода. Таймаут - говорит, и останавливает. Настройка передаётся изолированным дочерним прогонам, а MCP-инструмент run_tests получил параметр fail_fast. Ожидание в РазворачиваниеСпискаНаборовИзКаталоговИФайлов считается по каталогу, а не константой: тесту добавлены три фикстуры, и константа ломалась бы у каждого следующего. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 26 ++ ...20\276\320\262\320\260\321\202\321\214.os" | 10 + ...20\265\321\201\321\202\320\276\320\262.os" | 112 ++++++- ...20\273\321\217\321\206\320\270\320\270.os" | 5 + ...20\242\320\265\321\201\321\202\321\213.os" | 10 + ...20\260\320\265\320\274\321\213\320\271.os" | 41 +++ ...20\265\321\201\321\202\320\276\320\274.os" | 16 + ...20\275\320\265\321\200\320\276\320\274.os" | 13 + ...21\203\320\272\321\202\320\276\321\200.os" | 11 + ...20\222\321\201\320\265\320\274\320\270.os" | 12 + ...20\270\321\202\320\265\320\273\321\214.os" | 301 ++++++++++++++++++ ...20\242\320\265\321\201\321\202\321\213.os" | 8 +- 12 files changed, 560 insertions(+), 5 deletions(-) create mode 100644 "tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\237\321\200\320\265\321\200\321\213\320\262\320\260\320\265\320\274\321\213\320\271.os" create mode 100644 "tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\320\260\320\264\320\260\321\216\321\211\320\270\320\274\320\230\320\267\320\276\320\273\320\270\321\200\320\276\320\262\320\260\320\275\320\275\321\213\320\274\320\242\320\265\321\201\321\202\320\276\320\274.os" create mode 100644 "tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\265\321\200\321\213\320\262\320\260\320\265\320\274\321\213\320\274\320\232\320\276\320\275\321\202\320\265\320\271\320\275\320\265\321\200\320\276\320\274.os" create mode 100644 "tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\273\320\276\320\274\320\260\320\275\320\275\321\213\320\271\320\232\320\276\320\275\321\201\321\202\321\200\321\203\320\272\321\202\320\276\321\200.os" create mode 100644 "tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\273\320\276\320\274\320\260\320\275\320\275\321\213\320\271\320\237\320\265\321\200\320\265\320\264\320\222\321\201\320\265\320\274\320\270.os" diff --git a/README.md b/README.md index 9c8dae4..bba3d37 100644 --- a/README.md +++ b/README.md @@ -745,6 +745,10 @@ $ oneunit execute tree - Выводить дерево процесса исполнения теста, а также ошибки и статистику (по умолчанию tree) --debug Порт по которому нужно запустить сервер отладки (по умолчанию 0) + --failFast Прекратить прогон после первого упавшего теста. + Набор, в котором тест упал, сворачивается штатно: ПослеКаждого + и ПослеВсех отрабатывают. То, до чего прогон не дошёл, попадает + в отчёты пропущенным ``` ### 2.1.2 Работа с зависимостями @@ -754,6 +758,26 @@ $ oneunit execute > Важно При использовании локальных зависимостей проекта, пожалуйста, убедитесь, что у вас установлены зависимости версий не меньших, чем требуется для oneunit. Версии, необходимые для работы, указаны в [packagedef](packagedef), и если вы используете зависимости версий ниже, чем указано, то скорее всего вы получите разнообразные ошибки при запуске тестов. +### 2.1.3 Прекращение прогона на первом падении + +Опция `--failFast` останавливает прогон, как только что-то упало: + +```sh +oneunit execute -d tests --failFast +``` + +Это нужно там, где важен только ответ «зелено или нет», а не полная картина: проверка отдельного коммита, мутационное тестирование, локальный цикл правка-прогон. + +Прогон именно прекращается, а не обрывается. Набор, в котором упал тест, сворачивается штатно: `ПослеКаждого` упавшего теста и `ПослеВсех` набора отрабатывают, и открытые ими файлы, соединения и временные каталоги закрываются. + +Останавливает прогон любое падение, откуда бы оно ни пришло: упавший тест обычного набора, изолированный тест, тесты изолированного набора, а также набор, красный без единого упавшего теста — со сломанным конструктором или `ПередВсеми`. + +Пропущенные и прерванные тесты прогон не останавливают: они ничего не говорят о работоспособности кода. Таймаут — говорит, поэтому останавливает. + +То, до чего прогон не дошёл, не пропадает из отчётов: наборы и тесты за точкой остановки попадают в них пропущенными с причиной «Прогон прекращён на первом падении». Счётчики поэтому показывают весь набор целиком, а не только исполненную часть. + +Той же настройкой управляет деталька `OneUnit.ПрерыватьПриПервомПадении` — она пригодится тем, кто поднимает прогон программно, минуя командную строку. Опция командной строки просто записывает её значение, а прогон передаёт настройку своим изолированным дочерним прогонам. + ### 2.2 Отладка тестов Для того чтобы отладить написанные тестовые наборы, необходимо в vscode в конфигурации запуска отладки указать тип конфигурации не `launch`, а `attach`, а также указать порт отладки: @@ -862,6 +886,8 @@ claude mcp add oneunit -- cmd /c oneunit mcp Оба инструмента поддерживают параметры фильтрации, аналогичные опциям командной строки: каталоги (`directories`) и файлы (`files`) тестов, поиск в подкаталогах (`recursive`), фильтры по тегам (`tags_include`/`tags_exclude`), наборам (`suites_include`/`suites_exclude`) и методам (`methods_include`/`methods_exclude`), а `run_tests` дополнительно принимает таймаут теста в миллисекундах (`timeout`). Параметр `working_directory` позволяет указать каталог проекта, если он отличается от каталога запуска сервера. +Параметр `fail_fast` инструмента `run_tests` прекращает прогон на первом падении — см. [раздел 2.1.3](#213-прекращение-прогона-на-первом-падении). + Параметр `coverage` инструмента `run_tests` добавляет в ответ сводку покрытия кода тестами: общий процент (`percent`), количество покрытых и исполняемых строк (`lines_covered`/`lines_total`) и разбивку по файлам с номерами непокрытых строк — файлы отсортированы от наименее покрытых. Пример результата `run_tests`: diff --git "a/src/cli/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\321\202\321\214.os" "b/src/cli/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\321\202\321\214.os" index d3a51c9..4b24532 100644 --- "a/src/cli/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\321\202\321\214.os" +++ "b/src/cli/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\321\202\321\214.os" @@ -65,6 +65,15 @@ &ПоУмолчанию(0) Перем ПортОтладки; +&Опция(Имя = "failFast", Описание = "Прекратить прогон после первого упавшего теста. +| Набор, в котором тест упал, сворачивается штатно: ПослеКаждого +| и ПослеВсех отрабатывают. То, до чего прогон не дошёл, попадает +| в отчёты пропущенным +| ") +&ТБулево +&ПоУмолчанию(Ложь) +Перем ПрерыватьПриПервомПадении; + Перем _Лог; Перем _ЛогДляКомандыМенеджера; Перем _Запускатель; @@ -93,6 +102,7 @@ Детальки.Вставить("ТестовыеНаборыИсключаемые", ТестовыеНаборыИсключаемые); Детальки.Вставить("ТестовыеМетодыВключаемые", ТестовыеМетодыВключаемые); Детальки.Вставить("ТестовыеМетодыИсключаемые", ТестовыеМетодыИсключаемые); + Детальки.Вставить("ПрерыватьПриПервомПадении", ПрерыватьПриПервомПадении); Детальки.Вставить("РежимВыводаЛога", _Служебный.РежимВыводаЛога(РежимВывода)); 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 e8e73e5..3427668 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" @@ -5,15 +5,28 @@ Перем _Поделка; // Поделка - Поделка текущего прогона Перем _ПубликаторСобытий; // ПубликаторСобытий - Публикатор событий исполнения Перем _ИсполнительИзоляции; // ИсполнительИзоляции - Исполнитель изолированных наборов и тестов +Перем _ПрерыватьПриПадении; // Булево - Прекращать прогон после первого упавшего теста +Перем _Прервано; // Булево - Прогон прекращён, оставшиеся тесты не запускаются Процедура Исполнить(ТестПлан) Экспорт + // Поделка живёт дольше одного прогона: без сброса повторный Тестировать() + // не выполнил бы ни одного теста + _Прервано = Ложь; + _ПубликаторСобытий.ОпубликоватьСобытие(ТестПлан, "ИсполнениеТестПланНачало", Новый Массив); Наборы = ТестПлан.Дети(); Для Каждого Набор Из Наборы Цикл + // Проверка в начале, а не в конце тела: изолированный набор уходит + // на Продолжить и конец тела пропускает + Если _Прервано Тогда + ОтметитьНезапущенным(Набор); + Продолжить; + КонецЕсли; + Если Набор.Изоляция() <> Неопределено Тогда _ИсполнительИзоляции.ИсполнитьНабор(Набор); Продолжить; @@ -51,7 +64,14 @@ Если РезультатВыполненияНабора.Состояние = СостоянияВыполненияТестов.Успех Тогда Для Каждого Тест Из Набор.Дети() Цикл + + Если _Прервано Тогда + ОтметитьНезапущенным(Тест); + Продолжить; + КонецЕсли; + ВыполнитьТест(Тест, Набор, РезультатВыполненияНабора); + КонецЦикла; КонецЕсли; @@ -140,7 +160,14 @@ Если Тест.ТипОпределения() = ТипыОпределенийТестов.Контейнер Тогда Для Каждого ВложенныйТест Из Тест.Дети() Цикл + + Если _Прервано Тогда + ОтметитьНезапущенным(ВложенныйТест); + Продолжить; + КонецЕсли; + ВыполнитьТест(ВложенныйТест, ОпределениеТестНабора, РезультатВыполненияНабора); + КонецЦикла; // Контейнер всегда зелёный @@ -223,6 +250,82 @@ КонецПроцедуры +// Отмечает пропущенным то, до чего прекращённый прогон не дошёл. +// +// Прекращение не обрывает план: репортёры обходят план и спрашивают результат +// у каждого узла, а у незапущенного узла результата нет. Без этой отметки JUnit +// падал на пустом Опциональном, а изолированный набор терял результаты целиком - +// его дочерний прогон умирал на записи своего JSON-отчёта. Заодно счётчики +// перестают показывать план короче, чем он есть. +Процедура ОтметитьНезапущенным(Определение) + + Если ТипЗнч(Определение) = Тип("ОпределениеТестНабора") Тогда + СобытиеНачала = "ИсполнениеТестНаборНачало"; + СобытиеКонца = "ИсполнениеТестНаборКонец"; + Иначе + СобытиеНачала = "ИсполнениеТестНачало"; + СобытиеКонца = "ИсполнениеТестКонец"; + КонецЕсли; + + _ПубликаторСобытий.ОпубликоватьСобытие(Определение, СобытиеНачала, Новый Массив); + + Результат = _Поделка.НайтиЖелудь( + "РезультатВыполненияТеста", + Массивы.ИзЭлементов( + СостоянияВыполненияТестов.Пропущен, + Списки.ИзЭлементов("Прогон прекращён на первом падении"), + "" + ) + ); + + _ПубликаторСобытий.ОпубликоватьСобытие( + Определение, + СобытиеКонца, + Массивы.ИзЭлементов(Результат) + ); + +КонецПроцедуры + +// Отмечает падение по результату любого теста прогона. +// +// Подписка, а не проверка по месту: результаты приходят разными путями - обычный +// тест, пропуск, упавший в своём условии, изолированный тест и тесты изолированного +// набора, которые переэмитятся сюда из дочернего прогона. Проверять каждый путь +// по отдельности значит однажды забыть один из них. +&ПодпискаНаСобытие("ИсполнениеТестКонец") +Процедура ОтметитьПадениеТеста(Тест, Результат) Экспорт + + ОтметитьПадение(Результат); + +КонецПроцедуры + +// Набор может оказаться красным и без единого упавшего теста: сломанный конструктор, +// ошибка в ПередВсеми, отказ изолированной среды. +&ПодпискаНаСобытие("ИсполнениеТестНаборКонец") +Процедура ОтметитьПадениеНабора(Набор, Результат) Экспорт + + ОтметитьПадение(Результат); + +КонецПроцедуры + +Процедура ОтметитьПадение(Результат) + + Если Не _ПрерыватьПриПадении Или Результат = Неопределено Тогда + Возврат; + КонецЕсли; + + // Падение - это сломанный код: упавшее утверждение или ошибка исполнения. + // Пропущенные и прерванные прогон не останавливают, они ничего не говорят + // о работоспособности + Если Результат.Состояние() = СостоянияВыполненияТестов.Ошибка + Или Результат.Состояние() = СостоянияВыполненияТестов.Сломан Тогда + + _Прервано = Истина; + + КонецЕсли; + +КонецПроцедуры + Функция ВыполнитьМетод(Объект, ИмяМетода, Параметры, Таймаут) Делегат = Новый Действие(Объект, ИмяМетода); @@ -439,10 +542,13 @@ Процедура ПриСозданииОбъекта( &Пластилин Поделка, &Пластилин ПубликаторСобытий, - &Пластилин ИсполнительИзоляции) + &Пластилин ИсполнительИзоляции, + &Деталька("OneUnit.ПрерыватьПриПервомПадении", ЗначениеПоУмолчанию = Ложь) ПрерыватьПриПадении) - _Поделка = Поделка; - _ПубликаторСобытий = ПубликаторСобытий; + _Поделка = Поделка; + _ПубликаторСобытий = ПубликаторСобытий; _ИсполнительИзоляции = ИсполнительИзоляции; + _ПрерыватьПриПадении = ПрерыватьПриПадении; + _Прервано = Ложь; КонецПроцедуры diff --git "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" index acad735..105a5d7 100644 --- "a/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" +++ "b/src/core/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214\320\230\320\267\320\276\320\273\321\217\321\206\320\270\320\270.os" @@ -16,6 +16,7 @@ Перем _ПубликаторСобытий; // ПубликаторСобытий - Публикатор событий исполнения текущего прогона Перем _Запускатель; // ЗапускательТестирования - Запускатель дочерних прогонов Перем _КаталогСтатистикиПокрытия; // Строка - Каталог статистики покрытия прогона, пустая строка - без покрытия +Перем _ПрерыватьПриПадении; // Булево - Прекращать прогон после первого упавшего теста Перем _Лог; // Лог - Лог ядра // Выполняет изолированный тестовый набор в отдельной среде и публикует его @@ -499,6 +500,7 @@ Детальки.Вставить("ПутьКОтчетуJSON", ПутьКОтчету); Детальки.Вставить("ВыполняетсяВИзоляции", Истина); Детальки.Вставить("ТестовыеМетодыВключаемые", ФильтрМетода); + Детальки.Вставить("ПрерыватьПриПервомПадении", _ПрерыватьПриПадении); // Вывод дочернего прогона не транслируется в консоль родителя, чтобы не // разрывать его собственный вывод: пер-тестовые выводы и причины падений @@ -874,6 +876,7 @@ Детальки.Вставить("OneUnit.ВыполняетсяВИзоляции", Истина); Детальки.Вставить("OneUnit.РежимВыводаЛога", РежимыВыводаЛога.Ничего); Детальки.Вставить("OneUnit.ТестовыеМетодыВключаемые", ФильтрМетода); + Детальки.Вставить("OneUnit.ПрерыватьПриПервомПадении", _ПрерыватьПриПадении); // Консольный вывод дочернего прогона (свой лог ошибок и статистики) // перехватывается, чтобы не разрывать вывод родителя: причины и выводы @@ -1156,12 +1159,14 @@ &Пластилин ПубликаторСобытий, &Пластилин ЗапускательТестирования, &Деталька("OneUnit.КаталогСтатистикиПокрытия", ЗначениеПоУмолчанию = "") КаталогСтатистикиПокрытия, + &Деталька("OneUnit.ПрерыватьПриПервомПадении", ЗначениеПоУмолчанию = Ложь) ПрерыватьПриПадении, &Лог("oscript.lib.oneunit.core") Лог) _Поделка = Поделка; _ПубликаторСобытий = ПубликаторСобытий; _Запускатель = ЗапускательТестирования; _КаталогСтатистикиПокрытия = КаталогСтатистикиПокрытия; + _ПрерыватьПриПадении = ПрерыватьПриПадении; _Лог = Лог; КонецПроцедуры diff --git "a/src/mcp/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\320\222\321\213\320\277\320\276\320\273\320\275\320\270\321\202\321\214\320\242\320\265\321\201\321\202\321\213.os" "b/src/mcp/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\320\222\321\213\320\277\320\276\320\273\320\275\320\270\321\202\321\214\320\242\320\265\321\201\321\202\321\213.os" index 81b0c3f..e988427 100644 --- "a/src/mcp/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\320\222\321\213\320\277\320\276\320\273\320\275\320\270\321\202\321\214\320\242\320\265\321\201\321\202\321\213.os" +++ "b/src/mcp/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\320\222\321\213\320\277\320\276\320\273\320\275\320\270\321\202\321\214\320\242\320\265\321\201\321\202\321\213.os" @@ -98,6 +98,14 @@ ) Перем СобиратьПокрытие; +&ПараметрИнструмента( + Имя = "fail_fast", + Описание = "Прекратить прогон после первого упавшего теста. Набор, в котором тест упал, сворачивается + |штатно, а то, до чего прогон не дошёл, попадает в отчёт пропущенным", + Тип = "boolean" +) +Перем ПрерыватьПриПервомПадении; + Перем _Служебный; // MCPИнструментыСлужебный &Инструмент( @@ -150,6 +158,8 @@ МетодыИсключаемые ); + Детальки.Вставить("ПрерыватьПриПервомПадении", ПрерыватьПриПервомПадении = Истина); + РезультатПрогона = _Служебный.ВыполнитьТестирование( Детальки, Таймаут, diff --git "a/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\237\321\200\320\265\321\200\321\213\320\262\320\260\320\265\320\274\321\213\320\271.os" "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\237\321\200\320\265\321\200\321\213\320\262\320\260\320\265\320\274\321\213\320\271.os" new file mode 100644 index 0000000..b2ac712 --- /dev/null +++ "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\237\321\200\320\265\321\200\321\213\320\262\320\260\320\265\320\274\321\213\320\271.os" @@ -0,0 +1,41 @@ +// Набор для проверки прерывания на первом падении: первый тест зелёный, +// второй падает, третий не должен исполняться вовсе. Журнал выполнения +// показывает и то, что после падения отработали ПослеКаждого и ПослеВсех. + +#Использовать asserts +#Использовать "../../support" + +&ПередВсеми +Процедура ПередВсеми() Экспорт + ЖурналВыполнения.Очистить(); + ЖурналВыполнения.Записать("ПередВсеми"); +КонецПроцедуры + +&Тест +&Порядок(1) +Процедура Зеленый() Экспорт + ЖурналВыполнения.Записать("Зеленый"); +КонецПроцедуры + +&Тест +&Порядок(2) +Процедура Падающий() Экспорт + ЖурналВыполнения.Записать("Падающий"); + Ожидаем.Что(Ложь, "Падение намеренное").ЭтоИстина(); +КонецПроцедуры + +&Тест +&Порядок(3) +Процедура ПослеПадения() Экспорт + ЖурналВыполнения.Записать("ПослеПадения"); +КонецПроцедуры + +&ПослеКаждого +Процедура ПослеКаждого() Экспорт + ЖурналВыполнения.Записать("ПослеКаждого"); +КонецПроцедуры + +&ПослеВсех +Процедура ПослеВсех() Экспорт + ЖурналВыполнения.Записать("ПослеВсех"); +КонецПроцедуры diff --git "a/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\320\260\320\264\320\260\321\216\321\211\320\270\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/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\320\260\320\264\320\260\321\216\321\211\320\270\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..d847647 --- /dev/null +++ "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\320\260\320\264\320\260\321\216\321\211\320\270\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,16 @@ +// Изолированный тест падает в своей поделке, а его результат переэмитится +// в общий прогон. Соседний тест того же набора запускаться после этого не должен + +#Использовать asserts + +&Тест +&Изолированный +&Порядок(1) +Процедура ИзолированныйПадающий() Экспорт + Ожидаем.Что(Ложь, "Падение намеренное").ЭтоИстина(); +КонецПроцедуры + +&Тест +&Порядок(2) +Процедура СоседПоНабору() Экспорт +КонецПроцедуры diff --git "a/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\265\321\200\321\213\320\262\320\260\320\265\320\274\321\213\320\274\320\232\320\276\320\275\321\202\320\265\320\271\320\275\320\265\321\200\320\276\320\274.os" "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\265\321\200\321\213\320\262\320\260\320\265\320\274\321\213\320\274\320\232\320\276\320\275\321\202\320\265\320\271\320\275\320\265\321\200\320\276\320\274.os" new file mode 100644 index 0000000..848d82c --- /dev/null +++ "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\237\321\200\320\265\321\200\321\213\320\262\320\260\320\265\320\274\321\213\320\274\320\232\320\276\320\275\321\202\320\265\320\271\320\275\320\265\321\200\320\276\320\274.os" @@ -0,0 +1,13 @@ +// Параметризованный тест - контейнер: падает второй его случай, а третий +// запускаться не должен. Незапущенные случаи отмечаются так же, как тесты +// набора, иначе репортёры спрашивали бы результат у узла, у которого его нет + +#Использовать asserts + +&ПараметризованныйТест +&ИсточникЗначение(1) +&ИсточникЗначение(2) +&ИсточникЗначение(3) +Процедура ПадаетНаВтором(Параметр) Экспорт + Ожидаем.Что(Параметр, "Падение намеренное").Не_().Равно(2); +КонецПроцедуры diff --git "a/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\273\320\276\320\274\320\260\320\275\320\275\321\213\320\271\320\232\320\276\320\275\321\201\321\202\321\200\321\203\320\272\321\202\320\276\321\200.os" "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\273\320\276\320\274\320\260\320\275\320\275\321\213\320\271\320\232\320\276\320\275\321\201\321\202\321\200\321\203\320\272\321\202\320\276\321\200.os" new file mode 100644 index 0000000..7f59821 --- /dev/null +++ "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\273\320\276\320\274\320\260\320\275\320\275\321\213\320\271\320\232\320\276\320\275\321\201\321\202\321\200\321\203\320\272\321\202\320\276\321\200.os" @@ -0,0 +1,11 @@ +// Набор, который не удаётся инстанцировать: конструктор падает. +// Прогон узнаёт об этом до первого теста, и результат приходит от набора + +&Тест +Процедура ДоНегоНеДойдут() Экспорт +КонецПроцедуры + +&ТестовыйНабор +Процедура ПриСозданииОбъекта() + ВызватьИсключение "Конструктор набора сломан намеренно"; +КонецПроцедуры diff --git "a/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\273\320\276\320\274\320\260\320\275\320\275\321\213\320\271\320\237\320\265\321\200\320\265\320\264\320\222\321\201\320\265\320\274\320\270.os" "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\273\320\276\320\274\320\260\320\275\320\275\321\213\320\271\320\237\320\265\321\200\320\265\320\264\320\222\321\201\320\265\320\274\320\270.os" new file mode 100644 index 0000000..a599b25 --- /dev/null +++ "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\273\320\276\320\274\320\260\320\275\320\275\321\213\320\271\320\237\320\265\321\200\320\265\320\264\320\222\321\201\320\265\320\274\320\270.os" @@ -0,0 +1,12 @@ +// Набор, красный без единого упавшего теста: ПередВсеми ломается до тестов. +// Для мутационного тестирования это рядовой случай - мутант ломает подготовку +// набора, а не отдельную проверку + +&ПередВсеми +Процедура ПередВсеми() Экспорт + ВызватьИсключение "ПередВсеми сломан намеренно"; +КонецПроцедуры + +&Тест +Процедура ДоНегоНеДойдут() Экспорт +КонецПроцедуры diff --git "a/tests/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214.os" "b/tests/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214.os" index 2a7ae44..bfd0c4e 100644 --- "a/tests/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214.os" +++ "b/tests/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214.os" @@ -309,6 +309,216 @@ #КонецОбласти +#Область ТестыПрерыванияПриПадении + +&Тест +Процедура ПриПаденииОставшиесяТестыНеЗапускаются() Экспорт + + Прогон = ПрогонСПрерыванием(); + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "НаборПрерываемый"); + + Ожидаем.Что(Прогон.Статистика.Результат(ПрогоныТестов.НайтиТест(Набор, "Зеленый")).Состояние()) + .Равно(СостоянияВыполненияТестов.Успех); + + Ожидаем.Что(Прогон.Статистика.Результат(ПрогоныТестов.НайтиТест(Набор, "Падающий")).Состояние()) + .Равно(СостоянияВыполненияТестов.Ошибка); + + // в наборе три теста, а исполнились два: до третьего дело не дошло + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0), "Успешных").Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОшибка", 0), "Упавших").Равно(1); + +КонецПроцедуры + +&Тест +Процедура ПриПаденииНаборСворачиваетсяШтатно() Экспорт + + // Прогон прекращается, но не обрывается: ПослеКаждого упавшего теста и ПослеВсех + // набора обязаны отработать - там закрываются файлы, соединения и всё прочее, + // что набор открыл + + ПрогонСПрерыванием(); + + Ожидаем.Что(ЖурналВыполнения.Записи).РавноМассиву(СтрРазделить( + "ПередВсеми,Зеленый,ПослеКаждого,Падающий,ПослеКаждого,ПослеВсех", + "," + )); + +КонецПроцедуры + +&Тест +Процедура ПадениеВИзоляцииПрекращаетПрогон() Экспорт + + // Изолированный набор идёт своей средой, а его тесты переэмитятся в общий прогон. + // Падение там - такое же падение, и следующие наборы запускаться не должны + + Прогон = ПрогонСИзоляциейИПрерыванием(); + + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОшибка", 0), "Упавших").Равно(1); + + // второй набор идёт после красного изолированного и запуститься не должен + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0), "Успешных").Равно(0); + +КонецПроцедуры + +&Тест +Процедура НезапущенныйТестПопадаетВОтчетПропущенным() Экспорт + + // Репортёры обходят план и спрашивают результат у каждого узла. Без результата + // у незапущенного теста JUnit падал на пустом Опциональном, а изолированный набор + // терял результаты целиком + + Прогон = ПрогонСПрерыванием(); + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "НаборПрерываемый"); + + Ожидаем.Что(Прогон.Статистика.Результат(ПрогоныТестов.НайтиТест(Набор, "ПослеПадения")).Состояние()) + .Равно(СостоянияВыполненияТестов.Пропущен); + + // План обнаружил три теста, и столько же обязан показать счётчик + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОбнаружено", 0), "Обнаружено").Равно(3); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовПропущено", 0), "Пропущено").Равно(1); + +КонецПроцедуры + +&Тест +Процедура ОтчетJUnitПишетсяПриПрекращении() Экспорт + + // Отчёт обходит план и просит результат у каждого узла: на незапущенном тесте + // прогон падал необработанным исключением, а файл не создавался вовсе + + ПрогонСПрерыванием(); + + ПроверитьОтчет(ПрогоныТестов.ВременныйПуть("ОтчетПрерванногоJUnit", "xml"), "ПослеПадения"); + +КонецПроцедуры + +&Тест +Процедура ПадениеКонструктораПрекращаетПрогон() Экспорт + + // Набор бывает красным без единого упавшего теста. Для мутационного тестирования + // это рядовой случай: мутант ломает конструктор набора, а не отдельную проверку + + Прогон = ПрогонСоСломаннымНабором("ПрогонСоСломаннымКонструктором", "НаборСломанныйКонструктор"); + + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовСломано", 0), "Сломанных наборов").Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0), "Успешных тестов").Равно(0); + +КонецПроцедуры + +&Тест +Процедура ПадениеПередВсемиПрекращаетПрогон() Экспорт + + Прогон = ПрогонСоСломаннымНабором("ПрогонСоСломаннымПередВсеми", "НаборСломанныйПередВсеми"); + + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("НаборовСломано", 0), "Сломанных наборов").Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0), "Успешных тестов").Равно(0); + +КонецПроцедуры + +&Тест +Процедура ПадениеИзолированногоТестаПрекращаетПрогон() Экспорт + + // Изолированный тест уходит из общего потока и возвращается результатом. + // Прогон обязан остановиться на нём так же, как на обычном + + Прогон = ПрогонСИзолированнымТестом(); + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "НаборСПадающимИзолированнымТестом"); + + Ожидаем.Что(Прогон.Статистика.Результат(ПрогоныТестов.НайтиТест(Набор, "СоседПоНабору")).Состояние()) + .Равно(СостоянияВыполненияТестов.Пропущен); + + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0), "Успешных").Равно(0); + +КонецПроцедуры + +&Тест +Процедура НезапущенныйСлучайКонтейнераТожеПропускается() Экспорт + + // Случаи параметризованного теста живут в контейнере, а не прямо в наборе, + // и обходятся своим циклом. Репортёры спускаются в контейнер, поэтому + // результат нужен и его случаям + + ДопДетальки = Новый Соответствие; + ДопДетальки.Вставить("OneUnit.ПрерыватьПриПервомПадении", Истина); + ДопДетальки.Вставить( + "OneUnit.ПутьКОтчетуJUnit", + ПрогоныТестов.ВременныйПуть("ОтчетКонтейнераJUnit", "xml")); + + Прогон = ПрогоныТестов.ПрогнатьОднажды( + "ПрогонСПрерываемымКонтейнером", + "НаборСПрерываемымКонтейнером", + "", + "./tests/fixtures/execution/НаборСПрерываемымКонтейнером.os", + ДопДетальки + ); + + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "НаборСПрерываемымКонтейнером"); + Контейнер = ПрогоныТестов.НайтиТест(Набор, "ПадаетНаВтором"); + + ПроверитьСостоянияДетей(Прогон, Контейнер, Массивы.ИзЭлементов( + СостоянияВыполненияТестов.Успех, + СостоянияВыполненияТестов.Ошибка, + СостоянияВыполненияТестов.Пропущен + )); + + ПроверитьОтчет(ПрогоныТестов.ВременныйПуть("ОтчетКонтейнераJUnit", "xml"), "ПадаетНаВтором"); + +КонецПроцедуры + +&Тест +Процедура ПовторныйПрогонПослеПрекращенияВыполняетТесты() Экспорт + + // Поделка живёт дольше одного прогона: без сброса отметки второй Тестировать() + // молча не выполнил бы ни одного теста. + // Прогон свой, не из кэша: он гоняется дважды и чужие ожидания бы сбил + + ДопДетальки = Новый Соответствие; + ДопДетальки.Вставить("OneUnit.ПрерыватьПриПервомПадении", Истина); + + Прогон = ПрогоныТестов.Прогнать( + "НаборКрасныхТестов", + "", + "./tests/fixtures/НаборКрасныхТестов.os", + ДопДетальки + ); + + Прогон.Поделка.НайтиЖелудь("МенеджерТестирования").Тестировать(); + + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовОшибка", 0), "Упавших во втором прогоне").Равно(1); + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовПропущено", 0), "Пропущенных во втором прогоне").Равно(1); + +КонецПроцедуры + +&Тест +Процедура БезПрерыванияИдутВсеТесты() Экспорт + + Прогон = ПрогонБезПрерывания(); + Набор = ПрогоныТестов.НайтиНабор(Прогон.План, "НаборПрерываемый"); + + Ожидаем.Что(Прогон.Статистика.Результат(ПрогоныТестов.НайтиТест(Набор, "ПослеПадения")).Состояние()) + .Равно(СостоянияВыполненияТестов.Успех); + + Статистика = Прогон.Статистика.СтатистикаТестПлана(); + + Ожидаем.Что(Статистика.ПолучитьИлиУмолчание("ТестовУспешно", 0), "Успешных").Равно(2); + +КонецПроцедуры + +#КонецОбласти + #Область ТестыУсловий &Тест @@ -493,6 +703,97 @@ КонецФункции +// Прогон одного набора с прерыванием на первом падении. Журнал выполнения +// глобальный, поэтому набор в прогоне ровно один: иначе записи перемешались бы. +Функция ПрогонСПрерыванием() + + ДопДетальки = Новый Соответствие; + ДопДетальки.Вставить("OneUnit.ПрерыватьПриПервомПадении", Истина); + + // Отчёт пишется этим же прогоном: повторный прогон того же набора перетёр бы + // глобальный журнал выполнения, на который смотрит соседний тест + ДопДетальки.Вставить( + "OneUnit.ПутьКОтчетуJUnit", + ПрогоныТестов.ВременныйПуть("ОтчетПрерванногоJUnit", "xml")); + + Возврат ПрогоныТестов.ПрогнатьОднажды( + "ПрогонСПрерыванием", + "НаборПрерываемый", + "", + "./tests/fixtures/execution/НаборПрерываемый.os", + ДопДетальки + ); + +КонецФункции + +// Красный набор, за ним зелёный обычный: второй запускаться не должен. +// Набор красен без единого упавшего теста - ломается конструктор или ПередВсеми. +Функция ПрогонСоСломаннымНабором(Ключ, ИмяНабора) + + ДопДетальки = Новый Соответствие; + ДопДетальки.Вставить("OneUnit.ПрерыватьПриПервомПадении", Истина); + + ФайлыТестов = Новый Массив; + ФайлыТестов.Добавить(СтрШаблон("./tests/fixtures/execution/%1.os", ИмяНабора)); + ФайлыТестов.Добавить("./tests/fixtures/Тест.os"); + + Возврат ПрогоныТестов.ПрогнатьОднажды( + Ключ, + СтрШаблон("%1,Тест", ИмяНабора), + "", + СтрСоединить(ФайлыТестов, ","), + ДопДетальки + ); + +КонецФункции + +Функция ПрогонСИзолированнымТестом() + + ДопДетальки = Новый Соответствие; + ДопДетальки.Вставить("OneUnit.ПрерыватьПриПервомПадении", Истина); + + Возврат ПрогоныТестов.ПрогнатьОднажды( + "ПрогонСИзолированнымТестом", + "НаборСПадающимИзолированнымТестом", + "", + "./tests/fixtures/execution/НаборСПадающимИзолированнымТестом.os", + ДопДетальки + ); + +КонецФункции + +// Красный изолированный набор, за ним зелёный обычный: второй запускаться не должен. +// Порядок наборов в плане - порядок файлов в списке. +Функция ПрогонСИзоляциейИПрерыванием() + + ДопДетальки = Новый Соответствие; + ДопДетальки.Вставить("OneUnit.ПрерыватьПриПервомПадении", Истина); + + ФайлыТестов = Новый Массив; + ФайлыТестов.Добавить("./tests/fixtures/isolation/ИзолированныйПоделочныйКрасныйНабор.os"); + ФайлыТестов.Добавить("./tests/fixtures/Тест.os"); + + Возврат ПрогоныТестов.ПрогнатьОднажды( + "ПрогонСИзоляциейИПрерыванием", + "ИзолированныйПоделочныйКрасныйНабор,Тест", + "", + СтрСоединить(ФайлыТестов, ","), + ДопДетальки + ); + +КонецФункции + +Функция ПрогонБезПрерывания() + + Возврат ПрогоныТестов.ПрогнатьОднажды( + "ПрогонБезПрерывания", + "НаборПрерываемый", + "", + "./tests/fixtures/execution/НаборПрерываемый.os" + ); + +КонецФункции + Функция ПрогонПлоскогоЛога() ДопДетальки = Новый Соответствие; diff --git "a/tests/\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\320\242\320\265\321\201\321\202\321\213.os" "b/tests/\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\320\242\320\265\321\201\321\202\321\213.os" index bf2a766..3d5a963 100644 --- "a/tests/\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\320\242\320\265\321\201\321\202\321\213.os" +++ "b/tests/\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\320\242\320\265\321\201\321\202\321\213.os" @@ -53,8 +53,12 @@ Ложь ); - // 5 фикстур каталога execution + 1 явно переданный файл - Ожидаем.Что(Файлы).ИмеетДлину(6); + // Все фикстуры каталога execution плюс один явно переданный файл. + // Ожидание считается по каталогу, а не константой: иначе тест падал бы + // у каждого, кто добавит фикстуру + ФикстурКаталога = НайтиФайлы("./tests/fixtures/execution", "*.os").Количество(); + + Ожидаем.Что(Файлы).ИмеетДлину(ФикстурКаталога + 1); КонецПроцедуры From e92e81a8e45a76064cfe3d5d9135aea83a944468 Mon Sep 17 00:00:00 2001 From: Nikita Fedkin Date: Wed, 2 Sep 2026 08:42:44 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BE=D1=87=D0=B8=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B5=D0=BA=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B3=D0=BE=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Блок помощи снова дословный: у --failFast убрана последняя строка-продолжение Описания. У --mode на неё ложится "(по умолчанию tree)", а у булевой опции умолчание не печатается, и оставалась строка из одних пробелов. Пропущенный контейнер выводится с символом и причиной. Заголовок контейнера паркуется так же, как заголовок набора: о состоянии контейнера говорят его случаи, а если не выполнялся ни один - его собственный результат. В README смягчено утверждение про счётчики: целиком виден набор, в котором прогон встал, а набор за точкой остановки и недостижимый контейнер своих тестов не разворачивают - как набор, выключенный условием. РазворачиваниеСпискаНаборовИзКаталоговИФайлов сверяет состав, а не длину: длина сошлась бы и у списка из чужих файлов. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 4 +- ...20\276\320\262\320\260\321\202\321\214.os" | 6 +- ...20\265\321\200\320\265\320\262\320\276.os" | 8 ++- ...20\275\320\265\321\200\320\276\320\274.os" | 18 ++++++ ...20\270\321\202\320\265\320\273\321\214.os" | 58 +++++++++++++++++++ ...20\242\320\265\321\201\321\202\321\213.os" | 18 ++++-- 6 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 "tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\264\320\276\321\201\321\202\320\270\320\266\320\270\320\274\321\213\320\274\320\232\320\276\320\275\321\202\320\265\320\271\320\275\320\265\321\200\320\276\320\274.os" diff --git a/README.md b/README.md index bba3d37..4ac682e 100644 --- a/README.md +++ b/README.md @@ -774,7 +774,9 @@ oneunit execute -d tests --failFast Пропущенные и прерванные тесты прогон не останавливают: они ничего не говорят о работоспособности кода. Таймаут — говорит, поэтому останавливает. -То, до чего прогон не дошёл, не пропадает из отчётов: наборы и тесты за точкой остановки попадают в них пропущенными с причиной «Прогон прекращён на первом падении». Счётчики поэтому показывают весь набор целиком, а не только исполненную часть. +То, до чего прогон не дошёл, не пропадает из отчётов: наборы и тесты за точкой остановки попадают в них пропущенными с причиной «Прогон прекращён на первом падении». Набор, в котором прогон встал, поэтому виден целиком, а не только исполненной частью. + +Набор целиком за точкой остановки своих тестов не разворачивает — как и набор, выключенный условием: он пропущен и в счёте тестов не участвует. То же с параметризованным тестом, до которого прогон не дошёл, — пропущен он сам, а не каждый его случай. Той же настройкой управляет деталька `OneUnit.ПрерыватьПриПервомПадении` — она пригодится тем, кто поднимает прогон программно, минуя командную строку. Опция командной строки просто записывает её значение, а прогон передаёт настройку своим изолированным дочерним прогонам. diff --git "a/src/cli/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\321\202\321\214.os" "b/src/cli/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\321\202\321\214.os" index 4b24532..e8b7632 100644 --- "a/src/cli/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\321\202\321\214.os" +++ "b/src/cli/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\242\320\265\321\201\321\202\320\270\321\200\320\276\320\262\320\260\321\202\321\214.os" @@ -65,11 +65,13 @@ &ПоУмолчанию(0) Перем ПортОтладки; +// Последней строки-продолжения здесь нет намеренно: у --mode на неё ложится +// "(по умолчанию tree)", а у булевой опции умолчание не печатается, и осталась бы +// пустая строка из пробелов &Опция(Имя = "failFast", Описание = "Прекратить прогон после первого упавшего теста. | Набор, в котором тест упал, сворачивается штатно: ПослеКаждого | и ПослеВсех отрабатывают. То, до чего прогон не дошёл, попадает -| в отчёты пропущенным -| ") +| в отчёты пропущенным") &ТБулево &ПоУмолчанию(Ложь) Перем ПрерыватьПриПервомПадении; 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\233\320\276\320\263\320\224\320\265\321\200\320\265\320\262\320\276.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\233\320\276\320\263\320\224\320\265\321\200\320\265\320\262\320\276.os" index d0d19a4..7b41134 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\200\320\233\320\276\320\263\320\224\320\265\321\200\320\265\320\262\320\276.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\233\320\276\320\263\320\224\320\265\321\200\320\265\320\262\320\276.os" @@ -72,7 +72,10 @@ Процедура ИсполнениеТестНачало(Тест) Экспорт РаспарковатьКонтейнер(); Если Тест.ТипОпределения() = ТипыОпределенийТестов.Контейнер Тогда - ВыводКонтейнера(Тест); + // Заголовок контейнера паркуется, как заголовок набора: о состоянии + // контейнера говорят его дети, а если детей не было - его собственный + // результат, и тогда заголовок без символа и причины ничего не значил бы + ЗапаркованныйКонтейнер = Тест; КонецЕсли; КонецПроцедуры @@ -81,6 +84,9 @@ Если Тест.ТипОпределения() = ТипыОпределенийТестов.Контейнер Тогда + // Контейнер всё ещё запаркован - значит, ни один его случай не выполнялся + РаспарковатьКонтейнер(Результат); + Префикс.Удалить(Префикс.Получить(Префикс.ВГраница())); ТекущийКонтейнер = Тест.Родитель(); diff --git "a/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\264\320\276\321\201\321\202\320\270\320\266\320\270\320\274\321\213\320\274\320\232\320\276\320\275\321\202\320\265\320\271\320\275\320\265\321\200\320\276\320\274.os" "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\264\320\276\321\201\321\202\320\270\320\266\320\270\320\274\321\213\320\274\320\232\320\276\320\275\321\202\320\265\320\271\320\275\320\265\321\200\320\276\320\274.os" new file mode 100644 index 0000000..7625d46 --- /dev/null +++ "b/tests/fixtures/execution/\320\235\320\260\320\261\320\276\321\200\320\241\320\235\320\265\320\264\320\276\321\201\321\202\320\270\320\266\320\270\320\274\321\213\320\274\320\232\320\276\320\275\321\202\320\265\320\271\320\275\320\265\321\200\320\276\320\274.os" @@ -0,0 +1,18 @@ +// Падение до контейнера: ни один случай параметризованного теста не выполняется. +// О состоянии контейнера обычно говорят его дети, а здесь детей нет вовсе + +#Использовать asserts + +&Тест +&Порядок(1) +Процедура ПадаетДоКонтейнера() Экспорт + Ожидаем.Что(Ложь, "Падение намеренное").ЭтоИстина(); +КонецПроцедуры + +&ПараметризованныйТест +&Порядок(2) +&ИсточникЗначение(1) +&ИсточникЗначение(2) +Процедура НедостижимыйКонтейнер(Параметр) Экспорт + Ожидаем.Что(Параметр).Заполнено(); +КонецПроцедуры diff --git "a/tests/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214.os" "b/tests/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214.os" index bfd0c4e..65d8865 100644 --- "a/tests/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214.os" +++ "b/tests/\320\230\321\201\320\277\320\276\320\273\320\275\320\270\321\202\320\265\320\273\321\214.os" @@ -476,6 +476,48 @@ КонецПроцедуры +&Тест +Процедура ПропущенныйКонтейнерВыводитсяССимволомИПричиной() Экспорт + + // О состоянии контейнера обычно говорят его дети, поэтому заголовок печатается + // без символа. Если ни один случай не выполнялся, заголовок остаётся пустой + // строкой - хотя причина у контейнера есть, и в отчёты она попадает + + ПотокВывода = Новый ПотокВПамяти(); + ТестированиеСлужебный.НачатьПерехватВывода(ПотокВывода); + + Попытка + ДопДетальки = Новый Соответствие; + ДопДетальки.Вставить("OneUnit.ПрерыватьПриПервомПадении", Истина); + ДопДетальки.Вставить("OneUnit.РежимВыводаЛога", РежимыВыводаЛога.Дерево); + + ПрогоныТестов.Прогнать( + "НаборСНедостижимымКонтейнером", + "", + "./tests/fixtures/execution/НаборСНедостижимымКонтейнером.os", + ДопДетальки + ); + Исключение + ТестированиеСлужебный.ЗавершитьПерехватВывода(); + ВызватьИсключение; + КонецПопытки; + + ТестированиеСлужебный.ЗавершитьПерехватВывода(); + + Вывод = ПрогоныТестов.ПрочитатьПоток(ПотокВывода); + + СтрокаКонтейнера = СтрокаВыводаС(Вывод, "НедостижимыйКонтейнер"); + + Ожидаем.Что( + СтрНайти(СтрокаКонтейнера, СостоянияВыполненияТестов.СимволСостояния(СостоянияВыполненияТестов.Пропущен)), + "Символ пропуска у контейнера" + ).Больше(0); + + Ожидаем.Что(СтрНайти(СтрокаКонтейнера, "Прогон прекращён на первом падении"), "Причина у контейнера") + .Больше(0); + +КонецПроцедуры + &Тест Процедура ПовторныйПрогонПослеПрекращенияВыполняетТесты() Экспорт @@ -877,6 +919,22 @@ КонецПроцедуры +// Возвращает первую строку вывода, содержащую подстроку. +// +// Дерево печатается построчно, и проверять надо строку узла, а не весь вывод: +// причина того же пропуска встречается там же у соседних узлов +Функция СтрокаВыводаС(Знач Вывод, Знач Подстрока) + + Для Каждого Строка Из СтрРазделить(Вывод, Символы.ПС) Цикл + Если СтрНайти(Строка, Подстрока) > 0 Тогда + Возврат Строка; + КонецЕсли; + КонецЦикла; + + ВызватьИсключение СтрШаблон("В выводе нет строки с ""%1"":%2%3", Подстрока, Символы.ПС, Вывод); + +КонецФункции + Процедура ПроверитьОтчет(ПутьКОтчету, ОжидаемоеСодержимое) Ожидаем.Что(Новый Файл(ПутьКОтчету).Существует(), "Отчёт существует: " + ПутьКОтчету).ЭтоИстина(); diff --git "a/tests/\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\320\242\320\265\321\201\321\202\321\213.os" "b/tests/\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\320\242\320\265\321\201\321\202\321\213.os" index 3d5a963..4103bd0 100644 --- "a/tests/\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\320\242\320\265\321\201\321\202\321\213.os" +++ "b/tests/\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\320\242\320\265\321\201\321\202\321\213.os" @@ -53,12 +53,22 @@ Ложь ); - // Все фикстуры каталога execution плюс один явно переданный файл. - // Ожидание считается по каталогу, а не константой: иначе тест падал бы + // Сверяется состав, а не длина: длина сошлась бы и у списка из чужих файлов. + // Ожидание при этом строится по каталогу, а не константой - иначе тест падал бы // у каждого, кто добавит фикстуру - ФикстурКаталога = НайтиФайлы("./tests/fixtures/execution", "*.os").Количество(); + Ожидаемые = Новый Соответствие; - Ожидаем.Что(Файлы).ИмеетДлину(ФикстурКаталога + 1); + Для Каждого Фикстура Из НайтиФайлы("./tests/fixtures/execution", "*.os") Цикл + Ожидаемые.Вставить(Фикстура.ПолноеИмя, Истина); + КонецЦикла; + + Ожидаемые.Вставить(Новый Файл("./tests/fixtures/Тест.os").ПолноеИмя, Истина); + + Ожидаем.Что(Файлы).ИмеетДлину(Ожидаемые.Количество()); + + Для Каждого Файл Из Файлы Цикл + Ожидаем.Что(Ожидаемые.Получить(Файл.ПолноеИмя), "Развёрнут " + Файл.ПолноеИмя).ЭтоИстина(); + КонецЦикла; КонецПроцедуры