Skip to main content

Использование параллелизма, выражений и тестовой матрицы

Использование расширенных функций GitHub Actions для непрерывной интеграции (CI).

Обзор примера

В этой статье на примере рабочего процесса демонстрируется применение некоторых функций GitHub Actions для непрерывной интеграции. Когда этот рабочий процесс активируется, он проверяет код с помощью матрицы сочетаний тестов с npm test.

На следующей схеме показано общее представление этапов рабочего процесса и их выполнение в задании:

Обзорная схема этапов рабочего процесса

Функции, используемые в этом примере

Пример рабочего процесса демонстрирует следующие возможности GitHub Actions:

ВозможностьРеализация
Запуск рабочего процесса из пользовательского интерфейса вручную:workflow_dispatch

Пример рабочего процесса

Следующий рабочий процесс был создан командой разработчиков документации для GitHub. Чтобы получить последнюю версию этого файла из репозитория github/docs, перейдите по адресу: test.yml.

Примечание. Каждая строка этого рабочего процесса описана в следующем разделе Основные сведения о примере.

YAML
name: Node.js Tests

# **What it does**: Runs our tests.
# **Why we have it**: We want our tests to pass before merging code.
# **Who does it impact**: Docs engineering, open-source engineering contributors.

on:
  workflow_dispatch:
  pull_request:
  push:
    branches:
      - main

permissions:
  contents: read
  # Needed for the 'trilom/file-changes-action' action
  pull-requests: read

# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
  group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
  cancel-in-progress: true

jobs:
  test:
    # Run on self-hosted if the private repo or ubuntu-latest if the public repo
    # See pull # 17442 in the private repo for context
    runs-on: ${{ fromJSON('["ubuntu-latest", "self-hosted"]')[github.repository == 'github/docs-internal'] }}
    timeout-minutes: 60
    strategy:
      fail-fast: false
      matrix:
        # The same array lives in test-windows.yml, so make any updates there too.
        test-group:
          [
            content,
            graphql,
            meta,
            rendering,
            routing,
            unit,
            linting,
            translations,
          ]
    steps:
      # Each of these ifs needs to be repeated at each step to make sure the required check still runs
      # Even if if doesn't do anything
      - name: Check out repo
        uses: actions/checkout@v3
        with:
          # Not all test suites need the LFS files. So instead, we opt to
          # NOT clone them initially and instead, include them manually
          # only for the test groups that we know need the files.
          lfs: ${{ matrix.test-group == 'content' }}
          # Enables cloning the Early Access repo later with the relevant personal access token
          persist-credentials: 'false'

      - name: Figure out which docs-early-access branch to checkout, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        id: check-early-access
        uses: actions/github-script@v6
        env:
          BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
        with:
          github-token: ${{ secrets.DOCUBOT_REPO_PAT }}
          result-encoding: string
          script: |
            // If being run from a PR, this becomes 'my-cool-branch'.
            // If run on main, with the `workflow_dispatch` action for
            // example, the value becomes 'main'.
            const { BRANCH_NAME } = process.env
            try {
              const response = await github.repos.getBranch({
                owner: 'github',
                repo: 'docs-early-access',
                BRANCH_NAME,
              })
              console.log(`Using docs-early-access branch called '${BRANCH_NAME}'.`)
              return BRANCH_NAME
            } catch (err) {
              if (err.status === 404) {
                console.log(`There is no docs-early-access branch called '${BRANCH_NAME}' so checking out 'main' instead.`)
                return 'main'
              }
              throw err
            }

      - name: Check out docs-early-access too, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        uses: actions/checkout@v3
        with:
          repository: github/docs-early-access
          token: ${{ secrets.DOCUBOT_REPO_PAT }}
          path: docs-early-access
          ref: ${{ steps.check-early-access.outputs.result }}

      - name: Merge docs-early-access repo's folders
        if: ${{ github.repository == 'github/docs-internal' }}
        run: |
          mv docs-early-access/assets assets/images/early-access
          mv docs-early-access/content content/early-access
          mv docs-early-access/data data/early-access
          rm -r docs-early-access

      # This is necessary when LFS files where cloned but does nothing
      # if actions/checkout was run with `lfs:false`.
      - name: Checkout LFS objects
        run: git lfs checkout

      - name: Gather files changed
        uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b
        id: get_diff_files
        with:
          # So that `steps.get_diff_files.outputs.files` becomes
          # a string like `foo.js path/bar.md`
          output: ' '

      - name: Insight into changed files
        run: |

          # Must to do this because the list of files can be HUGE. Especially
          # in a repo-sync when there are lots of translation files involved.
          echo "${{ steps.get_diff_files.outputs.files }}" > get_diff_files.txt

      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: 16.14.x
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Cache nextjs build
        uses: actions/cache@v3
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('package*.json') }}

      - name: Run build script
        run: npm run build

      - name: Run tests
        env:
          DIFF_FILE: get_diff_files.txt
          CHANGELOG_CACHE_FILE_PATH: tests/fixtures/changelog-feed.json
        run: npm test -- tests/${{ matrix.test-group }}/

