Engineering-Guides

Xcode-Regressionen mit git bisect automatisch finden

Xcode-Regressionen mit git bisect automatisch finden

Ein bestimmter UI-Test schlägt seit letzter Woche zuverlässig fehl. Inzwischen wurden jedoch Dutzende Commits zusammengeführt. Jeden Commit einzeln auszuchecken, Xcode zu öffnen und den Test auszuführen, ist nicht nur langsam, sondern birgt auch das Risiko, Unterschiede in der Umgebung zu übersehen. Effizienter ist es, die Frage „Hat dieser Commit den gesuchten Fehler eingeführt?“ als wiederholbar ausführbaren Befehl zu formulieren und die binäre Suche anschließend git bisect zu überlassen. Bei 64 möglichen Commits sind theoretisch nur etwa 6 Prüfrunden erforderlich. Die eigentliche Herausforderung liegt nicht im Suchalgorithmus, sondern darin, in jeder Runde ein belastbares Ergebnis zu erhalten.

Die Regression als maschinell prüfbares Ergebnis definieren

Vor dem Start werden mindestens zwei Grenzen benötigt: ein nachweislich funktionierender Commit und ein nachweislich fehlerhafter Commit. Eine Beschreibung wie „Die Seite funktioniert nicht richtig“ ist zu ungenau. Grenzen Sie die Regression auf eine einzelne Beobachtung ein, etwa eine fehlgeschlagene XCTest-Assertion, ein Scheme, das sich nicht archivieren lässt, oder eine falsche Ausgabe bei einer festgelegten Eingabe.

Für die Prüfung müssen außerdem die folgenden Variablen festgelegt werden:

  • Scheme, Configuration und Testziel
  • Auswahlpfad von Xcode und Lockdateien der Abhängigkeiten
  • Simulatormodell, Systemversion, Sprache und Zeitzone
  • Testdaten, Netzwerkabhängigkeiten und ausführendes Benutzerkonto
  • Ein eindeutiger Nachweis für die Einstufung als funktionierend oder fehlerhaft

git bisect verstärkt Fehler des Prüfskripts konsequent. Wird ein sporadischer Fehler als fehlerhafter Commit gewertet, kann das Ergebnis vollkommen falsch sein, selbst wenn die anschließende Suche ohne weitere Probleme endet.

Führen Sie denselben Befehl zunächst jeweils zweimal manuell auf dem funktionierenden und dem fehlerhaften Commit aus. Lassen sich die Ergebnisse nicht stabil reproduzieren, muss zuerst die Testisolierung verbessert werden, statt sofort mit der binären Suche zu beginnen.

Die Ausführungsumgebung für jeden Commit isolieren

Bei der Ausführung auf einem Cloud-Mac von G-Mini empfiehlt sich eine eigene Arbeitskopie. Verwenden Sie nicht das Verzeichnis, in dem gerade entwickelt wird. Während der binären Suche wechselt Git häufig zwischen Commits. Nicht versionierte Dateien, automatisch erzeugte Konfigurationen und gemeinsam genutzte Caches können die Prüfung verfälschen.

Zu isolierender Bereich Empfohlene Vorgehensweise Grund
Git-Arbeitsverzeichnis Separaten clone oder worktree verwenden Verhindert das Überschreiben laufender Änderungen
DerivedData Verzeichnisse nach Commit-Hash trennen Verhindert die Wiederverwendung alter Artefakte über Commit-Grenzen hinweg
Simulator UDID fest vorgeben Verhindert Abweichungen durch die automatische destination-Auswahl
Ergebnis-Bundle Für jede Runde separat speichern Erleichtert die Überprüfung des ersten fehlerhaften Commits
Abhängigkeiten Lockdateien beibehalten und implizite Updates deaktivieren Verhindert Änderungen bei der Versionsauflösung

Führen Sie vor dem Start git status --porcelain aus. Die Ausgabe muss leer sein. Der Simulator sollte vorab erstellt und gestartet werden; anschließend wird seine UDID in einer Umgebungsvariable gespeichert. Das Prüfskript darf nicht anhand eines Namens kurzfristig irgendein verfügbares Gerät auswählen. Gleichnamige Geräte und Systemupdates können dazu führen, dass tatsächlich ein anderes Ziel verwendet wird.

Ein Xcode-Prüfskript mit drei Zuständen schreiben

git bisect run unterscheidet nicht nur zwischen Erfolg und Fehler. Der Exitcode 0 bedeutet, dass der Commit funktioniert. Werte von 1 bis 127 kennzeichnen einen fehlerhaften Commit, während 125 signalisiert, dass der aktuelle Commit nicht bewertet werden kann und übersprungen werden soll. Dieser dritte Zustand ist insbesondere bei älteren Projekten wichtig: Ein historischer Commit lässt sich mit den heutigen Abhängigkeiten möglicherweise nicht mehr kompilieren, muss aber deshalb nicht die untersuchte Regression enthalten.

