Загрузка данных
Поток setup (пошагово)
Ниже фактическая последовательность действий:
1) Загрузка данных с TestOps на стенд
- ImitatorDataUploader.upload_with_confirm():
- HttpClient.get_attachments_list_by_test_case_id(test_data_id)
- выбрать attachment по original_filename == archive_name
- скачать bytes через HttpClient.get_test_case_attachment_by_id(test_case_id, attachment_id)
- сохранить архив на runner (рабочая директория)
- проверить архив на runner: наличие rules.txt, tags.txt, директории data/
- создать временную директорию на сервере стенда (mkdir -p /data/imitator/autotest_data/<unique>/)
- скопировать архив на стенд
- проверить архив на стенде
- распаковать
- проверить структуру распаковки
2) Сброс окружения стенда
- DockerContainerManager.stop_all_lds_containers()
- RedisCleaner.delete_keys_with_check():
- удаляет ключи вида lds-layer-builder:<stand> и lds-core:<stand> через redis-cli внутри docker
- ClickHouseManager.copy_configuration_file_from_stand():
- копирует файл конфигурации со стенда
- ClickHouseManager.delete_clickhouse_keys_with_check():
- удаляет записи в таблицах lds.records и lds.records_lastvalue по парам objectId и parameterId из конфы через сlickhouse-client внутри docker
3) Поднятие сервисов (без core)
- старт: layer-builder → journals → web-app → api-gw → reports
- каждый шаг: docker start + проверка docker inspect на статус running
4) Проверка доступности OPC
- StandSetupManager.check_opc_server_status()
- важно: проверка выполняется с сервера стенда, команда через /dev/tcp/<host>/<port> (bash)
5) Запуск имитатора + core
- ImitatorCmdGenerator собирает команду dotnet ... Playground ...
ImitatorManager.run_imitator() запускает команду как долгий процесс (exec_popen)
ImitatorManager.log_imitator_stdout() пишет stdout имитатора в imitator.log (этот файл доступен как артефакт в pipeline)
DockerContainerManager.start_lds_core_containers() поднимает core
Поток teardown
Teardown делится на два уровня: "между suite" и "в конце сессии".
Между suite (в conftest.py):
- StandSetupManager.stop_imitator_wrapper():
- ждёт завершения процесса имитатора и затем принудительно останавливает, если нужно
- при необходимости делает pkill -f Playground
- удаляет директорию с данными прогона со стенда
В конце сессии (в pytest_sessionfinish):
- выгрузка allure-results в TestOps
- чистка allure-results и временных .tar.gz на runner
Переменные окружения (инфраструктурный минимум)
- STAND_NAME: выбор стенда/адресов (через HOST_MAP).
- SSH_USER_DEV, SSH_KEY_NAME: выполнение команд ssh/scp.
- TESTOPS_BASE_URL: скачивание вложений (datasets) и выгрузка allure-results.
- OPC_URL: проверка доступности OPC на стенде и необходимый флаг для запуска имитатора
- RUN_WITHOUT_TESTOPS=true: режим без скачивания/удаления данных через TestOps (используется уже подготовленная директория/архив).
Типовые точки отказа инфраструктуры
- TestOps attachments нет вложения с именем archive_name или suite_data_id неверный → uploader не найдёт файл.
- docker: контейнеры не переходят в running/exited → DockerContainerManager поднимет RuntimeError.
- OPC недоступен: OPC_URL некорректный или стенд не видит хост/порт.
- имитатор: процесс не стартует или не пишет в stdout → imitator.log и команды pgrep/pkill.
Как устроены WS контракты (sync и async=subscribe)
WebSocketClient: SignalR invocation
clients/websocket_client.py реализует:
- invoke(target, args):
- инкрементирует invocation_id
- отправляет messagepack по протоколу SignalR
receive_by_invocation_id(id):
- используется для синхронных запросов (как REST)
receive_by_type(message_type):
- используется для подписок (асинхронный поток событий)
Утилиты для сценариев
utils/helpers/ws_test_utils.py:
- connect_and_get_msg() - invoke + ждать ответ по invocation_id
- connect_and_subscribe_msg() - invoke + ждать первое сообщение нужного типа
- connect_and_get_parsed_msg_by_tu_id() - подписка с фильтрацией по tuId
Парсинг сообщений
utils/helpers/ws_message_parser.py:
- ищет payload с replyStatus
- парсит reply в типизированные структуры
- содержит хуки конвертации времени и UUID
Где "лежит покрытие": datasets и scenarios
Покрытие "в ширину" = datasets
Добавили новый набор данных → получили новый suite прогонов и повторили тот же набор проверок.
В datasets задаётся:
- набор метаданных (suite_name, suite_data_id, archive_name)
- список активных тестов (через CaseMarkers или None)
- параметры утечек, интервалы, ожидания и допустимые отклонения
Покрытие "в глубину" = scenarios
Добавили новую проверку в test_scenarios/scenarios.py → получили новый тип контракта/полей.
Как добавить новый dataset (suite)
1) Создать test_config/datasets/select_XX.py по образцу.
Тут есть важный момент - называть свой датасет надо либо частью suitename, либо также как suitename.
При запуске по -k select_4 мы ищем select_4.py, а в нем подготовлен suitename, далее идет фильтрация запускаемых сьютов, чтобы понять какой набор стартовать, и наша инфра тестов смотрит на введенную строку и по -k, то есть в данном случае ищет select_4 по подстроке среди всех suitename:
Select_4_tn3_215km_113
Select_6_tn3_56km_113
Select_19_20_tn3_75_181km_649
То есть есть вы введете select4, то инфра тестов не найдет нужный сьют для запуска и скипнет весь запуск
2) Экспортировать переменную *_CONFIG типа SuiteConfig.
3) Для single‑leak:
- заполнить leak=LeakTestConfig(...)
4) Для multi‑leak:
- заполнить leaks=[LeakTestConfig(...), ...]
5) В CaseMarkers обязательно:
- test_case_id (TMS id)
- offset (минуты)
Проверка:
- pytest tests/test_smoke.py --suites=select_xx_...
- убедиться, что тесты сгруппированы по suite и имитатор не падает по длительности.
Как добавить новую проверку (новый тест/сценарий)
Добавить сценарий
- В test_scenarios/scenarios.py добавить новую async def ...
- Внутри использовать ws_test_utils для invoke/subscribe
- Ассерты делать через StepCheck/SoftAssertions
Добавить pytest‑тест
- В tests/test_smoke.py добавить метод в TestSuiteScenarios или TestLeakScenarios.
- Привязать к конфигу:
- suite-level → поле в SuiteConfig
- leak-level → поле в LeakTestConfig
Подключить маркеры
В conftest.py обновить маппинги:
- SUITE_LEVEL_TEST_MAPPING или LEAK_LEVEL_TEST_MAPPING
Зачем: чтобы автоматически навешивались offset и test_case_id из datasets.
Teardown и выгрузка отчёта
В conftest.py в pytest_sessionfinish:
- выгружаются allure-results в TestOps
- затем локальные allure-results удаляются