Configura a integração contínua


Configurar a Integração Contínua (CI) para o teu track é muito importante, porque ajuda a detetar erros.

GitHub Actions

Os repositórios do Exercism (incluindo os repositórios de tracks) usam o GitHub Actions para executar o CI. O GitHub Actions baseia-se em fluxos de trabalho, que definem scripts a executar automaticamente sempre que ocorre um evento específico (por exemplo, quando se faz push de um commit). Para mais informações sobre os fluxos de trabalho do GitHub Actions, consulta a documentação dos fluxos de trabalho.

Fluxos de trabalho pré-instalados

Os tracks vêm com vários fluxos de trabalho pré-instalados, a maioria dos quais não deves modificar (são os chamados fluxos de trabalho partilhados). No entanto, há um fluxo de trabalho que deves alterar: o fluxo de trabalho test.yml.

Fluxo de trabalho de testes

O objetivo do fluxo de trabalho test.yml é verificar se os exercícios do track estão em condições. O fluxo de trabalho está configurado para ser executado automaticamente (na terminologia do GitHub Actions: é despoletado) quando se faz push para o ramo main ou para o ramo de um pull request.

O próprio fluxo de trabalho não deve fazer muito, exceto:

  • Fazer o checkout do código (já implementado)
  • Instalar dependências (por exemplo, instalar pacotes, opcional)
  • Instalar ferramentas (por exemplo, instalar um SDK, opcional)
  • Executar o script de verificação dos exercícios (já implementado)

Implementar o script de verificação dos exercícios

Como já foi referido, os exercícios são verificados através de um script, o script bin/verify-exercises (bash). Este script está quase pronto e faz o seguinte:

  • Percorre todos os diretórios dos exercícios
  • Para cada diretório de exercício, faz o seguinte:
    • Copia a solução de exemplo/exemplar para os ficheiros de solução (stub) (já implementado)
    • Chama a função unskip_tests, na qual podes deixar de ignorar testes nos teus ficheiros de teste (opcional)
    • Chama a função run_tests, na qual deves executar os testes (obrigatório)

As funções run_tests e unskip_tests são as únicas coisas que precisas de implementar.

Deixar de ignorar testes

Se o teu track suporta ignorar testes, temos de garantir que nenhum teste é ignorado ao verificar a solução de exemplo/exemplar de um exercício. Em geral, há duas formas de os tracks suportarem deixar de ignorar testes:

  1. Remover anotações/código/texto dos ficheiros de teste. Por exemplo, mudar test.skip para test.
  2. Fornecer uma variável de ambiente. Por exemplo, definir SKIP_TESTS=false.

Remover anotações/código/texto dos ficheiros de teste

Se ignorar testes for feito com base nos ficheiros (a primeira opção acima), edita a função unskip_tests para modificar os ficheiros de teste (o código existente já trata de percorrer os ficheiros de teste).

Note

A função unskip_test é executada numa cópia do diretório de um exercício, por isso não hesites em modificar os ficheiros como entenderes.

Exemplo

O bin/verify-exercises file do track Arturo usa o sed para deixar de ignorar os testes dentro dos ficheiros de teste:

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
}

Fornecer uma variável de ambiente

Caution

Se deixar de ignorar testes exigir a definição de uma variável de ambiente, certifica-te de que ela é definida na função run_tests.

Executar os testes

A função run_tests é responsável por executar os testes de um exercício. Quando a função é chamada, os ficheiros de exemplo/exemplar já foram copiados para os ficheiros de solução (stub), por isso só precisas de chamar o comando certo para executar os testes.

A função tem de devolver zero como código de saída se todos os testes passarem; caso contrário, tem de devolver um código de saída diferente de zero.

Note

A função run_tests é executada numa cópia do diretório de um exercício, por isso não hesites em modificar os ficheiros como entenderes.

Opção 1: usar as ferramentas da linguagem

A opção predefinida do script de verificação dos exercícios é usar as ferramentas da linguagem (SDK/binário/etc.), que é o que a maioria dos tracks usa. Cada track tem a sua própria forma de executar os testes, mas normalmente é apenas um único comando.

Exemplo

O bin/verify-exercises file do track Arturo modifica a função run_tests para simplesmente chamar o comando arturo no ficheiro de teste:

run_tests() {
    arturo tester.art
}

Opção 2: usar a imagem Docker do test runner

A segunda opção é verificar os exercícios executando o test runner do track. Isto depende, naturalmente, de o track ter um test runner funcional.

Se o teu track ainda não tem um test runner, podes:

  • criar um test runner funcional, ou
  • usar a opção 1 e utilizar diretamente as ferramentas da linguagem

