Testsuite gegen stille Umgebungsabhängigkeiten härten #8
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Der Anlass
Der erste CI-Lauf, der überhaupt bis
pytestkam (Run 52, 2026-08-30), hat zwei Tests umgeworfen, die auf jeder Entwicklermaschine seit Monaten grün sind:Ursache:
config.default_author()ruftgit config user.namemitcwd=config.ROOTauf. Die Fixture-Wurzel ist kein Repository, also antwortete die globale git-Konfiguration dessen, der die Suite laufen ließ. Im Job-Container alsrootgibt es keine.Beide Tests sind in
1.0.1repariert (sieheCHANGES.md): der erste macht die Fixture-Wurzel jetzt selbst zu einem Repository mit lokalemuser.nameund behauptet den konkreten Namen; der zweite setztWIKI_AUTHOR, weil es dort gar nicht um Autorschaft geht.Was offen bleibt
Der Fix behebt zwei bekannte Fälle. Die Frage ist, wie viele unbekannte es gibt. Die Suite hat 630 Tests und hing an der Umgebung, ohne es zu sagen — sie hat nicht gewarnt, sie war einfach grün, weil die Umgebung zufällig passte. Dass CI das gefunden hat, war Glück im Sinne von „endlich lief sie mal woanders", nicht Systematik.
Ohne diesen Schutz gilt: jeder neue Test kann dieselbe Abhängigkeit einführen, und gemerkt wird es erst, wenn jemand die Suite auf einer fremden Maschine startet — oder gar nicht, weil die verteilte Instanz die Tests nie fährt.
Vorschlag
Bevorzugt: eine autouse-Fixture in
tools/wiki_tools/tests/conftest.py. Dort steht bereitsisolated_trace_dirals autouse-Fixture; eine Schwester daneben ist der natürliche Ort. Sie neutralisiert die Umgebung für jeden Test:HOMEauf eintmp_path-UnterverzeichnisGIT_CONFIG_GLOBALundGIT_CONFIG_SYSTEMauf/dev/nullWIKI_AUTHOR,WIKITOOL_UPDATE_URL,WIKITOOL_UPDATE_TOKEN,WIKITOOL_SESSION_IDlöschenVorteil gegenüber einem zweiten CI-Job: die Härte gilt lokal genauso, ein Test kann die Abhängigkeit gar nicht erst einschleppen, und niemand muss sich an einen Sondermodus erinnern. Tests, die eine Identität brauchen, setzen sie dann explizit — was sie ohnehin tun sollten, siehe die zwei Fixes oben.
Alternative, falls das zu viel auf einmal umwirft: ein zweiter Schritt in
.gitea/workflows/ci.ymlmitDas ist genau der Befehl, mit dem der
1.0.1-Fix lokal verifiziert wurde (630 passed). Schwächer, weil es die Abhängigkeit erst nach dem Push meldet, aber sofort machbar.Akzeptanzkriterien
HOMEund ohne globale/systemweite git-Konfiguration.git configim Fixture-Repo oderWIKI_AUTHOR) — nicht die Fixture-Basis für alle, sonst istdefault_author()s Fallback gar nicht mehr getestet.test_new_source_fails_hard_without_any_authormuss weiterhin genau das prüfen, was sein Name sagt; es patchtdefault_authordirekt und darf durch die Härtung nicht tautologisch werden.WIKI_TRACEdarf nicht global abgeschaltet werden — sie behaupten, dass ein Trace geschrieben wird. Deshalb setzt CIWIKI_TRACE_DIRaußerhalb des Workspace, statt das Tracing zu deaktivieren.Hinweise
tools/pytest.iniist die Konfigurationsdatei,tools/requirements.txtenthält bewusst keinpytest— CI installiert es separat (pip install --quiet pytest).git initin einer Fixture dieinit.defaultBranch-Warnung erzeugen kann, wenn keine Konfiguration existiert. Harmlos, aber verwirrend in der Ausgabe;git init -q -b mainvermeidet es.Kleiner Datenpunkt aus 1.2.0, der die Prämisse dieses Issues bestätigt.
Beim Fix von #12 habe ich zwei neue Tests geschrieben, die
wikitool new sourceüber den CliRunner aufrufen. Lokal grün. Unter der Gegenprobe aus diesem Issue —— fielen beide um, mit exakt der Meldung aus dem Anlass hier:
Das ist der dritte und vierte Fall derselben Abhängigkeit, geschrieben von jemandem, der das Issue vorher gelesen hatte. Die Frage aus dem Issue-Text — „wie viele unbekannte gibt es" — ist damit weniger interessant als die andere: die Suite lädt neue Fälle schneller ein, als jemand sie findet. Ein zweiter CI-Job hätte das erst nach dem Push gemeldet; die bevorzugte autouse-Fixture hätte es beim ersten Lauf verhindert.
Beide Tests setzen jetzt
WIKI_AUTHORselbst, mit Kommentar — konform zum zweiten Akzeptanzkriterium hier. 658 Tests grün in beiden Umgebungen. Das ist ein Pflaster auf zwei Stellen, kein Ersatz für die Fixture.Umgesetzt in 1.7.1, Commit
31c9b81. Die bevorzugte Variante, nicht der zweite CI-Job.Was gebaut wurde
hermetic_environment, autouse-Fixture intools/wiki_tools/tests/conftest.py, direkt nebenisolated_trace_dir. Vor jedem Test:HOMEundXDG_CONFIG_HOMEin dentmp_pathdieses TestsGIT_CONFIG_GLOBALundGIT_CONFIG_SYSTEMauf/dev/nullWIKI_AUTHOR,WIKI_TRACE,WIKI_TRACE_CONTENT,WIKI_TRACE_MAX_CONTENT,WIKITOOL_SESSION_ID,WIKITOOL_UPDATE_URL,WIKITOOL_UPDATE_TOKENÜber den Vorschlag hinaus gelöscht werden auch git-eigene Identitäts- und Ortsvariablen:
GIT_DIR,GIT_WORK_TREE,GIT_AUTHOR_*,GIT_COMMITTER_*,EMAIL.GIT_AUTHOR_NAMEstichtgit config user.name, ist also derselbe Fall wie der im Anlass beschriebene; ein verirrtesGIT_DIRwürde jedes Fixture-Repo auf den Checkout des Entwicklers zeigen lassen.isolated_trace_dirdeklariert jetzthermetic_environmentals Abhängigkeit — nicht für einen Wert, sondern für die Reihenfolge: die eine Fixture löschtWIKI_TRACE, die andere setztWIKI_TRACE_DIR, und diese Reihenfolge sollte ausgesprochen sein statt aus der Deklarationsreihenfolge zu folgen.WIKI_TRACE_DIRbleibt als einzige Variable gesetzt, Tracing wird nirgends global abgeschaltet.Zu den Akzeptanzkriterien
HOMEund ohne git-Konfiguration. Drei Umgebungen, die jetzt exakt übereinstimmen — je 702 passed: die Entwickler-Shell; eine absichtlich vergiftete (WIKI_AUTHOR,WIKI_TRACE=0,WIKITOOL_*undGIT_*auf Müll gesetzt); undenv -i PATH=… HOME=<leer> GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null. Die dritte war vor der Änderung die, die fiel.WIKI_AUTHORin der Fixture-Basis. Die vier bekannten Fälle taten das schon (1.0.1 und 1.2.0); weitere gab es nicht, was hier die interessante Antwort auf „wie viele unbekannte gibt es" ist: zum Zeitpunkt der Härtung genau null, aber der Kommentar oben zeigt, dass das ein Momentanwert ist, kein Zustand.test_new_source_fails_hard_without_any_authorbleibt, was sein Name sagt. Es patchtdefault_authorweiter direkt. Der Patch ist durch die Härtung nicht redundant geworden: er pinnt den Rückgabewert unabhängig davon, was die Umgebung aufgelöst hätte, und hält den Test damit bei der Fehlerbehandlung der CLI statt bei der Umgebung.WIKI_TRACEnicht global abgeschaltet.Neu: die Fixture wird selbst getestet
tools/wiki_tools/tests/test_hermetic_env.py. Eine Fixture, gegen die nichts assertet, kann geschwächt werden oder eine Variable verlieren, und alles bleibt grün bis zum nächsten Lauf auf einer fremden Maschine — also dasselbe Versagen eine Ebene höher. Die Tests behaupten: die geleerten Variablen, das leereHOMEim eigenentmp_path, dassgit config user.nameaußerhalb eines Repos mit lokaler Identität nichts antwortet, und dass Tracing an und umgeleitet bleibt.Dazu alle drei Zweige von
config.default_author()— inklusive desNone-Zweigs, der erst durch die Härtung überhaupt schreibbar wird. Das ist die konkrete Gegenprobe dafür, dass die Fixture etwas tut: ohne sie liefertdefault_author()in einem Nicht-Repository auf der Entwicklermaschine'Torben Nehmer'aus der globalen git-Konfiguration; mit ihrNone. Genau das ist der Grund, warum Kriterium 2 kein Basis-WIKI_AUTHORerlaubt — der Fallback wäre sonst nicht nur ungetestet, sondern untestbar.Dokumentation
instructions/dev/testing-conventions.md, verlinkt aus Schritt 2 desstack-dev-Skills. Was die Fixture neutralisiert (als Tabelle), wie ein Test sich eine Identität herstellt und wonach sich die Wahl richtet, die Regel „neue Tool-Variable → in derselben Änderung nach_WIKITOOL_ENV", und dieenv -i-Gegenprobe vor dem Publish. Bis jetzt zeigte das Skill für genau diesen Fall auf „die Muster der bestehenden Testdateien" — die Muster waren aber das Problem.Der Tests-Step in
.gitea/workflows/ci.ymlbekommt einen Kommentar, warum es beim einen Lauf bleibt: der Container ist keine besondere Umgebung mehr, und ein zweiter gehärteter Lauf würde die Abhängigkeit wieder erst nach dem Push melden. Ohne die Notiz baut ihn irgendwann jemand aus dem alten Grund nach.git init -qim Fixture-Repo vontest_new_source_author_falls_back_to_git_configist jetztgit init -q -b main— der Hinweis aus dem Issue-Text.CI-Bestätigung, und damit die eigentliche Gegenprobe — der Job-Container ist die Umgebung, in der das hier angefangen hat.
Run 79 grün, alle acht Steps.
702 passed in 15.84s— dieselbe Zahl wie in den drei lokalen Umgebungen. Vier Umgebungen, ein Ergebnis; das ist der Zustand, den es vorher nicht gab. Run 80 hatv1.7.1getaggt (31c9b81).Ein Detail aus dem Log, das die Entscheidung gegen den zweiten CI-Job nachträglich stützt:
actions/checkout@v7legt inzwischen selbst eine globale git-Konfiguration an (kopiert/root/.gitconfig, überschreibtHOMEtemporär), und der Tool-Environment-Step schreibtsafe.directoryglobal dazu. Der Container ist also nicht mehr verlässlich „die Maschine ohne globale Konfiguration". Er hatte diese Eigenschaft in Run 52 zufällig — ein Guard, der darauf baut, hätte irgendwann still aufgehört zu greifen, ohne dass jemand es merkt. Die Fixture hängt an nichts dergleichen.Der
dist export-Step meldet 11 Instructions und 5 Skills gegen 14 und 6 im Dev-Baum:instructions/dev/testing-conventions.mdbleibt korrekt außerhalb der Distribution.Schließe das Issue.