Signal Lab в CI
Эксперименты, с помощью которых инсталляцию вводят в строй, заодно служат её регрессионными тестами. В конвейере signallab run запускает их без окна, печатает каждый шаг, пишет отчёт JUnit, который показывает любая CI-система, и завершается кодом, понятным заданию:
| Код завершения | Конвейеру следует |
|---|---|
0 | идти дальше: все эксперименты пройдены |
1 | завершиться неудачей: эксперимент запустился и не прошёл |
2 | завершиться неудачей: эксперимент или команда неверны (ошибка проверки, неизвестный параметр, отсутствующий секрет) |
3 | завершиться неудачей или повторить: ничего не удалось запустить (сервер недоступен или не принимает токен, не открывается порт) |
Начать можно четырьмя способами:
| Способ | Где работает |
|---|---|
| Действие GitHub | Раннер с Linux, из образа сервера. |
| Образ | Любая CI, которая запускает контейнеры: GitLab, Jenkins, оболочка с Docker. |
| Исполняемый файл | Любой раннер, включая Windows. |
| Лабораторный сервер | Запуски выполняются на сервере Signal Lab рядом с оборудованием; конвейер их только отправляет. |
GitHub Actions
Репозиторий — это ещё и действие GitHub (Action). Оно запускает signallab из образа ghcr.io/proanima/signallab, проваливает задание, если эксперимент не прошёл, и оставляет отчёт JUnit:
jobs:
signallab:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ProAnima/SignalLab@v1.0.0
with:
version: 1.0.0
experiments: tests/signallab/*.json
params: |
api=http://127.0.0.1:8080
env:
SIGNALLAB_SECRET_API_TOKEN: ${{ secrets.API_TOKEN }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: signallab-junit
path: signallab-junit.xmlЗапуск, который не прошёл, виден ещё и как аннотация-ошибка на странице запуска workflow, рядом с упавшим шагом, — с названием эксперимента и причиной неудачи.
Входные данные
| Вход | Что делает | По умолчанию |
|---|---|---|
experiments | Файлы экспериментов или имена встроенных шаблонов через пробел или с новой строки. Маски вроде tests/*.json раскрываются; путь с пробелами не поддерживается. | обязательно |
params | Значения параметров, NAME=VALUE, по одному в строке. | |
profile | Запускать с этим профилем экспериментов. | |
matrix | Запустить по разу для каждой комбинации: NAME=V1,V2, по одному имени в строке. | |
matrix-file | Комбинации из файла JSON (см. Матрица запусков). | |
fail-fast | "true": остановиться на первом запуске, который не прошёл. | "false" |
server | Запускать на этом сервере Signal Lab, а не в самом задании, например http://192.0.2.10:1430. | |
token | Токен доступа сервера. Передавайте секрет. | |
junit | Куда записать отчёт JUnit. | signallab-junit.xml |
timeout | Сколько секунд может длиться запуск, от 1 до 300. | 300 |
version | Тег образа. | latest |
image | Другой реестр или образ, собранный локально; version — его тег. | ghcr.io/proanima/signallab |
lang | Язык сообщений и отчёта: en, ru, es, fr, de, pt, zh, ja, ko, hi или ar. | en |
fail-on-error | "false": не проваливать шаг; вместо этого читайте exit-code. | "true" |
Пути (experiments, matrix-file, junit) отсчитываются от рабочей папки шага.
TIP
Зафиксируйте version на выпуске, с которым вы тестировали. latest переходит на каждый новый стабильный выпуск.
Выходные данные
| Выход | Что это |
|---|---|
junit | Путь к отчёту JUnit. |
exit-code | Код завершения signallab: 0, 1, 2 или 3. |
Продолжить после неудачи
Шаг, который завершился неудачей, не передаёт остальному заданию никаких выходных данных. Чтобы решать самим, задайте fail-on-error: "false" и ветвитесь по exit-code:
- id: lab
uses: ProAnima/SignalLab@v1.0.0
with:
experiments: tests/signallab/smoke.json
fail-on-error: "false"
- if: steps.lab.outputs.exit-code == '1'
run: echo "an experiment failed; the report is ${{ steps.lab.outputs.junit }}"
- if: steps.lab.outputs.exit-code != '0'
run: exit 1Секреты в действии
{{secret.NAME}} в эксперименте читает SIGNALLAB_SECRET_NAME. Задавайте эти переменные в шаге (env:) из секретов репозитория. Действие передаёт signallab по имени каждую переменную SIGNALLAB_SECRET_… своего шага; значения идут в окружении, никогда в командной строке, а в каждом отчёте и каждой строке журнала на их месте стоит ••••. Если нужный эксперименту секрет не задан, шаг падает с кодом завершения 2 — до того, как что-либо отправлено.
Вход token идёт тем же путём — как SIGNALLAB_TOKEN.
Как работает действие
- Нужен раннер с Linux и Docker (в
ubuntu-latestон есть). На раннере с Windows или macOS действие останавливается с кодом завершения2и аннотацией; там пользуйтесь исполняемым файлом. signallabработает в образе с сетью хоста: куда достаёт раннер, туда достаёт и он. Сервис, который задание запустило на раннере, — ваша тестируемая система или контейнер изservices:с опубликованным портом, — находится по адресу127.0.0.1.- Он работает от имени пользователя раннера, с рабочей папкой, смонтированной по тому же пути, поэтому отчёт принадлежит заданию.
- Он передаёт
runровно перечисленные выше входные данные и--junit. Для всего остального, что умеетsignallab, —--report,--seed,--json,emulate, — пользуйтесь образом напрямую.
Образ в любой CI
В образе сервера signallab лежит как /usr/local/bin/signallab. Чтобы им пользоваться, переопределите точку входа (entrypoint).
GitLab CI:
signallab:
image:
name: ghcr.io/proanima/signallab:1.0.0
entrypoint: [""]
script:
- signallab run tests/signallab/*.json --junit signallab-junit.xml
artifacts:
when: always
reports:
junit: signallab-junit.xmlСекреты — переменные CI/CD с именами SIGNALLAB_SECRET_<NAME> (пометьте их как masked); окружение задания передаёт их signallab как есть.
Docker, из оболочки или любого планировщика (cron, Jenkins, скрипт развёртывания):
docker run --rm --network host \
--user "$(id -u):$(id -g)" \
-v "$PWD:/work" -w /work \
-e SIGNALLAB_SECRET_API_TOKEN \
--entrypoint signallab \
ghcr.io/proanima/signallab:1.0.0 \
run tests/smoke.json --junit junit.xml
echo "signallab exited with $?"--network hostпозволяет запускам достать всё, что достаёт хост, включая широковещание и multicast, — на хосте с Linux. Без него контейнер достаёт другие хосты только по unicast.- Образ работает от имени непривилегированного пользователя (uid 10001).
--userзапускает его от вашего имени, чтобы он мог записать отчёт в вашу папку; без этого папка должна быть доступна для записи uid 10001. -e NAMEбез значения передаёт эту переменную из вашего окружения.
Исполняемый файл
В каждом выпуске есть и отдельный signallab: signallab-<version>-linux-x64.tar.gz и signallab-<version>-windows-x64.zip; они перечислены в SHA256SUMS.txt выпуска. Версия для Linux собрана в Ubuntu 22.04 и использует системный OpenSSL 3 (libssl3): она работает в этом выпуске и в более новых дистрибутивах.
- name: Signal Lab
run: |
curl -fsSL -o signallab.tar.gz https://github.com/ProAnima/SignalLab/releases/download/v1.0.0/signallab-1.0.0-linux-x64.tar.gz
tar -xzf signallab.tar.gz
./signallab run tests/signallab/smoke.json --junit signallab-junit.xmlНа раннере с Windows распакуйте zip и запускайте signallab.exe так же. На машине с установленным настольным приложением signallab уже есть в PATH.
Запуски на лабораторном сервере
До оборудования в сети инсталляции можно дотянуться из лаборатории, но не из облачного раннера. Запустите там сервер Signal Lab, храните его токен как секрет CI и отправляйте запуски на него:
signallab run tests/stage.json --server http://192.0.2.10:1430 --token-file token.txt --junit junit.xml --report reports/ - uses: ProAnima/SignalLab@v1.0.0
with:
experiments: tests/signallab/stage.json
server: http://192.0.2.10:1430
token: ${{ secrets.SIGNALLAB_TOKEN }}Эксперимент берётся из checkout конвейера; сервер запускает его со своей сетью, своими секретами и своей папкой данных, присылает шаги обратно и хранит отчёт (--report скачивает копию). Токен берётся из --token-file или SIGNALLAB_TOKEN. Коды завершения те же; недоступный сервер или отказ принять токен — это 3. Раннер должен уметь достать сервер: это self-hosted раннер в лаборатории или адрес сервера, который раннер может открыть.
Чтобы запускать на сервере вовсе без signallab, скрипт может обращаться к его HTTP API напрямую: см. Запуск эксперимента по HTTP.
Матрица запусков
Один эксперимент, каждая цель: каждая комбинация — отдельный запуск и отдельный набор тестов в отчёте JUnit, названный по её значениям.
signallab run tests/smoke.json \
-m device=192.0.2.20:9000,192.0.2.21:9000 \
-m user=admin,guestВ действии — по одному имени в строке:
with:
experiments: tests/signallab/smoke.json
matrix: |
device=192.0.2.20:9000,192.0.2.21:9000
user=admin,guest
fail-fast: "true"Или из файла, через --matrix-file (matrix-file в действии):
[
{ "device": "192.0.2.20:9000", "user": "admin" },
{ "device": "192.0.2.21:9000", "user": "guest" }
]Каждая комбинация проверяется до того, как первая что-либо отправит, из одной команды получается не более 256 запусков, а --fail-fast оставляет остальные незапущенными — в отчёте JUnit они значатся как пропущенные. Правила изложены на странице командной строки.
Чтобы перепробовать все профили эксперимента, запускайте по разу на профиль — по шагу на каждый или матрицей заданий вашей CI — с --profile.
Отчёты JUnit
--junit PATH (действие всегда пишет такой отчёт) содержит набор тестов на каждый запуск и тест-кейс на каждый узел: сбой — там, где он произошёл, на выбранном языке, с кодом ошибки и техническими подробностями; узлы, до которых запуск не дошёл, — как пропущенные; seed, исход, файл и значения матрицы — как свойства. GitHub (с действием для отчётов), GitLab (artifacts:reports:junit), Jenkins и Azure DevOps показывают его как результаты тестов. Его структура описана на странице командной строки.
Seed упавшего запуска указан в свойствах его набора и в журнале: --seed <that number> запускает его снова с теми же случайными значениями.
Зависимости, к которым обращается тестируемая система
Чтобы проверить собственную систему на API, устройстве или брокере, которых в CI нет, пусть их сыграет Signal Lab:
Внутри эксперимента узел Эмулятор играет зависимость в течение одного запуска, а узел Ждать HTTP-запрос проверяет, что ей отправила ваша система. Вывод запуска заканчивается сводкой того, о чём спрашивали каждый эмулятор. См. Эмуляторы.
Вокруг ваших тестов
signallab emulateотвечает в фоне, пока они идут, а его счётчики показывают, что вызывалось:bashsignallab emulate tests/payments-mock.json --for 300 --json > mock.ndjson & npm test wait
Вывод для скрипта
--json печатает по одному объекту JSON на строку в stdout и ничего больше: started, каждый step, ended со всем результатом и summary с total, passed, failed, not_started и exit_code. Ошибки несут стабильный code движка, поэтому скрипт может ветвиться по нему на любом языке. См. Вывод.
signallab run tests/smoke.json --json | jq -c 'select(.type == "ended") | {file, outcome, seed}'