Coverage-Reporting: erst messen, dann eine Schwelle setzen #10
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?
Ergebnis
Erledigt mit Stack 4.7.2 (
e00eae0). Die Suite hat einen Boden:fail_under = 85intools/.coveragerc, gesetzt gegen gemessene 87,0 % — und gesetzt erst, nachdem die Zahlbeobachtet war. Genau die Reihenfolge, für die dieses Issue existiert hat.
Drei Schritte, alle drei geliefert:
a243a4a, Stack 1.8.1) — CI installiertpytest-covnebenpytest, misstchemenu/ohnechemenu/tests/, lädt den Bericht als Artefakt hoch.e00eae0, Stack 4.7.2) —fail_under = 85, in eigenem Commit, mitder beobachteten Zahl als Begründung.
Die Beobachtung, auf der die Schwelle steht
Verbindlich ist jeweils die von CI gemessene Zahl, nie eine lokale: die lokale misst einen Baum,
der einen Commit hinter dem veröffentlichten liegt — beim ersten Mal hat genau das 5100/729
statt 5105/730 ergeben.
Das Paar trägt die Schwelle, nicht der Einzelwert. Zwischen den beiden Messungen ist der
gemessene Code um ein Viertel gewachsen und die Suite um ein Drittel, und die Quote hat sich um
einen Zehntelpunkt bewegt. Eine Untergrenze auf dieser Grundlage ist etwas anderes als eine
gegriffene Zahl.
Entschieden: 85, nicht 87
Der ursprüngliche Auslöser für Schritt 3 war ein Datum — 2026-09-14, gesetzt am 31.08. als
Schätzung, wie lange es dauert, bis genug Beobachtung zusammenkommt. Die Schätzung ist überholt
worden, weil der Stack schneller lief als sie: 1.8.1 → 4.7.1 in vier Tagen, 38 grüne Läufe,
+27 % Statements. Das Datum war ein Proxy für „mehrere CI-Läufe über echten Commits", und der
Proxy war am 2026-09-04 eingelöst. Auf dem Datum zu beharren, wäre die Umkehrung des Fehlers
gewesen, den dieses Issue verhindern soll: dort eine Zahl ohne Beobachtung dahinter, hier ein
Warten ohne Grund dahinter.
Der Wert ist 85 und nicht der gemessene 87er, und das ist keine Bequemlichkeit. Der
Coverage-Bericht unterscheidet drei Sorten ungedeckter Zeilen (siehe unten), und nur eine
bedeutet Arbeit. Ein neuer dünner Typer-Wrapper senkt den Gesamtwert, ohne dass irgendetwas
schlechter geworden wäre — seine Logik liegt daneben und ist dort getestet. Eine Schwelle auf dem
gemessenen Wert würde an genau diesem Commit rot, und eine Schwelle, die aus einem Nicht-Grund
rot wird, wird gesenkt statt verdient. Die zwei Punkte sind der Platz, den die Taxonomie
verlangt.
fail_understeht intools/.coveragercund nicht als--cov-fail-underim CI-Schritt: sositzt die Zahl neben der Begründung, die sie erzeugt hat, und gilt für jeden
--cov-Lauf stattnur für den einen, den CI schreibt.
Die Modulverteilung — der bleibende Teil des Befunds
Der Gesamtwert ist die uninteressanteste Größe. Drei Sorten sind zu unterscheiden, bevor daraus
Arbeit wird:
eval_cmd.py(36 %),types_cmd.py(40 %),search.py(49 %),cli.py(54 %),links_cmd.py(61 %). NiedrigeZahlen sind hier das Zeichen für einen guten Schnitt.
search.pyist der Musterfall: ungedecktist ausschließlich der Kommandokörper (Zeilen 117–176), während die Backends unter
chemenu/search/, die die Arbeit tun, zwischen 91 % und 98 % liegen. Wer nur den Modulwertliest, sieht hier Arbeit, wo keine ist.
Naht getestet wird:
version.pysfetch_latest()nimmt dafür einenfetcher, die echteNetzzeile bleibt absichtlich ungedeckt (
version.pygesamt 86,5 %).provenance_cmd.py(44 %),migrate_cmd.py(65 %),type_resolver.py(79 %). Das ist die einzige der drei Listen, die sich nicht bewegt hat, während alles um sie
herum wuchs —
provenance_cmd.pysteht bei unveränderten 44,1 %,migrate_cmd.pyist von71 % gefallen, weil das Modul gewachsen ist und die neuen Zeilen ungetestet ankamen.
Die Schwelle friert das ein, sie schließt es nicht. Das ist Gitea #51, mit der Liste, den
Zeilennummern und der Begründung, warum ausgerechnet diese drei Module zählen.
Der Artefakt-Abruf: geklärt
Der Bericht ist abrufbar, bestätigt am 2026-09-04 für
coverage-163über die Run-Seite.Der Widerspruch zwischen Kommentar 318 und 342 ist damit zugunsten von 318 aufgelöst und die
Ursache benannt statt vermutet: von den beiden dort genannten Erklärungen trifft die erste zu.
upload-artifact@v3legt über die ältere Artifact-API ab, die die Actions-Artefakt-Endpunktenicht lesen — daher
total_count: 0bei einem Artefakt, das tatsächlich da ist. Reproduziertüber 76 Läufe, auch für Lauf 163 einzeln.
Kein Folge-Issue. v3 funktioniert hier, nur die Listen-API sieht es nicht. Der Befund steht
an beiden Stellen, an denen ihn jemand sucht —
EVALS.mdund der Kommentar an derCoverage report-Stufe in.gitea/workflows/ci.yml—, jeweils mit dem Satz, dass eine leereListe kein fehlgeschlagener Upload ist. Ohne den wäre die naheliegende „Korrektur" der Wechsel
auf v4, der auf dieser Instanz eingeschränkt ist.
Nebenbefund, mitbehoben
dist exporthat die Coverage-Ausgabe mit ausgeliefert: der erste Export nach der Messung trug227 statt 162 Dateien, darunter den kompletten
htmlcov/-Baum — eine Messung dieses Repos, infremden Instanzen.
TOOLS_EXCLUDE_DIRSprunet nur Verzeichnisse, aber.coverage,coverage.xmlund unter Parallelläufen.coverage.<host>.<pid>liegen als Dateien neben demCode.
_copy_treenimmt seither zusätzlich ein Dateinamen-Prädikat. Ohne das hätte derCI-Export in jedem Lauf die Coverage des Laufs mitverpackt, weil „Tests" vor „Export the
distribution" läuft.
Akzeptanzkriterien
coverage-163, über dieRun-Seite; die Listen-API sieht v3-Artefakte nicht, siehe oben.
EVALS.md§ „How much of the stack thesuite reaches", nicht nur hier im Issue.
Ergebnis oben.
fail_under = 85, eigener Commit, PATCH-Bump auf4.7.2 (Version-Gate greift bei
^tools/).Verifiziert
pytest -q --covlokal: 975 passed, „Required test coverage of 85.0% reached. Totalcoverage: 87.01 %" — die Untergrenze ist aktiv und wird eingehalten, nicht nur konfiguriert.
tools/wikitool docs verifyundtools/wikitool instructions verifygrün, vor und nachversion release.e00eae0grün, alle neun Schritte — der erste Lauf mit aktiverUntergrenze.
docs/why-gates-are-code.md§ „Numbers that come frommeasurement, not intuition" führt die Untergrenze jetzt als zweiten Fall neben der
Iterationsgrenze (
b4e4501). Die Grenze war erst falsch und wurde dann gemessen; der Bodenwurde absichtlich zurückgehalten, bis die Zahl existierte — dasselbe Argument, vorwärts statt
rückwärts gelaufen.
Vorgeschichte
Angelegt am 2026-08-30, als 630 Tests in CI liefen und niemand wusste, wie viel vom Stack sie
abdecken. Der Verdacht war nicht theoretisch: Lauf 52 hatte zwei Tests gefunden, die grün waren,
weil die Umgebung zufällig passte. Was gar nicht ausgeführt wird, fällt noch leichter durch.
Der Zuwachs von 630 auf 975 Tests ist nicht nur Fläche. 2.1.1 hat eine Umgebungsabhängigkeit
geschlossen, die genau hierher gehört:
test_legacy_source_pages_flags_url_and_directorywargrün, weil der Checkout zufällig ein
raw/documents/besaß — die Korpus-Bereinigung leerte es,Git verfolgt keine leeren Verzeichnisse, und der Test wurde in CI rot, während er lokal grün
blieb. Derselbe Befund wie in Lauf 52, dritter Fall. Die systematische Härtung dagegen ist die
autouse-Fixture
hermetic_environment(#8); dass ihre Env-Liste von Hand gepflegt wird, ist #23.Pfade in diesem Body sind am 2026-09-04 von
wiki_tools/aufchemenu/nachgezogen (Rename in2.0.0, #3). Der Beleg für
upload-artifact@v3nannte bis dahin einen privaten Hostnamen; er istneutral als
torben/gitea-mcp@ci-build,ci-build.yaml, Läufe 42–45 formuliert. Teilerledigungvon #29.
Schritt 1 umgesetzt in
a243a4a, Stack 1.8.1. Das Issue bleibt offen — Schritt 3 (dieSchwelle) ist ausdrücklich ein eigener, späterer Commit.
Erste Messung, 2026-08-31: 86.9 % von 5100 Statements, 729 Tests.
Festgehalten in
EVALS.md§ „How much of the stack the suite reaches" — also nicht nur hier imIssue, wie das Akzeptanzkriterium es als Minimum zuließ.
Die Module unten, nach den drei Kategorien getrennt, die das Issue verlangt:
commands/eval_cmd.pycommands/provenance_cmd.pycli.pycommands/types_cmd.pycommands/version_cmd.pyversion.pycommands/search.pycommands/migrate_cmd.pytype_resolver.pyversion.pyliegt bei 89.1 % — der Netzzugriff sitzt wie beschrieben hinter dem injizierbarenfetcher, die echte Netzzeile bleibt absichtlich ungetestet. Die Wrapper-Zeilen sind keinArbeitsvorrat: niedrige Zahlen dort sind das Zeichen für den guten Schnitt, das im Issue schon
vorweggenommen war. Arbeit ist ausschließlich die dritte Spalte.
Umsetzung, mit einer Abweichung vom Issue.
pytest-covsteht nicht intools/requirements.txt, CI installiert es nebenpytest— wie gefordert. Die Konfigurationliegt aber in
tools/.coveragerc, nicht intools/pytest.ini: coverage.py liest.coveragerc,setup.cfg,tox.iniundpyproject.toml, aber keinpytest.ini, wo ein[coverage:*]-Abschnitt still ignoriert würde. Gemessen wirdwiki_tools/ohnewiki_tools/tests/. Bericht als Artefaktcoverage-<run id>überupload-artifact@v3(nichtv4), unter
if: always()— eine rote Suite ist genau der Moment, in dem die Zahlen pro Modulinteressant sind. Kein
--cov-fail-under.Die Messung hängt bewusst nicht in
addopts: das würde den nacktenpytest -qüberall dortbrechen, wo
pytest-covfehlt — also in jeder Instanz und in jedem lokalen Lauf ohne dasZusatzpaket.
Dabei gefunden und mitbehoben:
dist exporthat die Coverage-Ausgabe mit ausgeliefert. Dererste Export nach der Messung trug 227 statt 162 Dateien, darunter den kompletten
htmlcov/-Baum — eine Messung dieses Repos, ausgeliefert in fremde Instanzen.TOOLS_EXCLUDE_DIRSprunet nur Verzeichnisse, zwei Drittel der Ausgabe (
.coverage,coverage.xml, unterParallelläufen
.coverage.<host>.<pid>) liegen als Dateien neben dem Code._copy_treenimmtjetzt zusätzlich ein Dateinamen-Prädikat. Ohne das hätte der CI-Export in jedem Lauf die
Coverage des Laufs mitverpackt, weil „Tests" vor „Export the distribution" läuft.
Was noch offen ist
--cov-fail-undersetzen, in eigenem Commit, mit der beobachteten Zahl alsBegründung. Sie friert den erreichten Stand ein, sie rechnet ihn nicht schön.
Neu gelabelt
prio/2→prio/3. Der Wert ist unverändert real, aber der nächste Schrittist auf einen Auslöser gestellt, und der Auslöser ist Zeit: mehrere CI-Läufe über echten
Commits, frühestens ab 2026-09-14. Vorher gesetzt wäre die Schwelle wieder das, was das
Issue verhindern wollte — eine Zahl ohne Beobachtung dahinter.
CI-Lauf 87 grün, alle neun Schritte. Auch Lauf 88 (Release) grün.
Zwei Nachträge zum Kommentar oben.
1. Die Baseline-Zahlen waren um einen Commit veraltet — korrigiert in
2b7b3cb.Gemessen hatte ich lokal vor dem
dist export-Fix, der noch Statements hinzufügte, und vordem Test, der ihn absichert. Verbindlich ist, was der veröffentlichte Baum produziert:
86.9 % von 5105 Statements, 730 Tests (CI-Lauf 87), nicht 5100/729.
Der Prozentwert ist derselbe, die Grundgesamtheit nicht.
EVALS.mdund der Changelog-Eintragnennen jetzt beide die von CI gemessene Zahl und die Laufnummer dazu.
2. Das Artefakt ist hochgeladen, aber über die API nicht auffindbar — offen.
Aus dem Log von Lauf 87, Schritt „Coverage report":
Der Upload meldet also Erfolg: 63 Dateien, 487 KB komprimiert aus 4.3 MB roh.
Aber: sowohl
/repos/torben/llm-wiki-test1/actions/runs/87/artifactsals auch dieRepo-weite Artefaktliste liefern
total_count: 0. Der Lauf ist abgeschlossen, es ist also keinTiming-Effekt.
Damit ist das Akzeptanzkriterium „Der Bericht ist als Artefakt abrufbar" nicht bestätigt.
Die Erfolgsmeldung der Action ist ein Beleg dafür, dass sie hochgeladen hat, nicht dafür,
dass Gitea es herausgibt. Zwei plausible Ursachen, beide ungeprüft:
upload-artifact@v3legtüber die alte Artifact-API ab, während die REST-Liste nur v4-Artefakte kennt — oder die
Artefakt-Speicherung ist auf dieser Instanz nicht so konfiguriert, wie die Liste es erwartet.
Nächster Schritt, bevor irgendetwas an v4 geändert wird: auf der Run-Seite im Browser
nachsehen, ob das Artefakt dort zum Download angeboten wird
(https://gitea.nehmer.net/torben/llm-wiki-test1/actions/runs/87). Wenn ja, ist es nur die
Listen-API und das Kriterium ist erfüllt. Wenn nein, ist v3 auf dieser Instanz ein Upload ins
Leere, und das gehört in ein eigenes Issue — es beträfe dann jeden Workflow hier, nicht nur
Coverage.
Stand 2026-09-01 — Schritt 1 ist erledigt, Schritt 2 läuft
Schritt 1 (messen ohne Schwelle) ist mit 1.8.1 geliefert. CI installiert
pytest-covnebenpytest, misstchemenu/ohnechemenu/tests/, lädt den Bericht als Artefakt hoch und setzt kein--cov-fail-under. Die Akzeptanzkriterien „CI weist Coverage aus" und „Bericht ist als Artefakt abrufbar" sind damit erfüllt.Offen bleiben Schritt 2 und 3: die Zahl über ein paar Wochen beobachten — vor allem welche Module unten liegen — und danach in einem eigenen Commit eine Untergrenze setzen, die den erreichten Stand einfriert.
Zwei Korrekturen am Text oben:
Die Testzahl ist von 630 auf 752 gestiegen. Der Zuwachs ist nicht nur Fläche: 2.1.1 hat eine Umgebungsabhängigkeit geschlossen, die genau in die Argumentation dieses Issues gehört.
test_legacy_source_pages_flags_url_and_directorywar grün, weil der Checkout zufällig einraw/documents/besaß — die Korpus-Bereinigung leerte es, Git verfolgt keine leeren Verzeichnisse, und der Test wurde in CI rot, während er lokal grün blieb. Das ist derselbe Befund wie in Run 52, dritter Fall.Der Verweis auf
torben/gitea-mcp@ci-binford/binford-build.yamlals Beleg fürupload-artifact@v3nennt einen privaten Hostnamen. Die Sache stimmt weiterhin — v3 ist das, was auf dieser Gitea funktioniert —, der Beleg gehört aber neutral formuliert, bevor jemand den Text nach draußen kopiert. Pfade im Text:wiki_tools/heißt seit 2.0.0chemenu/.Changelog: Body vollständig auf den Stand von 4.5.0 umgeschrieben (Triage-Sitzung 2026-09-04). Der Text hatte sich seit dem 2026-08-30 nicht bewegt, während drei Kommentare den echten Stand trugen — genau das Muster, das
issue-tracking.mdSchritt 2 verhindern soll, und genau der Fall, den #47 als Befund 2 beschreibt.Was der alte Body falsch sagte:
pytest-cov„gehört nachtools/pytest.ini" — die Konfiguration liegt tatsächlich intools/.coveragerc, mit Grund: coverage.py liestpytest.ininicht.upload-artifact@v3nannte einen privaten Hostnamen (ci-binford,binford-build.yaml); im Baum war er längst zuci-buildneutralisiert, im Issue nicht. Nachgezogen, ebensowiki_tools/→chemenu/(Teilerledigung von #29).Neu im Body, vorher nur in Kommentaren oder gar nicht:
provenance_cmd.py44 %,migrate_cmd.py71 %,type_resolver.py79 %). Nur die dritte Spalte bedeutet Arbeit.dist exportdie Coverage-Ausgabe mit ausgeliefert hat (227 statt 162 Dateien), und wie er behoben wurde.total_count: 0bei abgeschlossenem Lauf. Kommentar 342 hat das Kriterium für erfüllt erklärt, ohne dass die Gegenprobe gelaufen wäre. Es steht jetzt als offener Punkt mit dem konkreten nächsten Schritt (Run-Seite im Browser ansehen, bevor irgendetwas an v4 geändert wird).Labels unverändert.
prio/waitingbleibt bis zum Auslöser 2026-09-14; das Datum steht jetzt im Body statt nur in Kommentar 315.Schritt 2 abgeschlossen, Artefakt-Frage geklärt (2026-09-04, Stack 4.7.1)
Der Artefakt-Abruf ist bestätigt.
coverage-163wird auf der Run-Seite zum Downloadangeboten. Damit ist das Akzeptanzkriterium erfüllt, und die Ursache des Widerspruchs aus
Kommentar 318 ist benannt statt vermutet: von den beiden dort genannten Erklärungen trifft die
erste zu.
upload-artifact@v3legt über die ältere Artifact-API ab, die dieActions-Artefakt-Endpunkte nicht lesen — deshalb
total_count: 0bei einem Artefakt, dastatsächlich da ist. Reproduziert über 76 Läufe: auch
list_run_artifactsfür Lauf 163 meldettotal_count: 0, während die Run-Seite die Datei herausgibt.Kein Folge-Issue. v3 funktioniert auf dieser Instanz, nur die Listen-API sieht es nicht. Der
Befund steht jetzt an beiden Stellen, an denen ihn jemand sucht: in
EVALS.mdund im Kommentaran der
Coverage report-Stufe in.gitea/workflows/ci.yml, jeweils mit dem Satz, dass eineleere Liste kein fehlgeschlagener Upload ist. Ohne den leitet ihn beim nächsten Mal jemand neu
her — und die naheliegende „Korrektur" wäre der Wechsel auf v4, der hier eingeschränkt ist.
Schritt 2 ist damit erledigt. Beobachtet wurden 38 CI-Läufe zwischen Lauf 87 und Lauf 163,
alle grün:
Ein Viertel mehr gemessener Code, ein Drittel mehr Tests, ein Zehntelpunkt Bewegung in der Quote.
Das ist die Beobachtung, die eine Schwelle tragen kann.
Die Modulverteilung ist der interessantere Teil, und sie hat sich bewegt — die vollständige
Liste steht jetzt im Body. Zwei Dinge daraus:
search.pyfällt von 68,6 % auf 48,5 % und ist kein Regress: ungedeckt ist derTyper-Kommandokörper (117–176), die Backends unter
chemenu/search/liegen bei 91–98 %.Kategorie 1 nach der Taxonomie dieses Issues. Wer nur den Gesamtwert pro Modul liest, hätte
hier Arbeit gesehen, wo keine ist — genau der Grund, warum das Issue die drei Kategorien
verlangt.
herum wuchs.
provenance_cmd.pysteht unverändert bei 44,1 %;migrate_cmd.pyist von 71 %auf 65 % gefallen, weil das Modul gewachsen ist und die neuen Zeilen ungetestet kamen.
Eine Schwelle friert das ein, sie schließt es nicht. Der Arbeitsvorrat gehört in ein eigenes
Issue.
Schritt 3 bleibt offen, Labels bleiben. Die inhaltliche Bedingung („mehrere CI-Läufe über
echten Commits") ist erfüllt, das Datum 2026-09-14 steht trotzdem — es soll die Beobachtung
sein, die die Zahl trägt, nicht die Ungeduld.
prio/waitingbis dahin. Empfehlung für dieSchwelle steht im Body: 85, nicht 87. 87 hat keine Luft, der nächste ehrliche
Wrapper-Commit macht CI rot, und dann wird die Schwelle gesenkt statt verdient — der Ausgang,
den dieses Issue verhindern soll.
Geliefert in
fe55ad2. Prose-only plus Workflow-Kommentar, also kein Version-Bump: dasVersion-Gate ist auf
^(tools/|types/|instructions/|AGENTS\.md$|[^/]+/CONTRACT\.md$)gescoped,und weder
EVALS.mdnoch.gitea/fällt darunter.Geschlossen mit 4.7.2 (
e00eae0). Body auf den Endstand umgeschrieben — er beschreibt jetztdas Ergebnis statt des Plans, und die drei Schritte lesen sich als erledigt statt als Vorhaben.
Was sich gegenüber dem Stand von heute Nachmittag geändert hat:
fail_under = 85intools/.coveragerc, gegengemessene 87,0 %. Der 2026-09-14 war ein Proxy für „mehrere CI-Läufe über echten Commits", und
der Proxy war eingelöst: 38 grüne Läufe, +27 % Statements, +245 Tests, ein Zehntelpunkt
Bewegung in der Quote. Auf dem Datum zu beharren, wäre das Spiegelbild des Fehlers gewesen, den
dieses Issue verhindern sollte — dort eine Zahl ohne Beobachtung, hier ein Warten ohne Grund.
Die Begründung für 85 statt 87 steht im Body.
Artifact-API ab, die Listen-Endpunkte lesen die nicht. Festgehalten in
EVALS.mdund imWorkflow-Kommentar, damit die naheliegende „Korrektur" auf v4 niemandem mehr einfällt.
#51 trägt die drei echten Lücken (
provenance_cmd.py44 %,migrate_cmd.py65 %,type_resolver.py79 %) mit Zeilennummern und der Begründung, warum gerade die zählen.docs/why-gates-are-code.mdnachgezogen (b4e4501): die Untergrenze steht dort jetzt alszweiter Fall neben der Iterationsgrenze. Die Grenze war erst falsch und wurde dann gemessen,
der Boden wurde absichtlich zurückgehalten, bis die Zahl existierte — dasselbe Argument in
beide Richtungen. Keine andere
docs/-Seite ist von dieser Änderung berührt.Verifiziert, nicht angenommen: CI-Lauf 166 über
e00eae0grün, alle neun Schritte, mitRequired test coverage of 85.0% reached. Total coverage: 87.01%im Log — die Untergrenze ist inCI aktiv und wird eingehalten, nicht bloß konfiguriert. Lokal derselbe Befund bei 975 Tests.