Automatischer Push: 11 Datei(en) geändert
- .claude/agents/planer.md - .claude/workflows/plankritik.js - .vault-tools/plan-kopie/LIESMICH.md - .vault-tools/plan-kopie/mutanten_lauf.py - .vault-tools/plan-kopie/mutanten_lauf_ENTWURF.md - .vault-tools/plan-kopie/mutanten_lauf_kopie.sh - .vault-tools/plan-kopie/mutanten_lauf_treiber.py - .vault-tools/workflow-pruefung/faelle_workflows.mjs - .vault-tools/workflow-pruefung/mutanten_workflows.py - AI/Regelwerk/codeworkflow.md - CLAUDE.md Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
e50c8296d7
commit
bc9b4f4497
11 files changed
+2214
-10
No files matched your search
@@ -0,0 +1,198 @@
|
||||
---
|
||||
type: resource
|
||||
status: active
|
||||
title: "Entwurf: mutanten_lauf.py — allgemeiner Mutantenläufer aus der Plantabelle"
|
||||
summary: "Entscheidungsliste, Dateiformat mutanten.toml, Ablauf, Status und Exit-Codes, Abdichtung und Angriffsliste für den allgemeinen Mutantenläufer. Punkt 3 der Arbeitsweise vom 2026-09-26."
|
||||
tags: [vault-tools, codeworkflow, pruefwerkzeuge]
|
||||
created: 2026-09-27
|
||||
updated: 2026-09-27
|
||||
---
|
||||
|
||||
# Entwurf: `mutanten_lauf.py`
|
||||
|
||||
Bisher hatte jeder Plan seinen eigenen Mutantenläufer. `mutlauf.py` für L1a las die Mutanten per Regex aus dem Plantext, `mutanten_py.py` hielt sie als Python-Liste. Die Mutanten von B1 stehen in einer dritten Form im Plan, die keiner der beiden liest. Der allgemeine Läufer liest stattdessen **eine Datei in einem festen Format**, die im Planordner neben der Übersicht liegt: `mutanten.toml`. Prosa im Plan wird nicht geparst.
|
||||
|
||||
Das Werkzeug ist ein Fangnetz, kein Nachweis. Es sagt, ob die genannten Tests unter einer bestimmten Änderung fallen. Ob sie **an der gemeinten Stelle** fallen, zeigt es nur über die Fehlermeldung, die es mit ausgibt. Das Urteil darüber bleibt beim Menschen.
|
||||
|
||||
## 1 Entscheidungsliste
|
||||
|
||||
| Frage | Herkunft |
|
||||
|---|---|
|
||||
| Steht das Muster eines Mutanten genau einmal als ganze Zeilenfolge in seiner Datei? | Projektvorgabe (Pläne L1a und B1: „jedes Muster steht als ganze Zeile genau einmal in seiner Datei“) |
|
||||
| Sind alle genannten Tests ohne Mutant grün (Grundlinie)? | Projektvorgabe ([[AI/Regelwerk/pruefwerkzeuge\|pruefwerkzeuge.md]], Abschnitt 3: zuerst die Grundlinie) |
|
||||
| Fällt unter dem Mutanten **jeder** genannte Test? | Projektvorgabe (Pläne: „‚Rot bei‘ nennt Tests, die fallen müssen“) |
|
||||
| Was heißt „fällt“? | Projektvorgabe: Der Test hat in einer Phase (setup, call, teardown) das Ergebnis `failed`, oder er fehlt, weil das Einsammeln seiner Datei scheitert. `skipped` und `xfail` sind nicht rot. |
|
||||
| Ist die Datei nach jedem Mutanten wieder byte-gleich? | Projektvorgabe: SHA-256 vorher und nachher |
|
||||
| Läuft das auf einer Wegwerfkopie? | Projektvorgabe: Die Wurzel darf kein `.git` haben (Ordner oder Datei), und keiner der Pfade aus `ohne` darf existieren |
|
||||
| Welche Tests und Ergebnisse meldet pytest? | Quelle: pytest-Hooks `pytest_runtest_logreport`, `pytest_collectreport`, `pytest_deselected` (pytest 9.1.1 im Venv `trading`, nachgesehen 2026-09-27). Die Knotennamen kommen aus `report.nodeid`, nicht aus einer JUnit-Klassenbezeichnung. |
|
||||
| Hält die Abdichtung Netz und echte Zugänge fern? | Annahme, aufgelöst als Projektvorgabe: Sie ist ein Fangnetz mit benannten Lücken (Abschnitt 5), kein Nachweis. |
|
||||
|
||||
## 2 Dateiformat `mutanten.toml`, Format 1
|
||||
|
||||
TOML, gelesen mit `tomllib`. Literale Zeichenketten in `'''…'''` nehmen Backslashes wörtlich; deshalb dieses Format und nicht JSON.
|
||||
|
||||
```toml
|
||||
format = 1
|
||||
python = "/home/claude/venvs/trading/bin/python"
|
||||
ohne = ["config/secrets.toml", "config/zugang.toml"]
|
||||
|
||||
[kuerzel]
|
||||
DA = "tests/test_bewertung_daten.py"
|
||||
U = "tests/test_bewertung_urteil.py"
|
||||
|
||||
[python_je_datei]
|
||||
"tests/test_broker_ibkr.py" = "/home/claude/venvs/trading-ibkr/bin/python"
|
||||
|
||||
[[mutant]]
|
||||
id = "D1"
|
||||
datei = "trading/bewertung/daten.py"
|
||||
aus = '''
|
||||
return ort.weekday() < 5 and SPERRE_VON <= ort.time() < SPERRE_BIS
|
||||
'''
|
||||
wird = '''
|
||||
return False
|
||||
'''
|
||||
rot_bei = ["DA::test_montag_bis_freitag_von_neun_bis_halb_fuenf_new_york_wird_nicht_abgerufen"]
|
||||
hinweis = "frei, wird nur ausgegeben"
|
||||
```
|
||||
|
||||
**Oberste Ebene**, erlaubt sind genau diese Schlüssel:
|
||||
|
||||
| Schlüssel | Pflicht | Typ | Bedeutung |
|
||||
|---|---|---|---|
|
||||
| `format` | ja | Ganzzahl, genau `1` | Formatfassung |
|
||||
| `python` | ja | absoluter Pfad | Interpreter für pytest; `~` ist ein Befund |
|
||||
| `ohne` | nein | Liste von relativen Pfaden | darf es in der Wurzel nicht geben; `.git` gilt immer zusätzlich |
|
||||
| `kuerzel` | nein | Tabelle Name → relativer Pfad einer Testdatei | Abkürzung in `rot_bei` |
|
||||
| `python_je_datei` | nein | Tabelle relativer Pfad → absoluter Pfad | anderer Interpreter für diese Testdatei |
|
||||
| `mutant` | ja | Liste von Tabellen, nicht leer | die Mutanten |
|
||||
|
||||
**Je Mutant**, erlaubt sind genau diese Schlüssel:
|
||||
|
||||
| Schlüssel | Pflicht | Typ | Bedeutung |
|
||||
|---|---|---|---|
|
||||
| `id` | ja | nicht leere Zeichenkette, eindeutig | Name in der Ausgabe und in `--nur` |
|
||||
| `datei` | ja | relativer Pfad | Datei in der Wurzel; kein `..`, nicht absolut, keine Verknüpfung, muss existieren |
|
||||
| `aus` | ja | nicht leere Zeichenkette | ganze Zeilen, die ersetzt werden |
|
||||
| `wird` | ja | Zeichenkette | ganze Zeilen, die dafür eingesetzt werden; leer heißt: Zeilen entfernen |
|
||||
| `rot_bei` | ja | nicht leere Liste von Zeichenketten | Tests, die fallen müssen |
|
||||
| `hinweis` | nein | Zeichenkette | wird in Ausgabe und Bericht mitgeführt |
|
||||
|
||||
Jeder unbekannte Schlüssel ist ein Befund (Positivliste). So fällt `rotbei` statt `rot_bei` auf.
|
||||
|
||||
**Zeilen aus `aus` und `wird`:** Ein einzelnes `\n` am Ende wird entfernt (die schließenden `'''` auf eigener Zeile). Eine leere Zeichenkette ergibt null Zeilen. Sonst wird an `\n` getrennt. Ein `\r` bleibt Teil der Zeile.
|
||||
|
||||
**Einträge in `rot_bei`:** `K::name` mit `K` aus `kuerzel` wird zu `<pfad>::name`. Sonst muss der Eintrag mit einem relativen Pfad auf `.py` beginnen, gefolgt von `::`. Alles andere ist ein Befund (unbekanntes Kürzel). Parametrisierte Tests stehen mit ihrer vollen Kennung, etwa `U::test_knapp_hinter_jeder_grenze_faellt_genau_dieses_kriterium[max_drawdown-0.1501-max_drawdown]`.
|
||||
|
||||
## 3 Ablauf
|
||||
|
||||
Aufruf in WSL, mit einem Python ab 3.11 (nur Standardbibliothek):
|
||||
|
||||
```
|
||||
python mutanten_lauf.py --wurzel <kopie> --mutanten <mutanten.toml> [--nur ID,ID,…] [--nur-muster] [--bericht <datei.json>] [--zeitlimit <sekunden>]
|
||||
```
|
||||
|
||||
`--zeitlimit` gilt je pytest-Aufruf, Vorgabe 900. `--nur-muster` fährt nur die Schritte 1 und 2.
|
||||
|
||||
1. **Vorbedingungen und Format.** Alle Befunde sammeln, nicht beim ersten abbrechen: Wurzel fehlt oder ist kein Ordner; `.git` in der Wurzel; ein Pfad aus `ohne` existiert; TOML nicht lesbar; jede Formatregel aus Abschnitt 2; ein Interpreter fehlt oder ist nicht ausführbar; `--nur` nennt eine unbekannte ID. Gibt es einen Befund, endet der Lauf hier mit Exit 2.
|
||||
2. **Muster.** Für jeden ausgewählten Mutanten: Datei als Bytes lesen, streng als UTF-8 dekodieren (sonst Befund), an `\n` trennen, die Zeilenfolge aus `aus` an jeder Startposition suchen (überlappende Treffer zählen mit). Genau ein Treffer ist gut. Sonst Status `MUSTER` mit der Trefferzahl; enthält die Datei ein `\r`, steht der Hinweis „Datei hat CR“ dabei. `aus` gleich `wird` ist ein Formatbefund aus Schritt 1 („wirkungslos“).
|
||||
3. **Grundlinie.** Alle genannten Tests der ausgewählten Mutanten ohne Mutant fahren, gruppiert nach Interpreter. Jeder muss `GRUEN` sein. Fehlt einer, ist er abgewählt (etwa durch den Marker `integration`), übersprungen oder rot, ist das ein Befund mit der Meldung, und der Lauf endet mit Exit 2, bevor ein Mutant läuft.
|
||||
4. **Mutanten** in der Reihenfolge der Datei, `MUSTER`-Fälle ausgelassen. Je Mutant:
|
||||
- SHA-256 der Originalbytes merken, die Zeilen ersetzen, mit `\n` zusammenfügen, als UTF-8-Bytes schreiben.
|
||||
- Die genannten Tests fahren, gruppiert nach Interpreter.
|
||||
- Im `finally` die Originalbytes zurückschreiben und den SHA-256 neu lesen. Weicht er ab, endet der ganze Lauf sofort mit dem Befund „Rücksetzen gescheitert“ und Exit 2.
|
||||
5. **Zusammenfassung** und Exit-Code.
|
||||
|
||||
**pytest-Aufruf:** `<python> -m pytest -q -p no:cacheprovider -p _mutanten_lauf_plugin <knoten…>` mit `cwd` = Wurzel. Das Plugin ist eine kleine Datei, die das Skript in einen eigenen Temp-Ordner schreibt; der Ordner kommt vorn in `PYTHONPATH`. Das Plugin schreibt je Bericht eine JSON-Zeile in die Datei aus der Umgebungsvariable `MUTANTEN_LAUF_ERGEBNIS`:
|
||||
|
||||
- `pytest_runtest_logreport`: `{"art": "test", "knoten": nodeid, "phase": when, "ergebnis": outcome, "xfail": hat wasxfail, "meldung": erste Zeile}`
|
||||
- `pytest_collectreport` nur bei `failed`: `{"art": "sammeln", "knoten": nodeid, "meldung": erste Zeile}`
|
||||
- `pytest_deselected`: je Element `{"art": "abgewaehlt", "knoten": nodeid}`
|
||||
|
||||
Die Meldung ist `longrepr.reprcrash.message`, wenn es das gibt, sonst die erste nicht leere Zeile von `str(longrepr)`, gekürzt auf 300 Zeichen. Das Plugin schreibt jede Zeile sofort und mit `flush`.
|
||||
|
||||
**Auswertung je Test:** eine Phase `failed` → `ROT` (bei `xfail` nicht); sonst `skipped` in einer Phase oder `xfail` → `UEBERSPRUNGEN`; sonst `call` mit `passed` → `GRUEN`; abgewählt → `ABGEWAEHLT`; gar kein Bericht → `FEHLT`. Unter einem Mutanten zählt `FEHLT` als `ROT` mit der Meldung „Sammelfehler: …“, wenn es für seine Testdatei (oder einen übergeordneten Knoten) einen `sammeln`-Bericht gibt. In der Grundlinie ist jedes `FEHLT` ein Befund.
|
||||
|
||||
**Zeitlimit:** Prozess mit `start_new_session=True` starten; bei Überschreitung die ganze Prozessgruppe mit `SIGKILL` beenden. Der Mutant bekommt `ZEIT`, das Rücksetzen läuft trotzdem.
|
||||
|
||||
## 4 Status, Ausgabe, Exit-Codes
|
||||
|
||||
| Status je Mutant | Bedeutung |
|
||||
|---|---|
|
||||
| `OK` | jeder genannte Test `ROT` |
|
||||
| `LUECKE` | mindestens ein genannter Test `GRUEN` oder `UEBERSPRUNGEN` |
|
||||
| `UNKLAR` | ein Test `FEHLT` ohne Sammelfehler, oder pytest endete ohne auswertbare Ergebnisdatei |
|
||||
| `ZEIT` | Zeitlimit überschritten |
|
||||
| `MUSTER` | Muster nicht genau einmal gefunden, nichts gefahren |
|
||||
|
||||
Ausgabe auf stdout, UTF-8, je Mutant eine Zeile: `D1 OK DA::test_x=ROT(<meldung, 120 Zeichen>) …`. Tests erscheinen in der Kurzform aus `rot_bei`. Ein vorhandener `hinweis` folgt in der nächsten Zeile, eingerückt. Oben stehen Wurzel, Mutantendatei, Anzahl gesamt und ausgewählt und je Interpreter die Python-Version. Unten eine gerechnete Zusammenfassung: `ergebnis: <n> Mutanten, <a> OK, <b> LUECKE, <c> UNKLAR, <d> ZEIT, <e> MUSTER`.
|
||||
|
||||
`--bericht` schreibt dasselbe als JSON: `{"wurzel", "mutanten_datei", "befunde": [...], "grundlinie": {knoten: {ergebnis, meldung}}, "mutanten": [{"id", "datei", "status", "hinweis", "treffer", "tests": [{"eintrag", "knoten", "ergebnis", "meldung"}]}], "zusammenfassung": {...}}`. Der Bericht wird auch bei einem Abbruch geschrieben, mit allem, was bis dahin feststand.
|
||||
|
||||
| Exit | Bedeutung |
|
||||
|---|---|
|
||||
| 0 | Grundlinie grün, jeder ausgewählte Mutant `OK` (mit `--nur-muster`: jedes Muster genau einmal) |
|
||||
| 1 | mindestens ein Mutant nicht `OK` |
|
||||
| 2 | Vorbedingung, Format, Grundlinie oder Rücksetzen gescheitert |
|
||||
| 3 | unerwarteter Abbruch; die bis dahin gesammelten Befunde und Ergebnisse werden trotzdem ausgegeben |
|
||||
|
||||
Der Hauptablauf steht in `try`. Ein Traceback ist ein Befund und wird als solcher gemeldet ([[AI/Regelwerk/pruefwerkzeuge|pruefwerkzeuge.md]], Abschnitt 4).
|
||||
|
||||
## 5 Abdichtung
|
||||
|
||||
Jeder pytest-Aufruf bekommt eine Kopie der Umgebung mit diesen Änderungen:
|
||||
|
||||
- `HOME` zeigt auf einen frischen, leeren Temp-Ordner. Zugänge, die unter `~/.config` liegen (Scharfschalter, IBKR-Live-Zugang, Scalable), sind damit nicht erreichbar.
|
||||
- `http_proxy`, `https_proxy`, `all_proxy` und dieselben in Großschrift zeigen auf `http://127.0.0.1:9`; `no_proxy` und `NO_PROXY` werden entfernt. Bibliotheken, die Proxy-Variablen lesen, laufen damit gegen einen geschlossenen Port.
|
||||
- `PYTHONDONTWRITEBYTECODE=1`, `COLUMNS=300`, `MUTANTEN_LAUF_ERGEBNIS=<temp-datei>`, `PYTHONPATH` mit dem Plugin-Ordner vorn.
|
||||
|
||||
**Was das nicht abdichtet:** einen Unterprozess mit absolutem Pfad (so kam der Vorfall vom 2026-09-26 zustande), einen Socket ohne Proxy, einen Client mit `trust_env=False` oder mit eigenem Transport, Dateien mit absolutem Pfad außerhalb von `HOME`. Die eigentliche Sperre bleibt die Netzsperre der Testdateien selbst und eine Kopie ohne Geheimnisdateien (`ohne`).
|
||||
|
||||
Alle Temp-Ordner liegen außerhalb der Wurzel und werden am Ende entfernt, auch nach einem Abbruch.
|
||||
|
||||
## 6 Angriffsliste für den Treiber `mutanten_lauf_treiber.py`
|
||||
|
||||
Der Treiber baut in einem Temp-Ordner ein kleines Projekt (ein Modul, eine Testdatei, `pyproject.toml` mit `[tool.pytest.ini_options]`), schreibt je Fall eine `mutanten.toml`, startet den Läufer als Unterprozess und prüft Exit-Code und ein Meldungsstück. **Zuerst die Grundlinie:** Der saubere Fall muss Exit 0 und nur `OK` liefern, sonst misst der Rest nichts. Nach **jedem** Fall prüft der Treiber, dass jede Datei des kleinen Projekts byte-gleich mit dem Ausgangsstand ist.
|
||||
|
||||
| Nr | Fall | erwartet |
|
||||
|---|---|---|
|
||||
| 1 | sauber, zwei Mutanten, beide getötet | Exit 0, zweimal `OK` |
|
||||
| 2 | ein Test bleibt grün, als erster Eintrag in `rot_bei` | Exit 1, `LUECKE` |
|
||||
| 3 | dasselbe als letzter Eintrag | Exit 1, `LUECKE` |
|
||||
| 4 | der lückenhafte Mutant an erster und an letzter Position der Datei | je Exit 1, `LUECKE` beim richtigen Mutanten |
|
||||
| 5 | `aus` kommt nicht vor | Exit 1, `MUSTER` mit Zahl 0 |
|
||||
| 6 | `aus` kommt zweimal vor | Exit 1, `MUSTER` mit Zahl 2 |
|
||||
| 7 | `aus` ist nur ein Teil einer Zeile (andere Einrückung) | Exit 1, `MUSTER` mit Zahl 0 |
|
||||
| 8 | Zieldatei mit CRLF | Exit 1, `MUSTER` mit Hinweis auf CR |
|
||||
| 9 | `aus` über zwei Zeilen, getötet | Exit 0, `OK` |
|
||||
| 10 | `wird = ""` entfernt eine Zeile, getötet | Exit 0, `OK` |
|
||||
| 11 | Mutant erzeugt einen Syntaxfehler, das Einsammeln scheitert | Exit 0, `OK` mit „Sammelfehler“ |
|
||||
| 12 | Mutant erzeugt eine Endlosschleife, `--zeitlimit 5` | Exit 1, `ZEIT`, Datei danach byte-gleich |
|
||||
| 13 | ein Test ist in der Grundlinie rot | Exit 2 |
|
||||
| 14 | ein Test ist in der Grundlinie übersprungen | Exit 2 |
|
||||
| 15 | ein Test ist per Marker abgewählt | Exit 2, Meldung nennt „abgewählt“ oder `ABGEWAEHLT` |
|
||||
| 16 | ein Testname existiert nicht | Exit 2 |
|
||||
| 17 | `.git` als Ordner in der Wurzel; dasselbe als Datei | je Exit 2 |
|
||||
| 18 | ein Pfad aus `ohne` existiert | Exit 2 |
|
||||
| 19 | unbekannter Schlüssel im Mutanten (`rotbei`) und auf oberster Ebene | je Exit 2 |
|
||||
| 20 | doppelte `id` | Exit 2 |
|
||||
| 21 | `rot_bei` leer; `rot_bei` als Zeichenkette statt Liste | je Exit 2 |
|
||||
| 22 | `datei` mit `..`; absolut; fehlt | je Exit 2 |
|
||||
| 23 | unbekanntes Kürzel in `rot_bei` | Exit 2 |
|
||||
| 24 | `aus` gleich `wird` | Exit 2 |
|
||||
| 25 | `aus` leer; `format = 2`; `format` fehlt; `python` relativ; `python` existiert nicht | je Exit 2 |
|
||||
| 26 | TOML-Syntaxfehler | Exit 2 |
|
||||
| 27 | keine Mutanten | Exit 2 |
|
||||
| 28 | `--nur` mit unbekannter ID | Exit 2 |
|
||||
| 29 | `--nur` wählt einen von zwei Mutanten | Exit 0, nur dieser läuft |
|
||||
| 30 | `--nur-muster` bei sauberem Fall; bei Fall 6 | Exit 0 ohne pytest-Lauf; Exit 1 |
|
||||
| 31 | Zieldatei ist kein UTF-8 | Exit 1 oder 2, Meldung nennt die Kodierung |
|
||||
| 32 | Abdichtung: ein Test prüft, dass `HOME` nicht das echte ist und `https_proxy` gesetzt ist | Grundlinie grün, Exit 0 |
|
||||
| 33 | `--bericht` schreibt gültiges JSON mit den Status aus Fall 2 | Datei lesbar, Status stimmt |
|
||||
| 34 | `python_je_datei` setzt für eine Testdatei einen zweiten Interpreter (derselbe Pfad über einen anderen Namen, etwa eine Verknüpfung im Temp) | Exit 0 |
|
||||
|
||||
Der Treiber gibt je Fall eine Zeile aus (`Fall 5 ok` oder `Fall 5 FEHLER: erwartet …, bekommen …`) und am Ende die gerechnete Zahl der Fehler. Exit 0 nur, wenn alle Fälle wie erwartet ausgehen und jedes Mal byte-gleich zurückgesetzt wurde.
|
||||
|
||||
## 7 Abbruchbedingung
|
||||
|
||||
Eine Runde mit dem Prüfer endet, wenn die Befunde nur noch Formulierungen betreffen. Der erste echte Einsatz ist B1 (74 Mutanten auf einer Kopie von `b1-mat-kopie`).
|
||||
Reference in new issue
Block a user