В #272 мутационный гейт полдня держал PR красным на пустом месте: он объявлял выжившими мутантов, при прогоне которых не выполнялось ни одного теста.
Что происходит
В reports/mutation/mutation.json у таких мутантов testsCompleted: 0 при сотне покрывающих:
CallExpression 110:13 coveredBy: 99 testsCompleted: 0
LogicalOperator 122:17 coveredBy: 11 testsCompleted: 0
В trace-логе Stryker'а тот же прогон выглядит так:
Test Files 2 skipped (5)
Tests 33 skipped (47)
Duration 309ms ... tests 0ms
Ни одного упавшего теста нет — и Stryker пишет «мутант выжил».
Набор таких мутантов плавает от прогона к прогону. Один и тот же коммит на CI: 5 выживших → перезапуск той же джобы → 6 других. Локально по одному файлу два прогона подряд дали разных единственных выживших. При этом каждый из них при ручной подстановке в код валит от одного до четырёх тестов — то есть тесты их убивают, гейт просто их не запускает.
Что не помогает:
| режим |
результат |
coverageAnalysis: perTest (как сейчас) |
выжившие с testsCompleted: 0 |
coverageAnalysis: off |
то же, другой набор |
--maxTestRunnerReuse 1 |
хуже (5 вместо 2) |
vitest.related: false |
dry-run падает на постороннем тесте diffing fixtures difficult-move |
Похоже на дефект связки @stryker-mutator/vitest-runner@10 ↔ vitest@4: в логе рядом висит DEPRECATED test.poolOptions was removed in Vitest 4 — раннер отдаёт vitest конфиг старого формата. Тем же классом хрупкости выглядит и RuntimeError: Test runner crashed. Tried twice to restart it, который иногда прилетает на отдельных мутантах (порог он не ломает — Stryker считает ошибки отдельной колонкой).
Что уже сделано
В #272 гейт (scripts/mutation-diff.mjs) научился различать «никто не заметил» и «ничего не запускали»: выживший с testsCompleted > 0 роняет прогон сразу, а нулевые перепроверяются вторым точечным прогоном по диапазону строк самого мутанта — там прогон надёжен. Перепроверенные помечаются в отчёте Killed с причиной; пустая перепроверка — падение, а не зелёный.
Это обходной путь, а не лечение.
Что стоит сделать дальше
- Собрать минимальный воспроизводимый пример и завести issue в апстриме StrykerJS (или найти существующий — судя по симптомам, это должно всплывать у всех, кто на vitest 4).
- Проверить, чинит ли проблему новая версия раннера, когда выйдет; после этого обходной путь из
mutation-diff.mjs снять, а врезку из docs/TESTING.md («Выжил с нулём выполненных тестов — не находка») убрать вместе с ним.
- Заодно посмотреть на
RuntimeError: Test runner crashed — сейчас он не влияет на порог, но это тот же симптом.
В #272 мутационный гейт полдня держал PR красным на пустом месте: он объявлял выжившими мутантов, при прогоне которых не выполнялось ни одного теста.
Что происходит
В
reports/mutation/mutation.jsonу таких мутантовtestsCompleted: 0при сотне покрывающих:В trace-логе Stryker'а тот же прогон выглядит так:
Ни одного упавшего теста нет — и Stryker пишет «мутант выжил».
Набор таких мутантов плавает от прогона к прогону. Один и тот же коммит на CI: 5 выживших → перезапуск той же джобы → 6 других. Локально по одному файлу два прогона подряд дали разных единственных выживших. При этом каждый из них при ручной подстановке в код валит от одного до четырёх тестов — то есть тесты их убивают, гейт просто их не запускает.
Что не помогает:
coverageAnalysis: perTest(как сейчас)testsCompleted: 0coverageAnalysis: off--maxTestRunnerReuse 1vitest.related: falsediffing fixtures difficult-moveПохоже на дефект связки
@stryker-mutator/vitest-runner@10↔vitest@4: в логе рядом виситDEPRECATED test.poolOptions was removed in Vitest 4— раннер отдаёт vitest конфиг старого формата. Тем же классом хрупкости выглядит иRuntimeError: Test runner crashed. Tried twice to restart it, который иногда прилетает на отдельных мутантах (порог он не ломает — Stryker считает ошибки отдельной колонкой).Что уже сделано
В #272 гейт (
scripts/mutation-diff.mjs) научился различать «никто не заметил» и «ничего не запускали»: выживший сtestsCompleted > 0роняет прогон сразу, а нулевые перепроверяются вторым точечным прогоном по диапазону строк самого мутанта — там прогон надёжен. Перепроверенные помечаются в отчётеKilledс причиной; пустая перепроверка — падение, а не зелёный.Это обходной путь, а не лечение.
Что стоит сделать дальше
mutation-diff.mjsснять, а врезку изdocs/TESTING.md(«Выжил с нулём выполненных тестов — не находка») убрать вместе с ним.RuntimeError: Test runner crashed— сейчас он не влияет на порог, но это тот же симптом.