- доменные типы internal/catalog лишились json-тегов и MarshalJSON; типы metricWire/layerWire/aggregationWire и перевод catalogWire живут в транспорте, тело отказа тоже получило объявленный тип — байты ответа не изменились - заведён сторож: обход графа типов ответа утверждает, что домен не доезжает до сериализации, плюс требование json-тега на полях транспортных структур и заведомо красные случаи к обоим правилам - решение с ценой обеих сторон записано в architecture.md и ADR; шаг lint в гейте получил свой кеш — общий на машину красил прогон находками из чужого worktree
11 KiB
Тесты
-
Тесты на разбор формата HAE держим на реальных пакетах, сложенных в
testdata(с вычищенными токенами). Документация формата ненадёжна — источником истины служат живые данные. -
Проверяем идемпотентность: повторный разбор того же пакета не меняет витрину.
-
Где код выбирает между двумя версиями одних данных, тест обязан прогнать обе стороны и хотя бы одну перестановку трёх. Пример на паре доказывает коммутативность и молчит про ассоциативность, а сломаться правило может именно на ней: полнота — частичный порядок, тай-брейк — тотальный, и их попарная свёртка дала нетранзитивное отношение победы, из-за которого одна и та же доставка меняла витрину при каждой пересборке. Ревью дизайна этого не увидело, ревью кода увидело только перебором троек. Правилом линтера не выражается — отсюда проза.
-
В тот же перебор обязана входить версия с содержимым, равным одной из уже присланных, и пара «равная каноническая форма, разные байты». Три версии с разными хешами ветку «содержание равно» не посещают ни разу — а именно на ней устаревал провенанс, и живая витрина расходилась с пересборкой молча. Пара с равной формой ловит другое: неединственный минимум, при котором победителем оказывается просто первый в срезе, то есть порядок элементов на проводе.
-
Изменение правила разбора или слияния сопровождается замером на живом архиве, и ответ «пересворачивать нечего» произносится с числом. Утверждение без числа не отличается от предположения, а цена ошибки здесь — необратимое решение о судьбе тел.
-
В проверке на живом корпусе утверждается инвариант, а число печатается. Корпус растёт с каждой доставкой, а прогон живого архива в гейт не входит — значит константа, производная от его размера, протухает по расписанию телефона и краснеет у того, кто мимо проходил. Правило шире, чем «не сравнивай с числом»: протухает и оценка области действия, снятая на прежнем корпусе. «Тай-брейк — крайний разряд после полноты» было верно на 2 897 столкновениях и неверно на 80 129, где полнота решает 1,2%; на этой оценке стоял нормативный текст спеки. Число, попавшее в спеку или в довод решения, обязано нести рядом метод замера — иначе следующий замер посчитает другое и разойдётся молча (так и вышло: ключ без слоя дал 29-кратное расхождение). Три случая одного класса за три дня: записи 2026-08-02, 2026-08-03 и 2026-08-04 в review.md.
Метода мало — синтетический корпус обязан содержать измеряемый случай в той форме, в какой он бывает в жизни. Сверка новизны секции мерялась на журнале, где новое имя стояло во всех доставках, то есть его первая встреча лежала в начале — ранний выход давал 31 мкс. В жизни секцию включают сегодня, первая встреча оказывается в хвосте, и та же операция стоит 52 мс: три порядка разницы, а на числе стояло решение «индекс не нужен» (запись 2026-08-04).
И число живёт в одном месте. Один и тот же замер, записанный в комментарий кода и в
architecture.md, разошёлся внутри одного изменения. Дом числа —design.mdизменения; остальные формулируют правило и ссылаются. -
Оракул сходимости называет свою посылку рядом с собой, и прогон её печатает. «Пересборка = приём» — не тождество, а утверждение с условиями: живая свёртка шла в порядке журнала, в журнале нет доставок, чью свёртку живой путь провалил, а пересборка проведёт, и за время прогона новых доставок не приезжало. Оракул, чья посылка не названа, краснеет по причине, к правилу отношения не имеющей, и краснота становится неотличимой от дефекта — то есть с ней начинают жить.
-
Проверка правила, зависящего от порядка, несёт отрицательный контроль. Тест «два пути дали один отпечаток» зеленеет и на правиле, которое к порядку безразлично, — то есть не проверяет ничего. Рядом обязан стоять прогон в заведомо другом порядке с утверждением, что отпечаток отличается.
-
Значение, попадающее в ключ витрины или в словарь, приёмочный тест берёт из
testdata, а не из литерала в тесте. Литерал, набранный руками, не воспроизводит невидимые символы источника — Apple шлёт неразрывные пробелы внутри своих строк (находка 24), — и совпадение теста с реализацией доказывает только согласие автора с самим собой. -
Утверждение о таблице-константе обходит саму таблицу, а не её видимые следствия. Проверка «таблица синонимов плоская», написанная через экспортированные функции, обходит лишь записи, достижимые из словаря: с неплоской таблицей она остаётся зелёной (воспроизведено). Такие утверждения живут во внутреннем тесте пакета и перебирают саму структуру.
-
Публичная форма ответа закрепляется байтами целого тела, и каждая различимая форма — своим литералом. Разбор проглатывает молча ровно то, что клиент видит первым:
nil-срез уезжает какnull, отсутствующий ключ неотличим от ключа с нулём, а разыменованный*time.Timeдаёт правдоподобную дату0001-01-01вместоnull. Тест, сличающий разобранные структуры или подстроки, зелен в каждом из этих случаев — проверка «в ответе есть"first_hour"» проходит и на нулевой дате. Различимых форм у ответа обычно больше одной (пустая коллекция, измеренное значение, неизмеренное), и литерал нужен каждой: одна закреплённая форма оставляет остальные без сторожа именно там, где ручной перевод и ошибается. Литерал при этом детектор изменения, а не источник истины контракта — правишь литерал, значит правишь контракт, и рядом обязана лежать правка спеки. -
Проверка, доказывающая ОТСУТСТВИЕ, несёт рядом заведомо красный случай. «Доменного типа в графе ответа нет», «значения точки в логе нет», «записи в таблице нет» — все они зелены и будучи сломанными: протухшая константа, пропущенная позиция обхода, перепутанное сравнение выглядят снаружи как «искомого нет». Это обобщение двух правил ниже (отрицательный контроль для правил порядка; утверждение о таблице-константе обходит саму таблицу): у проверки на отсутствие обязан быть предъявленный вход, на котором она краснеет.
-
Тест «в логе нет значения» проверяет запись без служебных полей, а не сырой буфер. Метка времени содержит доли секунды, поэтому искомая подстрока находится в ней сама: проверка на «5.1» краснела примерно раз на сотню прогонов от хода часов, а не от утечки. Разбираем запись, выбрасываем
timeи ищем в остатке. Правило общее — таких тестов будет больше (токены, тела запросов, координаты объектов).