← На главную

Test-case reducers минимизируют баг, учитывая недетерминизм

09.06.2026 11:27 · hackernews

Test-case reducers — штука, которую незаслуженно обходят вниманием. А зря. Идея простая: есть программа, падающая на большом входе. Берём этот вход, тупо удаляем из него куски и проверяем, осталась ли ошибка. Если осталась — оставляем уменьшенную версию. Повторяем.

Автор пишет простейший reducer на Python. Он просто перебирает строки во входном файле, пробует удалить каждую и проверяет через интересность-тест, воспроизводится ли баг. Работает медленно, но работает. На словаре /usr/share/dict/words он свёл проблему к одному слову: antidisestablishmentarianism.

Главное: редуктор ничего не знает про программу, которую тестирует. Он просто дёргает интересность-тест, пока не упрётся. Это и есть сила подхода.

Потом автор берёт случайную C-программу от LLM, которая выдаёт разный вывод в двух конфигурациях (FAST=0 и FAST=1). Его простой редуктор за 10 секунд урезал 78 строк кода до 54, сохранив разницу в поведении. С крошечной доработкой (сброс i=0 при успехе) результат стал ещё лучше.

Дальше — Shrink Ray. Готовый инструмент, который умеет в параллельную редукцию и кучу трюков: выкидывает комментарии, уменьшает целые числа, ищет «магический ключ», после которого вход вдруг сжимается на 90–99%.

Но есть нюансы. Интересность-тест легко написать криво. Например, проверять только разный вывод — проскочит ложные срабатывания. Лучше явно зафиксировать ожидаемое значение. Ещё важна скорость — редуктор может гонять тест сотни раз в секунду. И таймауты: если программа работала 0.1с, ставить 60с — убивать производительность.

Особая боль — недетерминированные баги. Допустим, ошибка вылезает только в каждом третьем запуске из-за random.random(). Можно написать тест, который запускает вход три раза и принимает, если баг случился хотя бы раз. Но это не гарантирует детерминизм — редукция может даже ухудшить ситуацию. Более строгий тест — «баг должен сработать во всех n запусках» — почти невозможно натравить на исходный вход с вероятностью 3.6%.

Автор выкручивается: сначала гоняет с мягким тестом, периодически проверяет, не стал ли баг стабильнее, и переключается на жёсткий. Костыль, но рабочий.

Ещё один костыль — «сравнение с глобальным счётчиком». При отладке yk (JIT-компилятора) автора интересует не столько длина входа, сколько число инструкций в трассе. Он пишет интересность-тест, который пишет в /tmp/global_best минимальное количество строк лога jit-asm. Если новый вход даёт больше строк — он бракуется, даже если короче. Это позволяет редуктору концентрироваться на полезном свойстве, а не на длине текста. Код ужасный, не потокобезопасный, но когда нужно быстро найти баг — сойдёт.

Итог: test-case reducers — не магия и не только для компиляторщиков. Они реально экономят время, если уметь крутить интересность-тесты.

Читать оригинал →