Aller au contenu

Contrôles obligatoires de CI

Vos contrôles obligatoires ont réussi. Vos tests n’ont pas été exécutés.

Comment des tâches de test ignorées sous condition peuvent satisfaire la protection de branche et comment des tâches de contrôle toujours exécutées empêchent les fusions non testées.

4 min de lecturePar

Une optimisation utile a créé une faille dans la politique

Les tâches unitaires et E2E ne démarraient qu’après l’ajout d’une étiquette run-tests. Nous pouvions ainsi différer les suites coûteuses pendant la préparation d’une pull request, mais chaque fusion devait tout de même les exécuter. La protection de branche n’imposait pas cette intention.

Lorsque la condition de la tâche était évaluée comme fausse, le processus de test était ignoré. GitHub accordait tout de même à la tâche ignorée une conclusion réussie pour les contrôles obligatoires. La pull request pouvait donc paraître verte sans qu’aucune commande de test ait été exécutée.

Conserver le processus de test conditionnel

J’ai conservé l’étiquette, car elle était utile pendant la revue. L’erreur consistait à rendre obligatoire le processus de test conditionnel lui-même. GitHub pouvait satisfaire ce contrôle lorsque ce processus était ignoré.

unit-tests:
  if: |
    github.event_name == 'workflow_dispatch' ||
    (github.event.action == 'labeled' && github.event.label.name == 'run-tests') ||
    (github.event.action == 'synchronize' &&
      contains(github.event.pull_request.labels.*.name, 'run-tests'))
  steps:
    - run: pnpm test:unit:coverage

Le processus de test décide s’il doit s’exécuter maintenant. Une tâche de contrôle distincte décide si la pull request peut être fusionnée. Mélanger ces deux décisions a créé le faux état vert.

Séparer le processus de test de la politique

J’ai ajouté une petite tâche de contrôle après chaque processus de test. Elle déclare ce processus dans needs, s’exécute avec if: always() et ne réussit que si son résultat est success. Une suite ignorée, annulée ou en échec bloque désormais la fusion avec un message utile.

ci-gate:
  name: CI Gate (Unit)
  runs-on: ubuntu-latest
  needs: [unit-tests]
  if: always()
  steps:
    - name: Check test results
      run: |
        result="${{ needs.unit-tests.result }}"
        if [[ "$result" != "success" ]]; then
          echo "::error::Unit tests did not pass (result: $result)"
          exit 1
        fi
        echo "Unit tests passed"
Résultat en amont  Contrôle obligatoire
success             réussite
failure             échec
cancelled           échec
skipped             échec avec un message exploitable indiquant l’étiquette à ajouter

Conserver les processus de test coûteux sous condition

Les tâches de contrôle précèdent de véritables suites de tests. La couverture unitaire comprenait 73 fichiers Jest, des seuils obligatoires et une augmentation automatique de la couverture. Playwright s’exécutait sur Chromium et WebKit avec quatre processus, deux nouvelles tentatives en CI et un état authentifié partagé.

Les tests unitaires et E2E ont chacun reçu un nom de contrôle stable. La protection de branche peut continuer à exiger ces noms même si l’implémentation des processus change ensuite.

Exiger le contrôle, pas le processus de test

La protection de branche a été modifiée pour exiger CI Gate (Unit) et CI Gate (E2E), et non les processus de test conditionnels. Le contrat de fusion est ainsi explicite : une suite attendue doit réussir ou produire un échec visible expliquant pourquoi elle ne s’est pas exécutée.

  • Conservez le caractère conditionnel des traitements coûteux lorsque le dépôt fournit une raison claire de le faire.
  • Regroupez toujours chaque résultat final de processus qui intervient dans la politique de fusion.
  • Exigez le nom stable de l’agrégateur dans la protection de branche.
  • Transformez une validation ignorée en action claire au lieu d’un succès silencieux.