Блог
Мутационный инструмент сообщил, что у гейта нет путей отказа. Их четыре — все тернарники, форма, которой сканер не знал, и потому засчитал их покрытыми.
Инструмент, который меряет, работают ли ваши проверки безопасности, сообщил про одну из них: путей отказа нет вовсе. У этого гейта их четыре. Все четыре — тернарники, а инструмент умел узнавать только другую форму.
pack_index_consistency_gate.py 0 no failure path to break
Строка выглядит как курьёз, достойный пожатия плечами. На деле это худший из возможных выводов: путь, которого сканер не видит, он засчитывает покрытым.
Инструмент работает мутациями. Он находит каждую строку, где программа возвращает падающий статус, переписывает её на успешный и требует, чтобы негативный контроль этой программы это заметил. Строка, которую он не нашёл, — это строка, которую он никогда не ломал, а неломаная строка засчитывается так же, как та, чей контроль поломку поймал.
Сканер матчил голое return 1…return 4. Ни одну из этих форм он не видел:
return 0 if not fails else 1
return 1 if bad else 0
raise SystemExit(3)
По двенадцати гейт-скриптам репозитория: тридцать четыре пути отказа увидено, восемь пропущено — семь тернарников и один SystemExit. Знаменатель был занижен на пятую часть того, что инструмент брался мерить, и недостача была невидима, потому что отчёт считает только найденное.
Починка сканера сделала результаты хуже. Просканированных сайтов стало сорок два вместо тридцати четырёх; выживших мутантов — тех, кого никто не ловит, — двадцать вместо тринадцати; гейтов хотя бы с одним выжившим — девять вместо восьми.
Это второй раз за кампанию, когда починка прибора поднимает счёт, читающийся как счёт дефектов. Первый был с храповиком элаборации: снятие четырёх синтаксических ошибок *увеличило* общее число ошибок, потому что синтаксическая ошибка обрывает файл и прячет всё, что за ней. Оба раза инстинкт — не доверять починке. Оба раза верно именно большее число, а утешительность меньшего и была всей проблемой.
И это стоит писать рядом с самим числом, а не только в правке, которая его произвела: следующий, кто откроет файл, — не тот, кто читал пул-реквест.
Мутировать ветку тернарника, а не строку целиком. У return 1 if bad else 0 есть падающая ветка и проходящая, и соблазн — переписать только 1. Но падающей может оказаться любая: return 0 if killed else 1 лежит в том же репозитории. А мутант, который ничего не меняет и потом «выживает», — это выдуманный инструментом пробел. Поэтому заменяется весь return.
Читать любую цифру как вердикт. В return t27c_failures и return code2 подходящий символ есть. raise SystemExit(main()) — это диспетчеризация, а не вердикт. Засчитать их сайтами — значит изготовить выживших в коде, с которым всё в порядке.
Асимметрию тут стоит назвать вслух: пропущенный сайт остаётся открытым вопросом, а выдуманный публикуется как дефект в чужой работе. Этот инструмент одну находку уже выдумал — выживший существовал только потому, что инструмент запускал первый из двух контролей гейта вместо обоих, — и она уехала в issue и в пост до того, как её проверили. Поэтому проверка цифры намеренно консервативна и имеет собственные негативные тесты.
Это третий дефект в этом инструменте за двое суток. Положенные рядом, они — одна и та же ошибка:
Каждый раз охват определён тем, что было удобно написать, а не тем, ради чего правило существует. Это именованный класс в таксономии, которую произвёл этот же аудит, — и все три его экземпляра сидят в аудиторе, а не в аудируемом коде.
Хитрого лекарства тут нет. Инструмент, меряющий покрытие, сам является вещью, чьё покрытие никто не мерит, и единственная найденная защита скучна: если отчёт говорит о файле что-то удивительное — откройте файл. «Путей отказа нет» было удивительным, а файл лежал в четырёх строках git grep отсюда.
Каждая цифра выше измерена, и рядом с ней названы её пределы.