Guides d’ingénierie

Automatiser git bisect pour isoler une régression Xcode

Automatiser git bisect pour isoler une régression Xcode

Un test d’interface échoue systématiquement depuis la semaine dernière, mais des dizaines de commits ont été intégrés récemment. Les examiner un par un, ouvrir Xcode et relancer le test serait à la fois lent et susceptible de masquer des différences d’environnement. Une méthode plus efficace consiste à transformer la question « ce commit introduit-il le dysfonctionnement recherché ? » en une commande reproductible, puis à confier la recherche dichotomique à git bisect. Pour 64 commits candidats, environ 6 itérations suffisent en théorie. La véritable difficulté ne réside pas dans l’algorithme, mais dans la fiabilité du verdict obtenu à chaque étape.

Définir d’abord la régression sous une forme vérifiable par une machine

Avant de commencer, identifiez au moins deux bornes : un commit dont le bon fonctionnement est confirmé et un commit dont le comportement anormal est avéré. Ne vous contentez pas d’une description vague comme « la page ne fonctionne pas ». Réduisez la régression à une seule observation, par exemple l’échec d’une assertion XCTest, l’impossibilité d’archiver un Scheme précis ou une sortie incorrecte pour une entrée fixe.

Les variables suivantes doivent toutes être figées dans les critères de décision :

  • Le Scheme, la Configuration et la cible de test
  • Le chemin de sélection de Xcode et les fichiers de verrouillage des dépendances
  • Le modèle du simulateur, la version du système, la langue et le fuseau horaire
  • Les données de test, les dépendances réseau et le compte d’exécution
  • L’unique élément de preuve permettant de conclure à un état normal ou anormal

git bisect amplifie fidèlement les erreurs du mécanisme de décision. Si un échec sporadique est classé comme un mauvais commit, la recherche peut se terminer sans incident tout en produisant une conclusion entièrement erronée.

Commencez par exécuter manuellement deux fois la même commande sur le bon commit, puis deux fois sur le mauvais. Si les résultats ne sont pas reproductibles, corrigez d’abord l’isolation des tests au lieu de lancer immédiatement la recherche dichotomique.

Isoler l’environnement d’exécution de chaque commit candidat

Sur un Mac cloud G-Mini, utilisez de préférence une copie de travail dédiée plutôt que le répertoire dans lequel un développeur travaille actuellement. La recherche dichotomique change fréquemment de commit ; des fichiers non suivis, des configurations générées automatiquement ou des caches partagés peuvent donc fausser le verdict.

Élément à isoler Pratique recommandée Raison
Espace de travail Git Utiliser un clone ou un worktree distinct Éviter d’écraser les modifications quotidiennes
DerivedData Créer un répertoire par hash de commit Empêcher la réutilisation d’anciens artefacts entre commits
Simulateur Fixer l’UDID Éviter que la sélection automatique de la destination ne dérive
Bundle de résultats Enregistrer chaque itération séparément Faciliter la vérification du premier commit défectueux
Dépendances Conserver les fichiers de verrouillage et désactiver les mises à jour implicites Éviter toute variation dans la résolution des versions

Avant de commencer, exécutez git status --porcelain et vérifiez que la sortie est vide. Le simulateur doit être créé et démarré à l’avance, puis son UDID enregistré dans une variable d’environnement. Dans le script de décision, ne sélectionnez pas à la volée « n’importe quel appareil disponible » à partir de son nom : des appareils portant le même nom ou une mise à niveau du système peuvent modifier la destination réellement utilisée.

Écrire un script Xcode à trois états pour établir le verdict

git bisect run ne se limite pas aux états de réussite et d’échec. Le code de sortie 0 indique un bon commit, les codes 1 à 127 un mauvais commit, tandis que 125 signifie que le commit actuel ne peut pas être évalué et doit être ignoré. Ce troisième état est particulièrement important pour les projets anciens : certains vieux commits peuvent ne plus compiler avec les dépendances actuelles sans pour autant contenir la régression étudiée.

Le script ci-dessous vérifie d’abord que le projet peut être compilé, puis exécute uniquement le test ciblé. Un échec de compilation de base est classé comme indéterminé ; seul un échec explicite du test est considéré comme anormal.

#!/bin/zsh
set -u

: "${DESTINATION_ID:?Set DESTINATION_ID first}"

sha="$(git rev-parse --short HEAD)"
root="${TMPDIR:-/tmp}/xcode-bisect"
derived="$root/derived-$sha"
result="$root/result-$sha.xcresult"
log="$root/test-$sha.log"

mkdir -p "$root"
rm -rf "$result"

