Mets en place l'intégration continue


Mettre en place l'intégration continue (CI) pour ton parcours est très important, car cela aide à repérer les erreurs.

GitHub Actions

Les dépôts Exercism (y compris les dépôts de parcours) utilisent GitHub Actions pour exécuter leur CI. GitHub Actions repose sur des workflows, qui définissent des scripts à exécuter automatiquement dès qu'un événement précis se produit (par exemple lorsqu'un commit est poussé). Pour plus d'informations sur les workflows GitHub Actions, consulte la documentation sur les workflows.

Workflows préinstallés

Les parcours sont livrés avec un certain nombre de workflows préinstallés, dont la plupart ne doivent pas être modifiés (qu'on appelle les workflows partagés). Il y a toutefois un workflow que tu dois modifier : le workflow test.yml.

Le workflow de test

Le but du workflow test.yml est de vérifier que les exercices du parcours sont en bon état. Le workflow est configuré pour s'exécuter automatiquement (dans la terminologie de GitHub Actions : il est déclenché) lors d'un push vers la branche main ou vers la branche d'une pull request.

Le workflow lui-même ne doit pas faire grand-chose, à part :

  • Récupérer le code (déjà implémenté)
  • Installer les dépendances (par exemple installer des paquets, facultatif)
  • Installer l'outillage (par exemple installer un SDK, facultatif)
  • Exécuter le script de vérification des exercices (déjà implémenté)

Implémente le script de vérification des exercices

Comme mentionné, les exercices sont vérifiés par un script, à savoir le script bin/verify-exercises (bash). Ce script est presque terminé, et fait ce qui suit :

  • Il parcourt tous les répertoires d'exercices
  • Pour chaque répertoire d'exercice, il effectue ensuite les opérations suivantes :
    • Copie la solution d'exemple (exemplar) dans les fichiers de solution (stub) (déjà implémenté)
    • Appelle la fonction unskip_tests, dans laquelle tu peux réactiver les tests ignorés dans tes fichiers de test (facultatif)
    • Appelle la fonction run_tests, dans laquelle tu dois exécuter les tests (obligatoire)

Les fonctions run_tests et unskip_tests sont les seules choses que tu dois implémenter.

Réactiver les tests

Si ton parcours permet d'ignorer des tests, on doit s'assurer qu'aucun test n'est ignoré lors de la vérification de la solution d'exemple (exemplar) d'un exercice. En général, il existe deux façons pour un parcours de gérer la réactivation des tests :

  1. Supprimer les annotations, le code ou le texte des fichiers de test. Par exemple, remplacer test.skip par test.
  2. Fournir une variable d'environnement. Par exemple, définir SKIP_TESTS=false.

Supprimer les annotations, le code ou le texte des fichiers de test

Si l'ignorance des tests se fait au niveau des fichiers (la première option mentionnée ci-dessus), modifie la fonction unskip_tests pour modifier les fichiers de test (le code existant gère déjà le parcours des fichiers de test).

Note

La fonction unskip_test s'exécute sur une copie du répertoire d'un exercice, donc n'hésite pas à modifier les fichiers comme bon te semble.

Exemple

Le fichier bin/verify-exercises du parcours Arturo utilise sed pour réactiver les tests dans les fichiers de test :

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
}

Fournir une variable d'environnement

Caution

Si la réactivation des tests nécessite qu'une variable d'environnement soit définie, assure-toi qu'elle est bien définie dans la fonction run_tests.

Exécuter les tests

La fonction run_tests est chargée d'exécuter les tests d'un exercice. Quand la fonction est appelée, les fichiers d'exemple (exemplar) ont déjà été copiés dans les fichiers de solution (stub), donc tu n'as plus qu'à appeler la bonne commande pour exécuter les tests.

La fonction doit renvoyer zéro comme code de sortie si tous les tests réussissent, sinon elle doit renvoyer un code de sortie différent de zéro.

Note

La fonction run_tests s'exécute sur une copie du répertoire d'un exercice, donc n'hésite pas à modifier les fichiers comme bon te semble.

Option 1 : utiliser l'outillage du langage

L'option par défaut pour le script de vérification des exercices est d'utiliser l'outillage du langage (SDK, binaire, etc.), ce que font la plupart des parcours. Chaque parcours a sa propre façon d'exécuter les tests, mais il s'agit généralement d'une seule commande.

Exemple

Le fichier bin/verify-exercises du parcours Arturo modifie la fonction run_tests pour simplement appeler la commande arturo sur le fichier de test :

run_tests() {
    arturo tester.art
}

Option 2 : utiliser l'image Docker de l'exécuteur de tests

La deuxième option consiste à vérifier les exercices en exécutant l'exécuteur de tests du parcours. Cela dépend bien sûr du fait que le parcours dispose d'un exécuteur de tests fonctionnel.

Si ton parcours n'a pas encore d'exécuteur de tests, tu peux soit :

  • construire un exécuteur de tests fonctionnel, soit
  • utiliser l'option 1 et te servir directement de l'outillage du langage