Общие сведения о примере

 В следующей таблице собраны сведения о том, как каждая из этих возможностей используется при создании рабочего процесса GitHub Actions.

Код Пояснение
YAML
name: Node.js Tests

Имя рабочего процесса в том виде, в котором оно появится на вкладке "Действия" репозитория GitHub.

YAML
on:

Ключевое слово on позволяет определять события, которые активируются при запуске рабочего процесса. Здесь можно определить несколько событий. Дополнительные сведения см. в статье Активация рабочего процесса.

YAML
  workflow_dispatch:

Добавьте событие workflow_dispatch, если требуется возможность вручную запустить этот рабочий процесс в пользовательском интерфейсе. Дополнительные сведения см. на веб-сайте workflow_dispatch.

YAML
  pull_request:

Добавьте событие pull_request, чтобы рабочий процесс запускался автоматически при каждом создании или обновлении запроса на вытягивание. Дополнительные сведения см. на веб-сайте pull_request.

YAML
  push:
    branches:
      - main

Добавьте событие push, чтобы рабочий процесс запускался автоматически при каждой отправке фиксации в ветвь, соответствующую фильтру main. Дополнительные сведения см. на веб-сайте push.

YAML
permissions:
  contents: read
  pull-requests: read

Изменяет разрешения по умолчанию, предоставленные GITHUB_TOKEN. Зависит от потребностей рабочего процесса. Дополнительные сведения см. в статье Назначение разрешений заданиям.

YAML
concurrency:
  group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'

Создает группу параллелизма для определенных событий и использует оператор || для определения резервных значений. Дополнительные сведения см. в статье Использование параллелизма.

YAML
  cancel-in-progress: true

Отменяет задание или рабочий процесс, которые сейчас выполняются в той же группе параллелизма.

YAML
jobs:

Объединяет все задания, выполняемые в файле рабочего процесса.

YAML
  test:

Определяет задание с идентификатором test, который хранится в ключе jobs.

YAML
    runs-on: ${{ fromJSON('["ubuntu-latest", "self-hosted"]')[github.repository == 'github/docs-internal'] }}

Настраивает задание для запуска в средстве выполнения тестов, размещенном в GitHub, или в локальном средстве выполнения тестов в зависимости от репозитория, выполняющего рабочий процесс. В этом примере задание будет выполняться в локальном средстве выполнения тестов, если репозиторий называется docs-internal и находится в организации github. Если репозиторий не соответствует этому пути, задание будет выполняться в средстве выполнения тестов ubuntu-latest, размещенном в GitHub. Дополнительные сведения об этих параметрах см. в статье Выбор средства выполнения тестов для задания.

YAML
    timeout-minutes: 60

Задает максимальное количество минут, в течение которых задание будет выполняться до его автоматической отмены. Дополнительные сведения см. на веб-сайте timeout-minutes.

YAML
    strategy:
В этом разделе определяется матрица сборки для заданий.
YAML
      fail-fast: false

Если для fail-fast задать значение false, GitHub запрещается отменять любые выполняемые задания при сбое какого-либо задания в матрице.

YAML
      matrix:
        test-group:
          [
            content,
            graphql,
            meta,
            rendering,
            routing,
            unit,
            linting,
            translations,
          ]

Создает матрицу с именем test-group с массивом тестовых групп. Эти значения соответствуют именам тестовых групп, которые будут выполняться npm test.

YAML
    steps:

Группируют все шаги, которые будут выполняться в рамках задания test. Каждое задание в рабочем процессе имеет собственный раздел steps.

YAML
      - name: Check out repo
        uses: actions/checkout@v3
        with:
          lfs: ${{ matrix.test-group == 'content' }}
          persist-credentials: 'false'

Ключевое слово uses сообщает заданию, что нужно получить действие с именем actions/checkout. Это действие, которое извлекает репозиторий и загружает его в средство выполнения, позволяя выполнять действия в коде (например, средства тестирования). Действие извлечения необходимо использовать в любой момент, когда рабочий процесс будет выполняться в коде репозитория или если вы используете действие, определенное в репозитории. Некоторые дополнительные параметры предоставляются действию с помощью ключа with.