xcodebuild \
  -project App.xcodeproj \
  -scheme App \
  -configuration Debug \
  -destination "platform=iOS Simulator,id=$DESTINATION_ID" \
  -derivedDataPath "$derived" \
  build >"$root/build-$sha.log" 2>&1

if [[ $? -ne 0 ]]; then
  exit 125
fi

xcodebuild \
  -project App.xcodeproj \
  -scheme App \
  -destination "platform=iOS Simulator,id=$DESTINATION_ID" \
  -derivedDataPath "$derived" \
  -resultBundlePath "$result" \
  -only-testing:AppTests/CheckoutReducerTests/testExpiredCart \
  test >"$log" 2>&1

status=$?

if [[ $status -eq 0 ]]; then
  exit 0
fi

if grep -q "TEST FAILED" "$log"; then
  exit 1
fi

exit 125

Adapter la classification au type de défaillance

Si vous recherchez une régression de compilation, un échec de build doit renvoyer 1 et non 125. Si vous recherchez une régression de comportement, les problèmes d’infrastructure tels qu’un échec de téléchargement des dépendances, une panne du service de simulateur ou une erreur d’écriture sur le disque doivent être ignorés. N’interprétez pas systématiquement tous les codes de sortie non nuls de xcodebuild comme la régression ciblée.

Lancer la recherche dichotomique et vérifier le premier mauvais commit

Une fois le script prêt, consignez la branche actuelle et l’état de l’espace de travail, puis exécutez :

chmod +x ./scripts/bisect-xcode.sh
export DESTINATION_ID="固定的模拟器UDID"

git bisect start
git bisect bad BAD_COMMIT
git bisect good GOOD_COMMIT
git bisect run ./scripts/bisect-xcode.sh

À la fin de la recherche, Git indique le premier commit défectueux. Ne clôturez pas immédiatement le problème : exécutez manuellement le script sur ce commit et sur son parent, puis examinez les journaux de compilation et les fichiers .xcresult enregistrés. Consultez également les différences introduites par le commit pour confirmer qu’elles expliquent bien le comportement observé, au lieu d’avoir simplement déclenché un autre échec.

Une fois l’analyse terminée, exécutez git bisect reset pour revenir à la branche d’origine. Si la recherche dichotomique a produit beaucoup de données DerivedData, supprimez-les en une seule fois après la vérification. Ne supprimez pas prématurément les bundles de résultats pendant l’enquête, sous peine de perdre les preuves ayant servi aux décisions.

Gérer les tests intermittents et les ruptures historiques

Appliquer un verdict majoritaire aux tests intermittents

Si une seule exécution n’est pas fiable, configurez le mécanisme de décision pour lancer le test trois fois et ne renvoyer 1 que si le même échec ciblé se produit au moins deux fois. Si les trois résultats se contredisent, renvoyez 125. Cette méthode allonge la durée d’exécution, mais elle coûte moins cher que de poursuivre l’enquête à partir du mauvais commit. Avant chaque nouvelle exécution, réinitialisez toujours les données de test afin que l’état d’une itération n’influence pas la suivante.

Réduire les bornes lorsque trop de commits sont ignorés

Un grand nombre de codes 125 peut empêcher Git d’identifier un commit unique. Cela se produit souvent lorsque le format du projet, le système de gestion des dépendances ou le nom du test a changé au cours de l’historique. Dans ce cas, limitez la plage de recherche à la période postérieure à la migration ou ajoutez au script des branches de compatibilité pour l’ancienne arborescence. En revanche, le script ne doit jamais modifier automatiquement le code source testé.

Enfin, conservez le bon commit, le mauvais commit, le premier commit défectueux, la version du script de décision, l’UDID du simulateur, la version de Xcode et le chemin des bundles de résultats. Un autre ingénieur pourra ainsi reproduire le diagnostic, et le même dispositif pourra servir tel quel à valider la régression après l’intégration du correctif.

Questions fréquentes

Quels codes de sortie utiliser dans un script destiné à git bisect ?

Retournez 0 pour un commit valide, 1 lorsque la régression recherchée est confirmée et 125 lorsque le commit ne peut pas être classé de manière fiable. Les erreurs d’infrastructure doivent être ignorées.

Peut-on utiliser git bisect avec un test Xcode instable ?

Il faut d’abord fixer le simulateur, la langue, le fuseau horaire et les données de test. Exécutez ensuite plusieurs essais par commit et ne classez le commit comme fautif qu’au-delà d’un seuil défini.

Nœud physique dédié

Préparez un Mac dans le cloud pour votre prochaine file de build

Comparez deux configurations M4 sur cinq nœuds et choisissez une location à la journée, à la semaine, au mois ou au trimestre selon votre rythme de travail.

Choisir une offre de Mac dans le cloud