generated from mononoki/pyproj
[+!] all but small
some small modifications, tests added
This commit is contained in:
176
tests/README.md
176
tests/README.md
@@ -1,70 +1,150 @@
|
||||
# Клиент и тесты сервера
|
||||
# Клиент и тесты SIRMENT/core
|
||||
|
||||
`src/.test/client_test.py` используется и для ручного запуска, и в интеграционных
|
||||
тестах. Он ожидает `token_ask`, отвечает `token_answer`, ожидает `handshake_ask`,
|
||||
отвечает `handshake_answer` и подтверждает состояние `air` ответом на heartbeat.
|
||||
Затем ручной запуск отправляет десять пакетов телеметрии с heartbeat между ними.
|
||||
Каждое ожидание ответа имеет таймаут; импорт файла не запускает клиент.
|
||||
Общий клиент `examples/client.py` используется для ручного запуска и настоящих
|
||||
TCP-интеграционных тестов. Fixture загружает его по пути относительно
|
||||
репозитория, без запуска CLI при импорте. Папка `src/.test` игнорируется Git и
|
||||
не нужна для выполнения тестов из чистого клона.
|
||||
|
||||
После запуска сервера:
|
||||
## Установка и запуск
|
||||
|
||||
```bat
|
||||
src\.test\start_client.bat rover --count 10 --interval 1
|
||||
Нужен Python 3.11 и зависимости тестов:
|
||||
|
||||
```sh
|
||||
python -m pip install -r req.txt
|
||||
python -m pip install -r req-dev.txt
|
||||
python -m pytest -q --cov=src --cov-branch --cov-report=term-missing --cov-report=xml
|
||||
```
|
||||
|
||||
Для запроса восстановления укажите старые значения из вывода клиента:
|
||||
Для окружения sites на Windows, из корня репозитория:
|
||||
|
||||
```bat
|
||||
src\.test\start_client.bat rover --old-token 123 --old-upf 456
|
||||
D:\_Programs\anaconda\envs\sites\python.exe -m pip install -r req-dev.txt
|
||||
D:\_Programs\anaconda\envs\sites\python.exe -m pytest -q --cov=src --cov-branch --cov-report=term-missing --cov-report=xml
|
||||
tests\start_tests.bat
|
||||
```
|
||||
|
||||
Сейчас сервер не сообщает результат восстановления отдельным ответом.
|
||||
Клиент сообщает только факт отправки запроса; heartbeat подтверждает новый
|
||||
handshake, но сам по себе не доказывает успешное восстановление.
|
||||
|
||||
Запуск всех тестов из корня `server`:
|
||||
Батник переходит в корень репозитория независимо от текущей папки. Без аргументов
|
||||
он запускает весь набор с покрытием; с аргументами передаёт их pytest:
|
||||
|
||||
```bat
|
||||
D:\_Programs\anaconda\envs\sites\python.exe -m pytest -q
|
||||
tests\start_tests.bat tests/test_router.py -q
|
||||
```
|
||||
|
||||
Также можно запустить `tests/start_tests.bat`. Только проверки самого клиента:
|
||||
Отдельные группы:
|
||||
|
||||
```bat
|
||||
tests\start_tests.bat tests/test_client_protocol.py -q
|
||||
```sh
|
||||
python -m pytest tests/test_client_protocol.py -q
|
||||
python -m pytest tests/test_session.py tests/test_session_manager.py tests/test_router.py -q
|
||||
python -m pytest tests/test_transport_boundary.py -q
|
||||
python -m pytest tests/test_entrypoint.py -q --cov=src --cov-branch
|
||||
```
|
||||
|
||||
Интеграционные тесты проверяют подключение, метаданные портов, телеметрию,
|
||||
heartbeat, немедленное закрытие транспорта и сохранение отключённой сессии,
|
||||
отключение после пяти команд в неправильном состоянии протокола,
|
||||
перенос данных и маршрутов в обе стороны, отказ для активной/отсутствующей
|
||||
сессии и неверного `upf`, а также однократность восстановления.
|
||||
Новые регрессионные проверки намеренно не помечены `xfail` и не отключены.
|
||||
С текущими ошибками реализации весь набор возвращает ненулевой код. После
|
||||
исправления соответствующей ошибки тот же тест должен пройти без изменения
|
||||
ожидаемого поведения. Результаты и пробелы проверки — в [REPORT.md](REPORT.md).
|
||||
|
||||
Дополнительные проверки:
|
||||
## Ручной клиент
|
||||
|
||||
- `test_session.py`: все этапы подключения, истечение и продление дедлайна,
|
||||
остановка воркера, ошибки последовательности и повторный handshake.
|
||||
- `test_session_manager.py`: обработка событий, истечение окна восстановления,
|
||||
однократность переноса и безопасность старой отложенной очистки.
|
||||
- `test_router.py`: перенос ссылок в обе стороны, несвязанные маршруты,
|
||||
окончательное удаление связей и данные отправителя при пересылке.
|
||||
- `test_source_validation.py`: синтаксис исходников и docstring каждого теста.
|
||||
После запуска сервера через `python src/main.py`:
|
||||
|
||||
В `test_client_run.py` также проверяются настоящие TCP-таймауты в состояниях
|
||||
`new/token/ready/air` и сохранение соединения после продления heartbeat.
|
||||
Краткое описание каждой проверки находится в docstring её функции.
|
||||
```sh
|
||||
python examples/client.py rover --host 127.0.0.1 --port 9000 --count 10 --interval 1
|
||||
python examples/client.py rover --old-token 123 --old-upf 456
|
||||
```
|
||||
|
||||
Интеграционные тесты запускают настоящий TCP-обработчик, сессии и роутер на
|
||||
свободном локальном порту. Маршруты задаются явно внутри теста: правила API/GUI
|
||||
не предполагаются. Изолированные проверки менеджера используют наблюдатель
|
||||
вызовов роутера; настоящий роутер дополнительно проверяется отдельно.
|
||||
На Windows: `examples\start_client.bat rover --count 10 --interval 1`.
|
||||
Клиент ждёт `token_ask`, отвечает `token_answer`, ждёт `handshake_ask`, отвечает
|
||||
`handshake_answer` и подтверждает состояние `air` ответом на heartbeat.
|
||||
Ручной запуск отправляет телеметрию с heartbeat между пакетами. Ожидания ответов
|
||||
ограничены таймаутом. Сервер пока не сообщает результат восстановления отдельным
|
||||
пакетом; клиент сообщает только факт отправки запроса.
|
||||
|
||||
В сценариях с таймерами подменяются только часы и ожидания времени в памяти.
|
||||
Тест явно продвигает время и освобождает нужное ожидание, поэтому реальные
|
||||
паузы в 5/60 секунд не нужны. Методы сессии, её воркер, обработчики менеджера
|
||||
и маршрутизация не подменяются. Неопределённые имена часов не добавляются
|
||||
тестами: такие ошибки остаются видимыми. Фоновые задачи завершаются или
|
||||
отменяются при выходе из каждого сценария; ошибки воркеров не скрываются.
|
||||
## Что проверяется
|
||||
|
||||
`fixtures/client_run_expected.json` оставлен как исторический эталон старого
|
||||
сценария. Новые тесты проверяют ответы протокола и состояние сервера, а не логи.
|
||||
| Файл | Сценарии |
|
||||
| --- | --- |
|
||||
| `test_client_protocol.py` | Порядок handshake клиента, буферизация данных до heartbeat, таймауты и ошибочные ответы |
|
||||
| `test_client_run.py` | Настоящий TCP, метаданные портов, телеметрия, heartbeat, EOF, таймауты всех этапов, восстановление в обе стороны, отказ переноса и однократность |
|
||||
| `test_session.py` | Дедлайны 60/5 секунд, собственный watcher, переходы протокола, retry handshake, последние данные, запрет новых отправок в stop/migrated, обработка данных во время фоновой отправки |
|
||||
| `test_session_manager.py` | События, срок восстановления 60 секунд, истечение окна, повторный close после удаления, ошибка writer.wait_closed через настоящий Connection.close, безопасность старой очистки после реконекта |
|
||||
| `test_router.py` | Исходящие/входящие связи, самоссылки, несвязанные узлы, удаление ссылок и данные отправителя |
|
||||
| `test_transport_boundary.py` | Отключение/очистка плохих пакетов, лимит неверных команд, status True/False/Other, auth робота, произвольные JSON-данные без автокаста, heartbeat, TCP-фрейминг, watcher/EOF, фоновые сетевые ошибки |
|
||||
| `test_entrypoint.py` | Настоящие main.py/TCP.run в отдельном процессе, handshake, heartbeat и завершение процесса |
|
||||
| `test_source_validation.py` | Синтаксис src/examples и краткие docstring каждого теста |
|
||||
|
||||
Некорректные клиенты проверяются одновременно с исправным соединением:
|
||||
закрытие одного транспорта не должно мешать heartbeat и телеметрии другого.
|
||||
Локальная ошибка разбора пакета допускается, если транспорт закрывается,
|
||||
запись и маршруты удаляются после окна восстановления, а другой клиент работает.
|
||||
Ожидаемое исключение разрешается только для проверенного обработчика плохого
|
||||
клиента; ошибки остальных задач продолжают проваливать тест.
|
||||
|
||||
`connection.payload.token` хранится в метаданных `auth` как токен робота.
|
||||
Отсутствующий/null токен не мешает подключению; whitelist роутера пока не
|
||||
реализован. Это поле отдельно от UID/upf восстановления в `token_answer`.
|
||||
По согласованному протоколу отказ handshake означает только JSON `false`:
|
||||
`0`, `null`, пустые строка/массив/объект означают успех. Тесты не требуют boolean.
|
||||
Для status оставлены три представителя: True, False и Other = 0. Значение 0
|
||||
отличает буквальный false от ложного значения Python без перебора всех типов.
|
||||
|
||||
Payload команд, читающих поля объекта, проверяется на безопасное отклонение,
|
||||
если его нельзя разобрать. Содержимое heartbeat не используется, поэтому
|
||||
необъектное значение разрешено. Для heartbeat без поля payload допустимы
|
||||
обработка или локальное отключение с очисткой. Полезная нагрузка данных
|
||||
сохраняется и маршрутизируется без преобразования JSON-значения в словарь.
|
||||
|
||||
## Метод проверки и завершение задач
|
||||
|
||||
Интеграционные тесты запускают настоящие TCP-обработчик, Session, SessionManager
|
||||
и Router на локальном порту, выбранном ОС. Моки сервера не используются.
|
||||
Маршруты задаются явно в тестах: правила будущих API/GUI не предполагаются.
|
||||
Изолированные тесты менеджера используют наблюдатель вызовов роутера, а сам
|
||||
Router проверяется отдельно.
|
||||
|
||||
В проверках таймаутов подменяются только монотонные часы и ожидания времени в
|
||||
памяти. Тест освобождает выбранный `sleep` явно; реальные паузы в 5/60 секунд
|
||||
не нужны. Основные методы сервера не подменяются. Ошибки транспорта вводятся
|
||||
локально на одном реальном connection. Искусственная передача Python set в
|
||||
сериализацию исключена: она не воспроизводит текущий путь данных из JSON.
|
||||
|
||||
Гонка watcher/TCP-finally проверяет одно закрытие и одну очистку и допускает
|
||||
как ожидание очистки вызывающей корутиной, так и отдельную задачу очистки.
|
||||
Ошибка закрытия воспроизводится на `writer.wait_closed` и проходит через
|
||||
настоящий `Connection.close`; удаление записи и маршрутов должно состояться.
|
||||
Уже существующий тест старой очистки после реконекта сохранён.
|
||||
|
||||
Фоновые сетевые исключения должны быть видны с исходным исключением; стандартное
|
||||
логирование asyncio, включая `Task exception was never retrieved`, допускается.
|
||||
Тест не требует немедленного disconnect от одной ошибки отдельной задачи:
|
||||
закрытие проверяется через настоящий EOF. При разрыве во время отправки
|
||||
допускаются завершение с сетевой ошибкой или отмена задачи; транспорт закрывается
|
||||
один раз, другое соединение работает. Отмена уже запланированных отправок при
|
||||
смене состояния не вводится как отдельное обязательное правило.
|
||||
|
||||
Фикстуры и контекст сервера завершают или отменяют созданные задачи, закрывают
|
||||
клиентов и listener и проверяют исключения задач. Ожидания ограничены временем;
|
||||
ошибка не превращается в зависший тест.
|
||||
|
||||
## Точка входа и покрытие дочернего процесса
|
||||
|
||||
В текущем `main.py` адрес/порт заданы напрямую, поэтому процессный тест запускает
|
||||
`tests/entrypoint_runner.py`, который выполняет настоящий файл через `runpy`.
|
||||
Обёртка перенаправляет только адрес и порт вызова `asyncio.start_server` на
|
||||
`127.0.0.1:0` и вызывает оригинальную функцию. Код main.py, TCP.run, handshake,
|
||||
сессий, маршрутизации и фоновых отправок не заменяется.
|
||||
|
||||
Процесс запускается из другой текущей папки. После проверки клиент закрывается,
|
||||
а команда по stdin отменяет главную корутину. Это позволяет завершить процесс
|
||||
штатно и сохранить покрытие, включая Windows. При зависании есть принудительное
|
||||
завершение. Тест не доказывает настраиваемость порта самим production CLI:
|
||||
такого интерфейса пока нет.
|
||||
|
||||
`pytest-cov` и `coverage` находятся в `req-dev.txt`. `.coveragerc` включает
|
||||
покрытие ветвей и `patch = subprocess`, чтобы учитывать main.py/TCP.run из
|
||||
дочернего процесса. Это [рекомендованный механизм pytest-cov 7](https://pytest-cov.readthedocs.io/en/latest/subprocess-support.html).
|
||||
Порог 100% не установлен; ошибки поведения не скрываются высоким процентом.
|
||||
`coverage.xml` и `.coverage*` — генерируемые, игнорируемые Git результаты.
|
||||
|
||||
`fixtures/client_run_expected.json` сохранён как исторический эталон старого
|
||||
сценария. Нынешние тесты проверяют протокол и состояние сервера, а не этот лог.
|
||||
|
||||
Reference in New Issue
Block a user