hermes-android/agy-work/TASK-2026-08-24-04-ci-and-test-harness.md

12 KiB
Raw Blame History

Task 04: CI и проверяемость тестов (hermes-android)

Repo: ochenstarik-ui/hermes-android Assigned to: Antigravity (режим оркестратора, два кодера) Priority: HIGH (без этого ни одно предыдущее задание не защищено от регресса) Date: 2026-08-24 Base SHA: результат задания 03 — указать фактический SHA при выдаче Зависимость: задания 0103 приняты. Их тесты — материал, на котором проверяется работоспособность CI.

Роли и протокол

Роль Модель Что делает
Оркестратор Antigravity Разбивает работу, маршрутизирует, принимает результат
Кодер 1 Gemini Flash 3.7 high Реализация §Scope целиком
Кодер 2 Gemini Pro high Независимая проверка работы кодера 1 и доработка

Раунд 1 (кодер 1). Пункты §Scope по порядку. Для этого задания тесты-первыми неприменимы к пунктам 12 (инфраструктура) — вместо этого требуется демонстрация: намеренно сломанный коммит в черновой ветке валит пайплайн, вывод приложен к отчёту. Нерешённое — в OPEN QUESTIONS. Раунд 2 (кодер 2). Независимо запускает пайплайн своим прогоном, проходит §Anti-checklist с явной отметкой каждого пункта, доводит минимальным дифом, даёт findings по шкале. Раунд 3 (оркестратор). Приёмка только при ссылке на фактический прогон CI (job URL или лог), а не на конфиг.

Проблема

1. CI отсутствует полностью (BUILD-03). Каталога .github в репозитории нет, хотя README документирует gradlew test и gradlew lint. 75 юнит-тестов и тесты hermes-pair никем не запускаются при пуше — все коммиты с пометками «fix P0/P1» проверялись только локально и на слово.

2. Объявлен runner несуществующих тестов (BUILD-05). app/build.gradle.kts:19 задаёт testInstrumentationRunner, но исходного набора androidTest нет, а из зависимостей подключён только androidTestImplementation(composeBom) — без androidx.test.ext:junit, espresso, compose-ui-test-junit4. Инструментальные тесты, требуемые заданиями 02 и 03, запускать нечем.

3. Фейки скрывают дефекты (TEST-01). FakeDaos.kt:70-76 возвращает сообщения в порядке вставки — маскирует DATA-02. Внутри фейка обычные mutableMap без синхронизации, при этом часть тестов гоняется на runBlocking(Dispatchers.Default): сами тесты могут падать с ConcurrentModificationException.

4. Нет инструментальных и UI-тестов (TEST-02). 75 тестов — все JVM-only. Не покрыты миграции Room, реальное поведение EncryptedSharedPreferences, экран чата, сканер, навигация, поворот экрана — то есть ровно то, что чинили задания 02 и 03.

5. Ожидания через delay(50) (TEST-03). JsonRpcGatewayClientTest.kt:100, EndToEndContractScenarioTest.kt:173,183,199, ApprovalRoutingTest.kt:209 — пять мест синхронизируются реальными задержками. На CI под нагрузкой такие тесты мигают, и первым делом их начнут отключать.

6. Тест покрывает несуществующий код (TEST-04). EndToEndContractScenarioTest.kt:45 проверяет HermesGatewayRepository — класс прошлой архитектуры, недостижимый из MainActivity. Тест зелёный и ничего не гарантирует.

Scope

1. Пайплайн (BUILD-03). .github/workflows/ci.yml: на push и pull_request. Джоб android: JDK 17, Android SDK, кэш Gradle, шаги testDebugUnitTest, lint, assembleDebug. Отчёты тестов и lint — артефактами. Джоб rust: cargo test --all-targets и cargo clippy -- -D warnings в hermes-pair/, кэш cargo. Джоб instrumented: connectedDebugAndroidTest на эмуляторе (reactivecircus/android-emulator-runner или эквивалент). Если эмулятор на раннере недоступен — джоб объявляется обязательным, но помечается известным ограничением в отчёте; молча выключать его нельзя. Настроить branch protection на main: merge блокируется при падении обязательных джобов. Если прав на настройку у исполнителя нет — зафиксировать в отчёте как действие для владельца, а не пропустить.

2. Инфраструктура инструментальных тестов (BUILD-05). Добавить androidx.test.ext:junit, espresso-core, androidx.compose.ui:ui-test-junit4, androidx.room:room-testing. Создать app/src/androidTest/… и убедиться, что тесты заданий 02 и 03 действительно запускаются.

