Налаштуйте безперервну інтеграцію


Налаштування безперервної інтеграції (CI) для треку дуже важливе, адже це допомагає виявляти помилки.

GitHub Actions

Репозиторії Exercism (зокрема репозиторії треків) використовують GitHub Actions для запуску свого CI. GitHub Actions ґрунтується на робочих процесах, які визначають скрипти для автоматичного запуску щоразу, коли відбувається певна подія (наприклад, надсилання коміту). Докладніше про робочі процеси GitHub Actions можна дізнатися з документації про робочі процеси.

Попередньо встановлені робочі процеси

Треки постачаються з кількома попередньо встановленими робочими процесами, більшість з яких не варто змінювати (їх називають спільними робочими процесами). Але є один робочий процес, який варто змінити, а саме робочий процес test.yml.

Робочий процес тестування

Мета робочого процесу test.yml - перевірити, що вправи треку перебувають у належному стані. Робочий процес налаштований на автоматичний запуск (термінологія GitHub Actions: запускається) під час надсилання змін до гілки main або до гілки пул-реквесту.

Сам робочий процес не повинен робити багато чого, крім:

  • Отримання коду (уже реалізовано)
  • Встановлення залежностей (наприклад, встановлення пакетів, необовʼязково)
  • Встановлення інструментарію (наприклад, встановлення SDK, необовʼязково)
  • Запуск скрипта перевірки вправ (уже реалізовано)

Реалізація скрипта перевірки вправ

Як уже згадувалося, вправи перевіряються за допомогою скрипта, а саме скрипта bin/verify-exercises (bash). Цей скрипт майже готовий і робить таке:

  • Проходить циклом по всіх каталогах вправ
  • Для кожного каталогу вправи він потім:
    • Копіює зразкове рішення у файли рішення (заготовки) (уже реалізовано)
    • Викликає функцію unskip_tests, у якій можна зняти пропуски з тестів у тестових файлах (необовʼязково)
    • Викликає функцію run_tests, у якій потрібно запустити тести (обовʼязково)

Функції run_tests й unskip_tests - це єдине, що потрібно реалізувати.

Зняття пропусків з тестів

Якщо трек підтримує пропуск тестів, ми маємо переконатися, що під час перевірки зразкового рішення вправи жоден тест не пропускається. Загалом є два способи, якими треки підтримують «зняття пропусків» з тестів:

  1. Видалення анотацій/коду/тексту з тестових файлів. Наприклад, заміна test.skip на test.
  2. Надання змінної середовища. Наприклад, встановлення SKIP_TESTS=false.

Видалення анотацій/коду/тексту з тестових файлів

Якщо пропуск тестів ґрунтується на файлах (перший варіант, згаданий вище), відредагуйте функцію unskip_tests, щоб змінити тестові файли (наявний код уже виконує прохід циклом по тестових файлах).

Note

Функція unskip_test працює з копією каталогу вправи, тож сміливо змінюйте файли на свій розсуд.

Приклад

bin/verify-exercises file треку Arturo використовує sed, щоб зняти пропуски з тестів у тестових файлах:

unskip_tests() {
    jq -r '.files.test[]' .meta/config.json | while read -r test_file; do
        sed -i 's/test.skip/test/g' "${test_file}"
    done
}

Надання змінної середовища

Caution

Якщо для зняття пропусків з тестів потрібно встановити змінну середовища, переконайтеся, що її встановлено у функції run_tests.

Запуск тестів

Функція run_tests відповідає за запуск тестів вправи. Коли функцію викликають, зразкові файли вже скопійовано у файли рішення (заготовки), тож потрібно лише викликати правильну команду для запуску тестів.

Функція повинна повернути нуль як код виходу, якщо всі тести проходять, або ненульовий код виходу в іншому разі.

Note

Функція run_tests працює з копією каталогу вправи, тож сміливо змінюйте файли на свій розсуд.

Варіант 1: використати інструментарій мови

Типовий варіант для скрипта перевірки вправ - використовувати інструментарій мови (SDK/бінарний файл/тощо), і саме так робить більшість треків. Кожен трек матиме власний спосіб запуску тестів, але зазвичай це лише одна команда.

Приклад

bin/verify-exercises file треку Arturo змінює функцію run_tests, щоб просто викликати команду arturo для тестового файлу:

run_tests() {
    arturo tester.art
}

Варіант 2: використати Docker-образ тест-раннера

Другий варіант - перевіряти вправи, запускаючи тест-раннер треку. Це, звісно, залежить від того, чи має трек робочий тест-раннер.

Якщо у треку ще немає тест-раннера, можна або:

  • зібрати робочий тест-раннер, або
  • скористатися варіантом 1 і безпосередньо використати інструментарій мови

До типового скрипта bin/verify-exercises потрібно внести такі зміни:

  1. Переконатися, що команда docker доступна
  2. Завантажити Docker-образ тест-раннера
  3. Використати docker run, щоб запустити Docker-образ тест-раннера для кожної вправи
  4. Використати jq, щоб перевірити, що файл results.json, який повертає Docker-контейнер, указує на те, що всі тести пройдено
  5. Видалити функцію unskip_test і виклик цієї функції
Note

