13 KiB
Клиент и тесты SIRMENT/core
Общий клиент examples/client.py используется для ручного запуска и настоящих
TCP-интеграционных тестов. Fixture загружает его по пути относительно
репозитория, без запуска CLI при импорте. Папка src/.test игнорируется Git и
не нужна для выполнения тестов из чистого клона.
Установка и запуск
Нужен Python 3.11 и зависимости тестов:
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, из корня репозитория:
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
Батник переходит в корень репозитория независимо от текущей папки. Без аргументов он запускает весь набор с покрытием; с аргументами передаёт их pytest:
tests\start_tests.bat tests/test_router.py -q
Отдельные группы:
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
Новые регрессионные проверки намеренно не помечены xfail и не отключены.
С текущими ошибками реализации весь набор возвращает ненулевой код. После
исправления соответствующей ошибки тот же тест должен пройти без изменения
ожидаемого поведения. Результаты и пробелы проверки — в REPORT.md.
Ручной клиент
После запуска сервера через python src/main.py:
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
На Windows: examples\start_client.bat rover --count 10 --interval 1.
Клиент ждёт token_ask, отвечает token_answer, ждёт handshake_ask, отвечает
handshake_answer и подтверждает состояние air ответом на heartbeat.
Ручной запуск отправляет телеметрию с heartbeat между пакетами. Ожидания ответов
ограничены таймаутом. Сервер пока не сообщает результат восстановления отдельным
пакетом; клиент сообщает только факт отправки запроса.
Что проверяется
| Файл | Сценарии |
|---|---|
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.
Порог 100% не установлен; ошибки поведения не скрываются высоким процентом.
coverage.xml и .coverage* — генерируемые, игнорируемые Git результаты.
fixtures/client_run_expected.json сохранён как исторический эталон старого
сценария. Нынешние тесты проверяют протокол и состояние сервера, а не этот лог.