3. Ревизия фейков (TEST-01). FakeUnifiedSessionDao и FakeHostDao — на потокобезопасные структуры. Порядок возврата сообщений — перемешанный (см. задание 02 §Scope 2), чтобы фейк не был мягче реального Room. Пройти по всем существующим тестам и отметить те, что зелены только благодаря удобному фейку. Список — в отчёт, с пометкой, какие из них требуют переписывания (сами переписывания — по пункту 4, если укладываются, иначе отдельным заданием).

4. Детерминированные ожидания (TEST-03). Пять мест с delay(50) перевести на runTest с виртуальным временем и Turbine (зависимость app.cash.turbine:turbine:1.2.0 уже подключена). Семантику проверок не менять — только способ ожидания.

5. Тест на актуальный путь (TEST-04). EndToEndContractScenarioTest переписать на UnifiedSessionRepository, сохранив покрываемые сценарии (полный жизненный цикл контракта, обновление токена). Старую версию удалить только после того, как новая проходит и покрывает те же сценарии — соответствие перечислить построчно в отчёте. Сам класс HermesGatewayRepository не удалять — это задание 10.

Do not change

  • Продуктовый код заданий 0103: CI не повод «попутно поправить».
  • Версии AGP, Kotlin, Compose BOM — задание 05.
  • Логику существующих тестов: меняется способ ожидания, не ассерты.
  • hermes-pair/** исходники — здесь только запуск его тестов.

Anti-checklist

  1. Workflow добавлен, но ни разу не запускался — «должно работать». Требование: приложить ссылку на фактический прогон и на прогон с намеренно сломанным тестом.
  2. Джоб instrumented добавлен с continue-on-error: true — падение не блокирует merge, то есть джоба нет. Проверить.
  3. cargo clippy без -D warnings — предупреждения не валят сборку, смысл потерян.
  4. Кэш Gradle настроен так, что валидный прогон переиспользует результаты предыдущего и тесты фактически не выполняются. Проверить по времени и логу, что тесты реально шли.
  5. Фейки объявлены потокобезопасными заменой mutableMapOf на ConcurrentHashMap, но итерация по ним осталась небезопасной в другом методе. Проверить все методы фейка.
  6. Порядок в фейке перемешан, но тест задания 02 после этого стал падать — и его «поправили» вместо кода. Это ослабление, запрещено.
  7. delay(50) заменён на advanceUntilIdle(), но ассерт при этом ослаблен или перенесён. Сверить ассерты до и после построчно.
  8. Новый EndToEndContractScenarioTest покрывает меньше сценариев, чем старый, а старый уже удалён. Требование: удаление только после построчного соответствия.
  9. В отчёте green для незапущенной команды (AGENTS.md §3).

Definition of Done

  • Пуш с намеренно сломанным юнит-тестом валит пайплайн — приложен лог.
  • Пуш с намеренно сломанным cargo clippy валит пайплайн — приложен лог.
  • Инструментальные тесты заданий 02 и 03 запускаются в CI и проходят.
  • Ни один тест не запускается с delay-ожиданием; все пять мест переведены.
  • Ни один тест не удалён без построчно обоснованной замены.
  • Фейки потокобезопасны, порядок сообщений в них не мягче реального Room.
  • main защищён от merge при падении обязательных джобов, либо это зафиксировано как требующее действия владельца.

Required tests

Отдельных продуктовых тестов задание не добавляет. Доказательства — прогоны:

  1. прогон на текущем HEAD: всё зелёное;
  2. прогон с намеренно сломанным юнит-тестом: красный, указано какой шаг;
  3. прогон с намеренно сломанным clippy-предупреждением: красный;
  4. прогон инструментальных тестов: список выполненных, не «0 tests».

Все четыре — с ссылками или полными логами, обоими кодерами независимо.

Required verification

./gradlew --no-daemon testDebugUnitTest
./gradlew --no-daemon connectedDebugAndroidTest
./gradlew --no-daemon lint
./gradlew --no-daemon assembleDebug
cd hermes-pair && cargo test --all-targets && cargo clippy -- -D warnings

Локально и в CI. Расхождение локального и CI-результата — само по себе находка, фиксируется в отчёте.

Result

agents/antigravity/done/TASK-2026-08-24-04-ci-and-test-harness.md — разделы ## Кодер 1, ## Кодер 2 (review + доработка), ## Вердикт оркестратора. Содержимое по AGENTS.md §4.