Перейти к содержимому

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:

yaml
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:

yaml
      - 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:

yaml
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, скрипт развёртывания):

bash
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): она работает в этом выпуске и в более новых дистрибутивах.

yaml
      - 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 и отправляйте запуски на него:

bash
signallab run tests/stage.json --server http://192.0.2.10:1430 --token-file token.txt --junit junit.xml --report reports/
yaml
      - 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, названный по её значениям.

bash
signallab run tests/smoke.json \
  -m device=192.0.2.20:9000,192.0.2.21:9000 \
  -m user=admin,guest

В действии — по одному имени в строке:

yaml
        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 в действии):

json
[
  { "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 отвечает в фоне, пока они идут, а его счётчики показывают, что вызывалось:

    bash
    signallab 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 движка, поэтому скрипт может ветвиться по нему на любом языке. См. Вывод.

bash
signallab run tests/smoke.json --json | jq -c 'select(.type == "ended") | {file, outcome, seed}'