Les modifications suivantes doivent être apportées au script bin/verify-exercises file par défaut :

  1. Vérifier que la commande docker est disponible
  2. Récupérer (télécharger) l'image Docker de l'exécuteur de tests
  3. Utiliser docker run pour exécuter l'image Docker de l'exécuteur de tests sur chaque exercice
  4. Utiliser jq pour vérifier que le fichier results.json renvoyé par le conteneur Docker indique que tous les tests ont réussi
  5. Supprimer la fonction unskip_test et l'appel à cette fonction
Note

Le principal avantage de cette approche est qu'elle reproduit au mieux la façon dont les tests sont exécutés en production (sur le site web). Avec cette approche, il est moins probable que des choses qui passaient en CI échouent en production. L'inconvénient de cette approche est qu'elle est généralement plus lente, à cause du téléchargement de l'image Docker et de la surcharge qu'implique Docker.

Exemple

Le fichier bin/verify-exercises file du parcours Unison ajoute la vérification que la commande docker est bien installée :

required_tool docker

Ensuite, il récupère l'image de l'exécuteur de tests du parcours :

docker pull exercism/unison-test-runner

Il modifie ensuite la fonction run_tests pour utiliser docker run afin d'exécuter l'exécuteur de tests sur l'exercice courant (qui se trouve dans le répertoire de travail), suivi d'une commande jq pour vérifier que le statut est le bon :

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
}

Enfin, on doit modifier l'appel à la commande run_tests, car elle nécessite désormais le slug :

run_tests "${slug}"

Implémente le workflow de test

Maintenant que le script verify-exercises est terminé, il est temps de finaliser le workflow test.yml. La façon de procéder dépend de l'option choisie pour l'implémentation du script verify-exercises.

Option 1 : utiliser l'outillage du langage

Si le script verify-exercises utilise directement l'outillage du langage, le workflow de test devra installer :

  • Les dépendances de l'outillage du langage, telles qu'openssh ou un compilateur C/C++.
  • L'outillage du langage, comme un SDK ou un binaire. Si l'installation de l'outillage du langage n'ajoute pas le ou les binaires installés au PATH, pense à l'ajouter au chemin système de GitHub Actions.

Une fois cela fait, verify-exercises devrait fonctionner comme prévu, et tu as mis en place ta CI avec succès !

Pour un exemple, consulte le workflow test.yml du parcours 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

Option 2 : utiliser l'image Docker de l'exécuteur de tests

La deuxième option consiste à vérifier les exercices en exécutant l'exécuteur de tests du parcours. Cette option nécessite que deux conditions soient remplies :

  1. Le parcours dispose d'un exécuteur de tests fonctionnel
  2. Le script verify-exercises utilise l'image Docker de l'exécuteur de tests pour exécuter les tests d'un exercice

Si ton parcours n'a pas encore d'exécuteur de tests, tu peux soit :

  • construire un exécuteur de tests fonctionnel, soit
  • utiliser l'option 1 et te servir directement de l'outillage du langage

Cette approche présente quelques avantages :

  1. Tu n'as besoin d'installer aucune dépendance ni outillage dans le workflow de test (puisqu'ils auront été installés dans l'image Docker)
  2. Cette approche reproduit au mieux la façon dont les tests sont exécutés en production (sur le site web), ce qui réduit le risque de problèmes en production.

Le principal inconvénient est qu'elle est probablement plus lente, à cause du téléchargement de l'image Docker et de la surcharge qu'implique Docker.

Il existe plusieurs façons de récupérer l'image Docker de l'exécuteur de tests :

  1. Télécharger l'image dans le fichier verify-exercises. C'est l'approche adoptée par le parcours Unison.
  2. Télécharger l'image dans le workflow. C'est l'approche adoptée par le parcours Standard ML.
  3. Construire l'image dans le workflow. C'est l'approche adoptée par le parcours 8th.

Alors, quelle approche choisir ? On recommande au moins d'implémenter l'option 1, pour que le script verify-exercises soit autonome. Si ton image est particulièrement volumineuse, il peut être utile d'implémenter aussi l'option 3, qui stockera l'image Docker construite dans le cache de GitHub Actions. Les exécutions suivantes pourront alors simplement lire l'image Docker depuis le cache, au lieu de la télécharger, ce qui peut être meilleur pour les performances (à mesurer pour en être sûr).

Option 3 : exécuter le script de vérification des exercices dans l'image Docker de l'exécuteur de tests

Une troisième option, alternative, est un hybride des deux précédentes. Ici, on utilise aussi l'image Docker de l'exécuteur de tests, sauf que cette fois on exécute le script verify-exercises à l'intérieur de cette image Docker. Pour activer cette option, on doit définir le conteneur du workflow sur l'exécuteur de tests :

container:
  image: exercism/vimscript-test-runner

On peut alors sauter les étapes d'installation des dépendances et de l'outillage (puisqu'elles auront été effectuées dans l'image Docker de l'exécuteur de tests) et passer à l'exécution du script bin/verify-exercises file.

Exemple

Le workflow test.yml du parcours vimscript utilise cette option :

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