12 KiB
Task 04: CI и проверяемость тестов (hermes-android)
Repo: ochenstarik-ui/hermes-android
Assigned to: Antigravity (режим оркестратора, два кодера)
Priority: HIGH (без этого ни одно предыдущее задание не защищено от регресса)
Date: 2026-08-24
Base SHA: результат задания 03 — указать фактический SHA при выдаче
Зависимость: задания 01–03 приняты. Их тесты — материал, на котором проверяется работоспособность CI.
Роли и протокол
| Роль | Модель | Что делает |
|---|---|---|
| Оркестратор | Antigravity | Разбивает работу, маршрутизирует, принимает результат |
| Кодер 1 | Gemini Flash 3.7 high | Реализация §Scope целиком |
| Кодер 2 | Gemini Pro high | Независимая проверка работы кодера 1 и доработка |
Раунд 1 (кодер 1). Пункты §Scope по порядку. Для этого задания тесты-первыми неприменимы к пунктам 1–2 (инфраструктура) — вместо этого требуется демонстрация: намеренно сломанный коммит в черновой ветке валит пайплайн, вывод приложен к отчёту. Нерешённое — в 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
- Продуктовый код заданий 01–03: CI не повод «попутно поправить».
- Версии AGP, Kotlin, Compose BOM — задание 05.
- Логику существующих тестов: меняется способ ожидания, не ассерты.
hermes-pair/**исходники — здесь только запуск его тестов.
Anti-checklist
- Workflow добавлен, но ни разу не запускался — «должно работать». Требование: приложить ссылку на фактический прогон и на прогон с намеренно сломанным тестом.
- Джоб
instrumentedдобавлен сcontinue-on-error: true— падение не блокирует merge, то есть джоба нет. Проверить. cargo clippyбез-D warnings— предупреждения не валят сборку, смысл потерян.- Кэш Gradle настроен так, что валидный прогон переиспользует результаты предыдущего и тесты фактически не выполняются. Проверить по времени и логу, что тесты реально шли.
- Фейки объявлены потокобезопасными заменой
mutableMapOfнаConcurrentHashMap, но итерация по ним осталась небезопасной в другом методе. Проверить все методы фейка. - Порядок в фейке перемешан, но тест задания 02 после этого стал падать — и его «поправили» вместо кода. Это ослабление, запрещено.
delay(50)заменён наadvanceUntilIdle(), но ассерт при этом ослаблен или перенесён. Сверить ассерты до и после построчно.- Новый
EndToEndContractScenarioTestпокрывает меньше сценариев, чем старый, а старый уже удалён. Требование: удаление только после построчного соответствия. - В отчёте
greenдля незапущенной команды (AGENTS.md §3).
Definition of Done
- Пуш с намеренно сломанным юнит-тестом валит пайплайн — приложен лог.
- Пуш с намеренно сломанным
cargo clippyвалит пайплайн — приложен лог. - Инструментальные тесты заданий 02 и 03 запускаются в CI и проходят.
- Ни один тест не запускается с
delay-ожиданием; все пять мест переведены. - Ни один тест не удалён без построчно обоснованной замены.
- Фейки потокобезопасны, порядок сообщений в них не мягче реального Room.
mainзащищён от merge при падении обязательных джобов, либо это зафиксировано как требующее действия владельца.
Required tests
Отдельных продуктовых тестов задание не добавляет. Доказательства — прогоны:
- прогон на текущем
HEAD: всё зелёное; - прогон с намеренно сломанным юнит-тестом: красный, указано какой шаг;
- прогон с намеренно сломанным clippy-предупреждением: красный;
- прогон инструментальных тестов: список выполненных, не «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.