Skip to main content

Uso de simultaneidad, expresiones y una matriz de pruebas

Cómo usar características avanzadas de GitHub Actions para la integración continua (CI).

Nota: Actualmente los ejecutores hospedados en GitHub no se admiten en GitHub Enterprise Server. Puede ver más información sobre la compatibilidad futura planeada en GitHub public roadmap.

Información general de ejemplo

En este artículo se usa un flujo de trabajo de ejemplo para mostrar algunas de las principales características de CI de GitHub Actions. Cuando se desencadena este flujo de trabajo, prueba el código mediante una matriz de combinaciones de pruebas con npm test.

En el diagrama siguiente se muestra una vista general de los pasos del flujo de trabajo y de cómo se ejecutan en el trabajo:

Diagrama general de los pasos del flujo de trabajo

Características que se usan en este ejemplo

El flujo de trabajo de ejemplo muestra las funcionalidades siguientes de GitHub Actions:

CaracterísticaImplementación
Ejecución manual de un flujo de trabajo desde la interfaz de usuario:workflow_dispatch

Flujo de trabajo de ejemplo

El flujo de trabajo siguiente lo creó el equipo de ingeniería de documentos de GitHub. Para revisar la versión más reciente de este archivo en el repositorio github/docs, consulta test.yml.

Nota: cada línea de este flujo de trabajo se explica en la sección siguiente en "Descripción del ejemplo".

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 PAT
          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 }}/

Descripción del ejemplo

En la tabla siguiente se explica cómo se usa cada una de estas características al crear un flujo de trabajo de GitHub Actions.

Código Explicación
YAML
name: Node.js Tests

El nombre del flujo de trabajo tal como aparecerá en la pestaña "Acciones" del repositorio GitHub.

YAML
on:

La palabra clave on te permite definir los eventos que se desencadenan cuando se ejecuta el flujo de trabajo. Aquí puedes definir varios eventos. Para más información, consulta "Desencadenamiento de un flujo de trabajo".

YAML
  workflow_dispatch:

Agrega el evento workflow_dispatch si quieres ejecutar manualmente este flujo de trabajo en la interfaz de usuario. Para más información, vea workflow_dispatch.

YAML
  pull_request:

Agrega el evento pull_request para que el flujo de trabajo se ejecute automáticamente cada vez que se crea o actualiza una solicitud de incorporación de cambios. Para más información, vea pull_request.

YAML
  push:
    branches:
      - main

Agrega el evento push para que el flujo de trabajo se ejecute automáticamente cada vez que se inserta una confirmación en una rama que coincida con el filtro main. Para más información, vea push.

YAML
permissions:
  contents: read
  pull-requests: read

Modifica los permisos predeterminados concedidos a GITHUB_TOKEN. Esto variará en función de las necesidades del flujo de trabajo. Para más información, consulta "Asignación de permisos a trabajos".

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

Crea un grupo de simultaneidad para eventos específicos y utiliza el operador || para definir los valores de reserva. Para más información, consulta "Uso de simultaneidad".

YAML
  cancel-in-progress: true

Cancela cualquier flujo de trabajo o trabajo actualmente en ejecución en el mismo grupo de simultaneidad.

YAML
jobs:

Agrupa todos los trabajos que se ejecutan en el archivo de flujo de trabajo.

YAML
  test:

Define un trabajo con el identificador test que se almacena dentro de la clave jobs.

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

Configura el trabajo para que se ejecute en un ejecutor hospedado en GitHub o en un ejecutor autohospedado en función del repositorio que ejecuta el flujo de trabajo. En este ejemplo, el trabajo se ejecutará en un ejecutor autohospedado si el repositorio tiene el nombre docs-internal y está dentro de la organización github. Si el repositorio no coincide con esta ruta de acceso, se ejecutará en un ejecutor ubuntu-latest hospedado en GitHub. Para más información sobre estas opciones, consulta "Elección del ejecutor para un trabajo".

YAML
    timeout-minutes: 60

Establece el número máximo de minutos para permitir que el trabajo se ejecute antes de que se cancele automáticamente. Para más información, vea timeout-minutes.

YAML
    strategy:
En esta sección, se define la matriz de compilación de los trabajos.
YAML
      fail-fast: false

Cuandofail-fast se establece en false, se impide que GitHub cancele todos los trabajos en curso si se produce algún error en un trabajo de la matriz.

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

Crea una matriz denominada test-group con una matriz de grupos de pruebas. Estos valores coinciden con los nombres de los grupos de pruebas que npm test ejecutará.

YAML
    steps:

Agrupa todos los pasos que se ejecutarán como parte del trabajo test. Cada trabajo de un flujo de trabajo tiene su propia sección steps.

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

La palabra clave uses indica al trabajo que recupere la acción denominada actions/checkout. Esta es una acción que revisa tu repositorio y lo descarga al ejecutor, lo que te permite ejecutar acciones contra tu código (tales como las herramientas de prueba). Debes utilizar la acción de verificación cada que tu flujo de trabajo se ejecute contra el código del repositorio o cada que estés utilizando una acción definida en el repositorio. Algunas opciones adicionales se proporcionan a la acción mediante la clave 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
            }

Si el repositorio actual es el repositorio github/docs-internal, este paso usa la acción actions/github-script para ejecutar un script a fin de comprobar si hay una rama denominada 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 }}

Si el repositorio actual es el repositorio github/docs-internal, este paso extrae la rama del github/docs-early-access que se identificó en el paso anterior.

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

Si el repositorio actual es el repositoriogithub/docs-internal, este paso usa la palabra clave run para ejecutar comandos de shell para mover las carpetas del repositorio docs-early-access a las carpetas del repositorio principal.

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

En este paso, se ejecuta un comando para extraer objetos LFS del repositorio.

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

En ese paso, se usa la acción trilom/file-changes-action para recopilar los archivos modificados en la solicitud de incorporación de cambios a fin de que se los pueda analizar en el paso siguiente. Este ejemplo se ancla a una versión específica de la acción mediante el SHA a6ca26c14274c33b15e6499323aac178af06ad4b.

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

En este paso, se ejecuta un comando de shell que utiliza una salida del paso anterior para crear un archivo que contenga la lista de archivos modificados en la solicitud de incorporación de cambios.

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

En este paso, se usa la acción actions/setup-node para instalar la versión especificada del paquete de software node en el ejecutor, lo que te da acceso al comando npm.

YAML
      - name: Install dependencies
        run: npm ci

En este paso, se ejecuta el comando de shell npm ci para instalar los paquetes de software npm para el proyecto.

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

En este paso, se usa la acción actions/cache para almacenar en caché la compilación Next.js de modo que el flujo de trabajo intente recuperar una caché de la compilación y no recompilarla de cero cada vez. Para más información, vea "Almacenamiento en caché de dependencias para acelerar los flujos de trabajo".

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

En este paso, se ejecuta el script de compilación.

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 }}/

En este paso, se ejecutan las pruebas mediante npm test y la matriz de pruebas proporciona un valor distinto para ${{ matrix.test-group }} para cada trabajo de la matriz. Usa la variable de entorno DIFF_FILE para saber qué archivos se modificaron y la variable de entorno CHANGELOG_CACHE_FILE_PATH para el archivo de caché del registro de cambios.

Pasos siguientes