Das folgende Skript prüft zunächst, ob das Projekt gebaut werden kann, und führt anschließend ausschließlich den relevanten Test aus. Schlägt bereits der grundlegende Build fehl, wird der Commit als nicht bewertbar eingestuft. Nur ein eindeutig fehlgeschlagener Test gilt als fehlerhaft.

#!/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

Die Klassifizierung an den Fehlertyp anpassen

Bei der Untersuchung einer Compile-Regression muss ein fehlgeschlagener Build 1 statt 125 zurückgeben. Geht es dagegen um eine Verhaltensregression, müssen grundlegende Umgebungsprobleme wie fehlgeschlagene Downloads von Abhängigkeiten, Störungen des Simulatordienstes oder Schreibfehler auf dem Datenträger übersprungen werden. Nicht jeder von null abweichende Exitcode von xcodebuild darf pauschal als gesuchte Regression interpretiert werden.

Die binäre Suche starten und den ersten fehlerhaften Commit überprüfen

Sobald das Skript vorbereitet ist, dokumentieren Sie den aktuellen Branch und den Zustand des Arbeitsverzeichnisses. Führen Sie danach die folgenden Befehle aus:

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

Nach Abschluss der Suche nennt Git den ersten fehlerhaften Commit. Schließen Sie die Untersuchung an diesem Punkt noch nicht ab. Führen Sie das Skript stattdessen sowohl auf diesem Commit als auch auf seinem übergeordneten Commit manuell aus und prüfen Sie die gespeicherten Build-Protokolle sowie die .xcresult-Datei. Untersuchen Sie außerdem die Änderungen des Commits. Sie müssen das beobachtete Verhalten tatsächlich erklären können, statt lediglich zufällig einen anderen Fehler auszulösen.

Führen Sie anschließend git bisect reset aus, um zum ursprünglichen Branch zurückzukehren. Falls während der binären Suche große Mengen an DerivedData entstanden sind, können sie nach Abschluss der Überprüfung gesammelt entfernt werden. Löschen Sie die Ergebnis-Bundles während der Untersuchung nicht zu früh, da sonst die Belege für den Prüfverlauf verloren gehen.

Sporadische Tests und Brüche im Projektverlauf behandeln

Mehrheitsentscheidung bei sporadischen Tests verwenden

Ist eine einzelne Ausführung nicht zuverlässig, kann das Prüfskript den Test dreimal hintereinander ausführen. Es gibt nur dann 1 zurück, wenn derselbe gesuchte Fehler mindestens zweimal auftritt. Widersprechen sich die drei Ergebnisse, wird 125 zurückgegeben. Dadurch verlängert sich zwar die Laufzeit, doch das ist günstiger, als die Untersuchung mit einem falsch identifizierten Commit fortzusetzen. Vor jeder Wiederholung müssen die Testdaten weiterhin zurückgesetzt werden, damit der Zustand eines vorherigen Durchlaufs den nächsten nicht beeinflusst.

Den Suchbereich bei zu vielen übersprungenen Commits verkleinern

Bei zu vielen Ergebnissen mit 125 kann Git möglicherweise keinen eindeutigen Commit bestimmen. Häufig liegt das daran, dass sich das Projektformat, die Verwaltung der Abhängigkeiten oder der Testname im Laufe der Historie geändert hat. Begrenzen Sie den Suchbereich in diesem Fall auf den Zeitraum nach der Migration oder ergänzen Sie einen Kompatibilitätszweig für die ältere Verzeichnisstruktur. Das Skript darf den zu testenden Quellcode jedoch nicht automatisch verändern.

Abschließend sollten der funktionierende Commit, der fehlerhafte Commit, der erste fehlerhafte Commit, die Version des Prüfskripts, die Simulator-UDID, die Xcode-Version und der Pfad zum Ergebnis-Bundle dokumentiert werden. Nur so kann eine andere Person die Untersuchung reproduzieren und denselben Ablauf nach Fertigstellung des korrigierenden Commits unverändert als Regressionstest verwenden.

Häufig gestellte Fragen

Welche Rückgabecodes sollte ein git-bisect-Prüfskript verwenden?

Ein guter Commit liefert 0, eine bestätigte Regression 1 und ein nicht zuverlässig bewertbarer Commit 125. Infrastruktur- und Simulatorfehler sollten in der Regel übersprungen werden.

Eignet sich git bisect auch für sporadisch fehlschlagende Xcode-Tests?

Erst nach Stabilisierung der Umgebung. Simulator, Sprache, Zeitzone und Testdaten werden fixiert; anschließend läuft der Test pro Commit mehrfach und gilt nur ab einem definierten Schwellenwert als fehlerhaft.

Exklusiver physischer Knoten

Cloud-Mac für die nächste Build-Warteschlange vorbereiten

Vergleichen Sie zwei M4-Konfigurationen an fünf Standorten und wählen Sie die passende Tages-, Wochen-, Monats- oder Quartalsmiete für Ihren Arbeitszyklus.

Cloud-Mac-Tarif auswählen