Blog ·
Ein Lauf, der sein Scheitern druckte
Der Prüflauf schrieb jeden Exitcode in eine Zusammenfassung und danach trotzdem DONE. Eine Auslieferung ging über eine rote Zeile, die niemand las.
Von Provibr
Vor einer Auslieferung lassen wir ein Skript laufen. Es führt die Web-Testsuite aus, die Suiten für Pakete, Gateway und Werkzeuge, zehn Typprüfungen, den Lint, die Inhaltsprüfung, den Protokoll-Lint und die Protokoll-Codeerzeugung, und es schreibt je Schritt eine Zeile in eine Zusammenfassungsdatei: den Namen des Schritts und seinen Exitcode. Danach schreibt es DONE.
Es schrieb DONE auch dann, wenn einige dieser Exitcodes nicht null waren.
Am siebten September ging eine Auslieferung hinaus, während in der Zusammenfassung eine Zeile bun test packages/gateway/tools exit 1 stand. Ich hatte die Zähler für bestanden und gescheitert von zwei Suiten gelesen und die dritte nicht. Nichts ging kaputt: der scheiternde Test war eine Wache über die Kopfzeilen von Messskripten, das Produkt war also nicht betroffen und der Kunde sah nie etwas. Das Ergebnis war in Ordnung und die Form war falsch, und die Form ist der einzige Teil, den aufzuschreiben sich lohnt.
Die Zähler sind nicht das Tor
Dass das peinlich und nicht bloß Pech ist, liegt daran, dass diese Fehlerart in unseren eigenen Notizen einen Namen hat, und der Name lautet leer grün: eine Prüfung, die nicht an eine Entscheidung angeschlossen ist, wird nicht gelesen, und eine Prüfung, die mit dem Auge gelesen wird, wird so gelesen, wie ein Mensch liest, nämlich indem er nach dem sucht, was er zu sehen erwartet.
Die Zusammenfassung war zum Überfliegen gebaut. Jede Suite druckt ihre eigenen Summen irgendwo weiter oben, die natürliche Bewegung ist also, auf „4515 pass, 0 fail“ zu schauen und sich fertig zu fühlen. Die Exitcodes saßen einen Bildschirm weiter unten, in einer Datei, deren letzte Zeile DONE sagte.
Die Reparatur sind vier Wörter Shell. Nach allen Schritten durchsucht das Skript seine eigene Zusammenfassung nach jedem Exitcode zwischen eins und neun, und findet es einen, hängt es eine Zeile an, die mit FAIL: beginnt. Die zugehörige Regel lautet: ein DONE ohne ein FAIL ist das Tor, und die Zähler sind es nicht. Die zweite Hälfte dieser Regel wiegt schwerer als die erste: wer ein billiges richtiges Signal hat, hört auf, teure Gewohnheiten um ein unzuverlässiges herum zu bauen.
Dazu gehört eine zweite Regel: ein Deploybefehl teilt sich nie eine Shell-Kette mit einer Prüfung, die nur druckt. Wenn die Prüfung die Kette nicht zum Scheitern bringen kann, ist sie Dokumentation.
Was tatsächlich rot war
Die Wache, die scheiterte, gehört zu einer Familie, und sie ist beschreibenswert, weil sie ungewöhnlich billig ist für das, was sie einbringt.
Das Gatewayverzeichnis enthält die Messskripte: Browserdurchläufe, Layoutsonden, Werkzeuge zum Säen und Aufräumen, die Dinge, die eine Runde schreibt, um etwas zu beweisen, und dann zurücklässt. Es sind heute gut fünfhundert. Es sind schlichte Skripte ohne Typprüfung, das Einzige also, was zwischen ihnen und dem Verfall steht, ist, dass zufällig jemand eines ausführt.
Vier Siebe laufen in einer gewöhnlichen Testsuite über alle:
- Jedes Skript lässt sich zerlegen, und zwar mit der Laufzeit, die es tatsächlich ausführt.
- Jeder relative Import löst sich auf eine Datei auf, die es gibt. Das ist die Art von Tod, die ein Zerleger nicht sieht: das Skript ist syntaktisch tadellos und fällt beim Ausführen in der ersten Zeile um.
- Jedes Skript trägt eine Kopfzeile mit Prosa darin. Ohne Erklärung ist ein Werkzeug von vor vier Runden nicht von einem zu unterscheiden, auf das es noch ankommt.
- Jede Kopfzeile nennt die Runde, aus der sie stammt, in einer von genau zwei festen Formen.
Das vierte war rot. Drei Skripte aus der Dokumentationsrunde hatten Kopfzeilen, gute sogar, und keine Rundenmarke. Das ist der ganze Vorfall: eine Namensgepflogenheit, gefangen von einer Wache, die es gibt, damit fünfhundert Skripte lesbar bleiben, sitzend in einer Zusammenfassungszeile, die niemand las.
Warum unbekannt einen Commit-Hash kosten muss
Das vierte Sieb nimmt zwei Formen an und sonst nichts. Entweder nennt die Kopfzeile den Rundennamen mit einem Datum und der Herkunft dieser Zuordnung, oder sie sagt, die Runde sei unbekannt, und trägt den Commit-Hash des ersten Commits der Datei, mit einem Datum.
Der Name darf ausdrücklich nicht das Wort unbekannt sein. Ohne diesen Ausschluss ist „Runde unbekannt, 2026-09-04“ ein völlig gültiger Name mit Datum, und der teure Zweig lässt sich mit einem Wort umgehen. So, wie es steht, kostet das Eingeständnis von Unwissen einen Hash, den jemand nachschlagen muss, und wer ihn nachgeschlagen hat, kennt meist auch die Runde.
Das Datum ist aus einem anderen Grund verpflichtend. Ein bloßer Name ist Prosa und keine Marke: ein Satz wie „die Runde, die auch die Kopfzeilenarbeit berührt hat“ enthält einen Rundennamen und behauptet nichts über die Datei, in der er steht. Mit einem Datum daran wird er zu einer Behauptung über dieses Skript, gemacht an einem Tag, und das ist das Kleinste, worauf man jemanden festnageln kann.
Ein Tor muss auf null stehen
Die naheliegende Frage zu fünfhundert untypisierten Skripten ist, warum wir sie nicht typprüfen. Wir haben das gemessen, und die Antwort ist lehrreich.
Die Konfiguration gibt es. Sie ist ein Instrument und kein Tor, und sie läuft im Prüfskript nicht mit. Gemessen über die Skripte in ihrem damaligen Zustand:
| Konfiguration | Fehler |
|---|---|
| Mit den strengen Einstellungen dieses Arbeitsbereichs | 3180 |
| Ohne streng | 398 |
| Ohne streng, nachdem eine gemeinsame Datei typisierbar gemacht wurde | 159 |
Ein Tor muss auf null stehen, und jene verbleibenden 159 sind größtenteils keine Defekte. Zwei Familien wurden abgelaufen, und beide erwiesen sich als Artefakte davon, untypisierten Code zu typisieren: ein Parameter ohne Vorgabewert, der aus der abgeleiteten Form herausfällt, und eine zerlegende Schleife über ein gemischtes Array, das sich zu einer Vereinigung weitet. Siebenundzwanzig davon sind ein und dieselbe Meldung, über den Import einer TypeScript-Datei aus einem Skript, das unter einem Lader läuft, der damit einwandfrei umgeht.
Was das Instrument eingebracht hat, ist der Schritt von 398 auf 159. Eine Zeile in einem gemeinsamen Browserhelfer, ein Konstruktor, der seine Argumente in einem Zug auf die Instanz legt, hielt 239 Fehler am Leben, denn jeder Zugriff auf eine Eigenschaft dieses Objekts in jedem Skript war nicht auflösbar. Eine Reparatur, 239 Fehler weg. Das ist ein guter Ertrag für eine Messung, die von null verschieden sein darf, und es ist genau die Art Ertrag, die man von einem Tor nicht bekommt, denn ein Tor, das sich nicht erfüllen lässt, wird abgeschaltet.
Die Prüfung, die ihre eigene Laufzeit maß
Bis die Zerlegungsprüfung lief, brauchte es zwei Anläufe, und beide scheiterten auf Arten, die wie Produktfehler aussahen.
Die erste Fassung bat die aktuelle Laufzeit, jede Datei zu prüfen. Unter unserem Testläufer ist die aktuelle Laufzeit nicht die Laufzeit, unter der die Skripte laufen, und ihre Prüfoption bedeutet etwas ganz anderes. Das Ergebnis waren 301 Meldungen über syntaktisch kaputte Dateien, mit Quellzeilen als Fehlertext zitiert. Jede einzelne war in Ordnung.
Die zweite Fassung nannte die richtige Laufzeit ausdrücklich und scheiterte dennoch, anders: der Testläufer erntet Kindprozesse ein, die zu einem Test gehören, und etwa ab der Hälfte der Liste kam jeder Aufruf mit einem Nullstatus zurück. Von einem Signal getötet, kein Zerlegungsfehler. Eine Wache, die zufällig rot wird, ist keine Wache.
Die Fassung, die funktioniert, startet einen Kindprozess, der die Dateien selbst prüft, acht auf einmal, in etwa neun Zehntelsekunden, als das gemessen wurde. Was behauptet wird, ist unverändert: ob die Laufzeit, die diese Skripte ausführt, sie annimmt.
Auch die Parallelität ist keine Feinheit. Seriell ausgeführt überschritt die Prüfung die Zeitgrenze je Test, und eine Wache, die in eine Zeitüberschreitung läuft, liest sich als kaputte Wache und nicht als langsame.
Die untere Schranke, nach der niemand fragt
In dieser Datei gibt es einen fünften Test, der nur behauptet, dass es mehr als fünfzig Skripte zu prüfen gibt.
Ein Verzeichnisläufer, der nichts findet, macht jede Behauptung danach trivial wahr. Benennen Sie das Verzeichnis um, ändern Sie den Endungsfilter, machen Sie den Läufer kaputt, und vier grüne Siebe berichten über eine leere Liste. Die untere Schranke liegt mit Absicht weit unter der wirklichen Zahl, denn es geht nicht darum, Aufräumen zu verhindern, sondern darum, den Unterschied zwischen „nichts ist falsch“ und „nichts wurde untersucht“ sichtbar zu machen.
Dieselbe Testdatei ist ehrlich über ihre Grenzen, und diese Ehrlichkeit gehört zum Entwurf. Keines der vier Siebe beweist, dass ein Skript noch misst, was es behauptet. Ein Selektor, den es nicht mehr gibt, eine Route, die umgezogen ist, eine Spalte, die entfernt wurde: alle vier Prüfungen bestehen. Es ist ein Boden und keine Typprüfung, und das in der Datei zu sagen hält den Nächsten davon ab, einen grünen Lauf für mehr zu halten, als er ist.
Was wir aufschreiben würden
- Eine Prüfung, die nur druckt, ist kein Tor. Sie muss das, was danach kommt, zum Scheitern bringen können.
- Schreiben Sie die Fehlerzeile, nicht nur die Exitcodes. Das Tor ist ein Wort, nach dem jemand greppen kann, an einer festen Stelle, und keine Zahl, die er bemerken muss.
- Hängen Sie nie ein Deploy hinter eine Prüfung, die es nicht aufhalten kann. Die Kette ist das Versprechen.
- Zähler sind eine Zusammenfassung, kein Urteil. Menschen lesen die Zahl, die sie zu sehen erwarten.
- Ein Tor muss auf null stehen können. Kann eine Messung das nicht, behalten Sie sie als Instrument und sagen Sie es, sonst wird sie abgeschaltet und danach übersehen.
- Machen Sie den billigen Ausweg aus einer Regel teuer. Ist das Eingeständnis von Unwissen ein Wort, schreiben alle dieses Wort.
- Eine Prüfung, die zufällig scheitert, ist keine Prüfung. Jagen Sie das Flackern, bevor Sie dem Signal trauen.
- Behaupten Sie eine untere Schranke für das, was der Läufer gefunden hat. Vier grüne Prüfungen über einer leeren Liste sehen genauso aus wie vier grüne Prüfungen.
Zur Klarheit: die Fehlerzeile steht in dem Skript, das im Repository liegt, und sie ist seither über Merges gelaufen, die grün waren. Sie ist noch nicht durch einen roten Lauf im täglichen Gebrauch bewiesen, sondern nur durch die Zusammenfassung, gegen die sie geschrieben wurde. Die Typisierungszahlen stammen aus einer Messung an einem Tag, und die Dateizahl bewegt sich jede Nacht.
Aus der Plattformarbeit an Provibr. Eine weitere Wache, die ihre eigene Null beweisen musste, steht in Barrierefreiheit: eine Ausnahme, die sich misst, und ein Feld, das still eine Absicht statt einer Tatsache hielt, steht in ein Datum in der Zukunft ist kein Plan.