Головна перевага цього підходу в тому, що він найкраще відтворює те, як тести запускаються в продакшені (на сайті). За такого підходу менш імовірно, що в продакшені не спрацює те, що пройшло в CI. Недолік цього підходу в тому, що він зазвичай повільніший через потребу завантажувати Docker-образ і через накладні витрати Docker.

Приклад

bin/verify-exercises file треку Unison додає перевірку того, що команда docker також встановлена:

required_tool docker

Потім він завантажує образ тест-раннера треку:

docker pull exercism/unison-test-runner

Далі він змінює функцію run_tests, щоб використати docker run для запуску тест-раннера поточної вправи (яка перебуває в робочому каталозі), а потім команду jq для перевірки належного статусу:

run_tests() {
    local slug

    slug="${1}"

    docker run \
        --rm \
        --network none \
        --mount type=bind,src="${PWD}",dst=/solution \
        --mount type=bind,src="${PWD}",dst=/output \
        --tmpfs /tmp:rw \
        exercism/unison-test-runner "${slug}" "/solution" "/output"
    jq -e '.status == "pass"' "${PWD}/results.json" >/dev/null 2>&1
}

Нарешті, потрібно змінити виклик функції run_tests, бо тепер вона потребує слага:

run_tests "${slug}"

Реалізація робочого процесу тестування

Тепер, коли скрипт verify-exercises готовий, час завершити робочий процес test.yml. Те, як це зробити, залежить від того, який варіант реалізації скрипта verify-exercises обрано.

Варіант 1: використати інструментарій мови

Якщо скрипт verify-exercises безпосередньо використовує інструментарій мови, робочому процесу тестування потрібно буде встановити:

  • Залежності інструментарію мови, як-от openssh або компілятор C/C++.
  • Інструментарій мови, як-от SDK або бінарний файл. Якщо встановлення інструментарію мови не додає встановлений бінарний файл (файли) до шляху, обовʼязково додайте його до системного шляху GitHub Actions.

Щойно це буде зроблено, verify-exercises працюватиме як слід, і безперервну інтеграцію успішно налаштовано!

Наприклад, дивіться робочий процес test.yml треку Arturo:

name: Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:

jobs:
  ci:
    runs-on: ubuntu-22.04

    steps:
      - name: Checkout repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install libgtk-3-dev libwebkit2gtk-4.0-dev libmpfr-dev

      - name: Install Arturo
        run: bin/install-arturo
        env:
          GH_TOKEN: ${{ github.token }}

      - name: Verify all exercises
        run: bin/verify-exercises

Варіант 2: використати Docker-образ тест-раннера

Другий варіант - перевіряти вправи, запускаючи тест-раннер треку. Цей варіант вимагає виконання двох умов:

  1. Трек має робочий тест-раннер
  2. Скрипт verify-exercises використовує Docker-образ тест-раннера для запуску тестів вправи

Якщо у треку ще немає тест-раннера, можна або:

  • зібрати робочий тест-раннер, або
  • скористатися варіантом 1 і безпосередньо використати інструментарій мови

Цей підхід має кілька переваг:

  1. Не потрібно встановлювати жодних залежностей чи інструментарію в робочому процесі тестування (їх буде встановлено всередині Docker-образу)
  2. Цей підхід найкраще відтворює те, як тести запускаються в продакшені (на сайті), що зменшує ймовірність проблем у продакшені.

Головний недолік у тому, що він, імовірно, повільніший через потребу завантажувати Docker-образ і через накладні витрати Docker.

Є кілька способів завантажити Docker-образ тест-раннера:

  1. Завантажити образ усередині файлу verify-exercises. Цей підхід використовує трек Unison.
  2. Завантажити образ усередині робочого процесу. Цей підхід використовує трек Standard ML.
  3. Зібрати образ усередині робочого процесу. Цей підхід використовує трек 8th.

То який підхід обрати? Ми радимо принаймні реалізувати варіант номер 1, щоб скрипт verify-exercises став самодостатнім. Якщо образ особливо великий, може бути корисно також реалізувати варіант 3, який збереже зібраний Docker-образ у кеші GitHub Actions. Наступні запуски зможуть просто читати Docker-образ із кешу замість завантаження, що може бути краще для продуктивності (будь ласка, виміряйте, щоб переконатися).

Варіант 3: запуск скрипта перевірки вправ усередині Docker-образу тест-раннера

Третій, альтернативний варіант - це гібрид двох попередніх варіантів. Тут ми також використовуємо Docker-образ тест-раннера, але цього разу запускаємо скрипт verify-exercises усередині цього Docker-образу. Щоб увімкнути цей варіант, потрібно встановити контейнер робочого процесу як тест-раннер:

container:
  image: exercism/vimscript-test-runner

Тоді ми можемо пропустити кроки встановлення залежностей та інструментарію (їх буде встановлено всередині Docker-образу тест-раннера) і перейти до запуску скрипта bin/verify-exercises.

Приклад

Робочий процес test.yml треку vimscript використовує цей варіант:

name: Verify Exercises

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

jobs:
  ci:
    runs-on: ubuntu-24.04
    container:
      image: exercism/vimscript-test-runner

    steps:
      - name: Checkout repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332

      - name: Verify all exercises
        run: bin/verify-exercises