Понедельник, одиннадцать двадцать. Уровень лежит на краю стола с воскресенья: я так и не повесила полку, а инструмент, которым проверяют, ровно ли она висит, оставила там, где он не нужен. За окном серо, лампа горит с утра, на экране строка вердикта из двух слов — «НЕ ПРОЙДЕНО». Ни причины, ни номера, ни того, какая стадия.

Полка висит криво третью неделю. Уровень показывает, что криво не полка, а стена, и с этим я согласна.

Что сказали ворота

Ворота — скрипт, который берёт коммит, разворачивает его в одноразовом контейнере и гоняет стадии: типы, линт, подготовительные проверки, тесты, контракты с базой. Запускают их руками, потому что автоматические проверки в репозитории выключены: все восемь workflow с тридцатого октября в состоянии disabled_manually, и последний запуск, созданный в тот день в 08:30:03 UTC, упал на старте.

Первого ноября на голове основной ветки, коммит 5942b5a8, ворота ответили «не пройдено». Разбор по стадиям: типы, линт, подготовительные проверки и контракты с базой зелёные, контрактов 1587. В стадии тестов упали четыре из 40 536. Четыре из сорока с лишним тысяч — это не «всё плохо» и не «мелочь»: это четыре теста, у которых есть имя.

Четыре падения без продуктовой причины

Два теста проверяют, что сигнал остановки прерывает зависший дамп и зависшее восстановление. В контейнере первый процесс — sleep infinity. Он не подбирает сирот: убитый процесс остаётся зомби, а проверка «жив ли» честно отвечает «жив». Тест падает не потому, что дамп не прервался, а потому что на зомби смотрят как на живого.

Ещё два — про висячие тома. Образ раннера node:20-bookworm несёт mawk 1.3.4, а этот awk не понимает интервал {64} в регулярном выражении. Том без метки, с именем из шестидесяти четырёх шестнадцатеричных знаков, не распознаётся как висячий, и тест, который проверяет уборку, видит неубранное.

В одну строку: проверка сломалась на собственном инструменте. Ни один из четырёх тестов не касался кода, который эти ворота должны были проверить.

Три строки и один гард

Правка 2182: восемнадцать добавленных строк, одна удалённая, два файла. В скрипт — флаг --init у запуска контейнера. По документации Docker он ставит первым процессом инициализатор, и тот делает то, чего не делает sleep: подбирает зомби; по умолчанию это docker-init на основе tini. Второй файл — гард, который проверяет не поведение, а сам запуск: нет --init в команде — гард падает.

Автор показал три вещи, и все три проверяемы. Гард падает на старом скрипте и проходит на новом: 75 из 75 вместе с соседним набором. Тот же набор в реальном образе, где первый процесс — sleep infinity: без инициализатора 2 упало из 39, с ним 39 из 39. И механизм отдельно, без тестов: осиротевший процесс, убитый сигналом, в обычном контейнере остаётся зомби, с инициализатором исчезает.

Вторую половину закрыли не здесь. Про mawk в описании правки сказано прямо: чинится в 2156, версией скрипта с регулярным выражением bash вместо awk. 2156 вошла в основную ветку сегодня утром, в 09:21 по Москве: 394 добавленные строки, и в скрипте уборки теперь [[ ... =~ ^[0-9a-f]{64}$ ]]. Я открыла файл в основной ветке и увидела эту строку: вторая причина падений не объяснена, а убрана.

Чего эти ворота не доказали

Полного повторного прогона ворот с этой правкой не было — об этом написано в самой правке: около часа, и два падения про тома останутся до слияния 2156. Значит, вердикт «не пройдено» на голове ветки до сих пор ничем не заменён. Гард доказывает, что флаг на месте; замер в контейнере доказывает, что с флагом эти тесты проходят. Что ворота теперь зелёные целиком, не доказано никем — и я этого не утверждаю.

Это не придирка к автору, а свойство жанра. У скрипта четыре кода выхода, и они разделены аккуратно: ноль — все стадии пройдены, один — упала хотя бы одна, два — ворота не смогли исполниться, и это, дословно из скрипта, «НЕ вердикт о коде», три — частичный прогон, полными воротами не считается. Словарь хороший. В нём нет только одного состояния: прогон состоялся, упал, и упал не по коду. Именно это случилось первого ноября, и именно поэтому вердикт пришлось разбирать руками, по именам тестов.

Четыре падения из 40 536 — если считать честно, это лучше, чем всё зелёное: у каждого падения есть имя и причина, и видно, что чинить надо не продукт. Хуже другое: такую проверку нельзя запустить в следующий раз и получить ответ, не читая.

Полку я так и не повесила. Уровень показывает, что стена уходит от вертикали на пару миллиметров на всю высоту, и это объясняет, почему полка висит криво, — но не объясняет, почему я три недели смотрела на пузырёк и ничего не делала.

С воротами проще: у них теперь есть флаг, гард и вторая половина в правке 2156. Не хватает одного прогона — часа, который пока никто не потратил.