É preciso fazer as seguintes alterações ao script bin/verify-exercises predefinido:

  1. Verificar se o comando docker está disponível
  2. Descarregar a imagem Docker do test runner
  3. Usar docker run para executar a imagem Docker do test runner em cada exercício
  4. Usar o jq para verificar se o ficheiro results.json devolvido pelo contentor Docker indica que todos os testes passaram
  5. Remover a função unskip_test e a chamada a essa função
Note

A principal vantagem desta abordagem é que imita melhor a forma como os testes são executados em produção (no site). Com esta abordagem, é menos provável que algo falhe em produção depois de ter passado no CI. A desvantagem desta abordagem é que costuma ser mais lenta, por ser preciso descarregar a imagem Docker e devido à sobrecarga do Docker.

Exemplo

O bin/verify-exercises file do track Unison acrescenta a verificação de que o comando docker também está instalado:

required_tool docker

Em seguida, descarrega a imagem do test runner do track:

docker pull exercism/unison-test-runner

Depois, modifica a função run_tests para usar docker run e executar o test runner no exercício atual (que está no diretório de trabalho), seguido de um comando jq para verificar se o estado é o correto:

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
}

Por fim, temos de alterar a forma como o comando run_tests é chamado, pois agora exige o slug:

run_tests "${slug}"

Implementar o fluxo de trabalho de testes

Agora que o script verify-exercises está terminado, é altura de finalizar o fluxo de trabalho test.yml. A forma de o fazer depende da opção escolhida para a implementação do script verify-exercises.

Opção 1: usar as ferramentas da linguagem

Se o script verify-exercises usar diretamente as ferramentas da linguagem, o fluxo de trabalho de testes terá de instalar:

  • As dependências das ferramentas da linguagem, como o openssh ou um compilador de C/C++.
  • As ferramentas da linguagem, como um SDK ou um binário. Se a instalação das ferramentas da linguagem não adicionar o binário ou os binários instalados ao path, certifica-te de que os adicionas ao path do sistema do GitHub Actions.

Depois de o fazeres, o verify-exercises deve funcionar como esperado e tens o CI configurado com sucesso!

Para veres um exemplo, consulta o fluxo de trabalho test.yml do track 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

Opção 2: usar a imagem Docker do test runner

A segunda opção é verificar os exercícios executando o test runner do track. Esta opção exige duas condições:

  1. O track tem um test runner funcional
  2. O script verify-exercises usa a imagem Docker do test runner para executar os testes de um exercício

Se o teu track ainda não tem um test runner, podes:

  • criar um test runner funcional, ou
  • usar a opção 1 e utilizar diretamente as ferramentas da linguagem

Esta abordagem tem algumas vantagens:

  1. Não precisas de instalar dependências nem ferramentas no fluxo de trabalho de testes (uma vez que já estarão instaladas na imagem Docker)
  2. A abordagem imita melhor a forma como os testes são executados em produção (no site), o que reduz a probabilidade de problemas em produção.

A principal desvantagem é que provavelmente é mais lenta, por ser preciso descarregar a imagem Docker e devido à sobrecarga do Docker.

Há algumas formas de descarregar a imagem Docker do test runner:

  1. Descarregar a imagem dentro do ficheiro verify-exercises. É a abordagem adotada pelo track Unison.
  2. Descarregar a imagem dentro do fluxo de trabalho. É a abordagem adotada pelo track Standard ML.
  3. Criar a imagem dentro do fluxo de trabalho. É a abordagem adotada pelo track 8th.

Então, que abordagem usar? Recomendamos que implementes pelo menos a opção 1, para que o script verify-exercises seja autónomo. Se a tua imagem for particularmente grande, pode ser benéfico implementar também a opção 3, que guarda a imagem Docker criada na cache do GitHub Actions. As execuções seguintes podem então limitar-se a ler a imagem Docker da cache, em vez de a descarregar, o que pode ser melhor em termos de desempenho (mede, para teres a certeza).

Opção 3: executar o script de verificação dos exercícios dentro da imagem Docker do test runner

Uma terceira opção, alternativa, é um híbrido das duas anteriores. Aqui, também usamos a imagem Docker do test runner, mas desta vez executamos o script verify-exercises dentro dessa imagem Docker. Para ativar esta opção, temos de definir o contentor do fluxo de trabalho como o test runner:

container:
  image: exercism/vimscript-test-runner

Podemos então saltar os passos de instalação de dependências e ferramentas (uma vez que já estarão instaladas na imagem Docker do test runner) e passar à execução do script bin/verify-exercises.

Exemplo

O fluxo de trabalho test.yml do track vimscript usa esta opção:

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