YAML
      - name: Figure out which docs-early-access branch to checkout, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        id: check-early-access
        uses: actions/github-script@v6
        env:
          BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
        with:
          github-token: ${{ secrets.DOCUBOT_REPO_PAT }}
          result-encoding: string
          script: |
            // If being run from a PR, this becomes 'my-cool-branch'.
            // If run on main, with the `workflow_dispatch` action for
            // example, the value becomes 'main'.
            const { BRANCH_NAME } = process.env
            try {
              const response = await github.repos.getBranch({
                owner: 'github',
                repo: 'docs-early-access',
                BRANCH_NAME,
              })
              console.log(`Using docs-early-access branch called '${BRANCH_NAME}'.`)
              return BRANCH_NAME
            } catch (err) {
              if (err.status === 404) {
                console.log(`There is no docs-early-access branch called '${BRANCH_NAME}' so checking out 'main' instead.`)
                return 'main'
              }
              throw err
            }

Если текущий репозиторий является репозиторием github/docs-internal, на этом шаге используется действие actions/github-script для запуска скрипта для проверки наличия ветви docs-early-access.

YAML
      - name: Check out docs-early-access too, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        uses: actions/checkout@v3
        with:
          repository: github/docs-early-access
          token: ${{ secrets.DOCUBOT_REPO_PAT }}
          path: docs-early-access
          ref: ${{ steps.check-early-access.outputs.result }}

Если текущий репозиторий является репозиторием github/docs-internal, на этом шаге извлекается ветвь из github/docs-early-access, указанного на предыдущем шаге.

YAML
      - name: Merge docs-early-access repo's folders
        if: ${{ github.repository == 'github/docs-internal' }}
        run: |
          mv docs-early-access/assets assets/images/early-access
          mv docs-early-access/content content/early-access
          mv docs-early-access/data data/early-access
          rm -r docs-early-access

Если текущий репозиторий является репозиторием github/docs-internal, на этом шаге используется ключевое слово run для выполнения команд оболочки для перемещения папок репозитория docs-early-access в папки основного репозитория.

YAML
      - name: Checkout LFS objects
        run: git lfs checkout

На этом шаге выполняется команда для извлечения объектов LFS из репозитория.

YAML
      - name: Gather files changed
        uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b
        id: get_diff_files
        with:
          # So that `steps.get_diff_files.outputs.files` becomes
          # a string like `foo.js path/bar.md`
          output: ' '

На этом шаге используется действие trilom/file-changes-action для сбора файлов, измененных в запросе на вытягивание, чтобы их можно было проанализировать на следующем шаге. Этот пример закрепляется в определенной версии действия с помощью SHA a6ca26c14274c33b15e6499323aac178af06ad4b.

YAML
      - name: Insight into changed files
        run: |
          echo "${{ steps.get_diff_files.outputs.files }}" > get_diff_files.txt

На этом шаге выполняется команда оболочки, которая использует выходные данные предыдущего шага для создания файла, содержащего список файлов, измененных в запросе на вытягивание.

YAML
      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: 16.14.x
          cache: npm

На этом шаге используется действие actions/setup-node для установки указанной версии пакета программного обеспечения node в средстве выполнения тестов, которое предоставляет доступ к команде npm.

YAML
      - name: Install dependencies
        run: npm ci

На этом шаге выполняется команда оболочки npm ci для установки пакетов программного обеспечения npm для проекта.

YAML
      - name: Cache nextjs build
        uses: actions/cache@v3
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('package*.json') }}

На этом шаге используется действие actions/cache для кэширования сборки Next.js, чтобы рабочий процесс пытался получить кэш сборки, а не перестраивать его с нуля каждый раз. Дополнительные сведения см. в разделе Кэширование зависимостей для ускорения рабочих процессов.

YAML
      - name: Run build script
        run: npm run build

На этом шаге запускается скрипт сборки.

YAML
      - name: Run tests
        env:
          DIFF_FILE: get_diff_files.txt
          CHANGELOG_CACHE_FILE_PATH: tests/fixtures/changelog-feed.json
        run: npm test -- tests/${{ matrix.test-group }}/

На этом шаге выполняются тесты с использованием npm test, а матрица тестов предоставляет другое значение для ${{ matrix.test-group }} для каждого задания в матрице. Она использует переменную среды DIFF_FILE, чтобы узнать, какие файлы были изменены, и переменную среды CHANGELOG_CACHE_FILE_PATH для файла кэша изменений.

Дальнейшие действия