# Тесты

- Тесты на разбор формата HAE держим на **реальных пакетах**, сложенных в
  `testdata` (с вычищенными токенами). Документация формата ненадёжна —
  источником истины служат живые данные.
- Проверяем идемпотентность: повторный разбор того же пакета не меняет
  витрину.
- **Где код выбирает между двумя версиями одних данных, тест обязан прогнать
  обе стороны и хотя бы одну перестановку трёх.** Пример на паре доказывает
  коммутативность и молчит про ассоциативность, а сломаться правило может
  именно на ней: полнота — частичный порядок, тай-брейк — тотальный, и их
  попарная свёртка дала нетранзитивное отношение победы, из-за которого одна
  и та же доставка меняла витрину при каждой пересборке. Ревью дизайна этого
  не увидело, ревью кода увидело только перебором троек. Правилом линтера не
  выражается — отсюда проза.
- **В тот же перебор обязана входить версия с содержимым, равным одной из уже
  присланных, и пара «равная каноническая форма, разные байты».** Три версии с
  разными хешами ветку «содержание равно» не посещают ни разу — а именно на ней
  устаревал провенанс, и живая витрина расходилась с пересборкой молча. Пара с
  равной формой ловит другое: неединственный минимум, при котором победителем
  оказывается просто первый в срезе, то есть порядок элементов на проводе.
- **Изменение правила разбора или слияния сопровождается замером на живом
  архиве, и ответ «пересворачивать нечего» произносится с числом.** Утверждение
  без числа не отличается от предположения, а цена ошибки здесь — необратимое
  решение о судьбе тел.
- **В проверке на живом корпусе утверждается инвариант, а число печатается.**
  Корпус растёт с каждой доставкой, а прогон живого архива в гейт не входит —
  значит константа, производная от его размера, протухает по расписанию
  телефона и краснеет у того, кто мимо проходил. Правило шире, чем «не
  сравнивай с числом»: протухает и **оценка области действия**, снятая на
  прежнем корпусе. «Тай-брейк — крайний разряд после полноты» было верно на
  2 897 столкновениях и неверно на 80 129, где полнота решает 1,2%; на этой
  оценке стоял нормативный текст спеки. Число, попавшее в спеку или в довод
  решения, обязано нести рядом **метод замера** — иначе следующий замер
  посчитает другое и разойдётся молча (так и вышло: ключ без слоя дал 29-кратное
  расхождение). Три случая одного класса за три дня: записи 2026-08-02,
  2026-08-03 и 2026-08-04 в [review.md](../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`
  и ищем в остатке. Правило общее — таких тестов будет больше (токены, тела
  запросов, координаты объектов).
