mutanten_lauf_kopie.sh faehrt lo im eigenen Netz-Namensraum hoch (unshare --keep-caps, ip, setpriv, exec in python3) und prueft das vorher fail-closed (ip, setpriv, python3, lo-Probe mit Faehigkeitspruefung). Anlass: Im Paket RB3 der Trading Plattform scheiterten Tests mit Server auf 127.0.0.1 im Namensraum an Errno 101 (lo aus). Neuer Treiber mutanten_lauf_kopie_treiber.py mit W1 bis W13; Regressionsnachweis gegen HEAD (W1, W2, W4, W5, W9 rot) und neun kaputte Varianten v1 bis v9, je an ihren Faellen rot. Lauf mit echten Daten: alle 73 RB3-Mutanten durch das neue Skript 73/73 OK, T1 und T12 jetzt im Namensraum. Ein pruefer las Skript und Treiber (kein blockierender Befund; der wichtige zu fehlenden Faellen fuer die Fehlerzweige ist mit Nachbesserung 1 behoben, W10 bis W13). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JU1LsUp4FdmPq8mvC368G3
26 KiB
type, status, title, summary, tags, created, updated
| type | status | title | summary | tags | created | updated | |||
|---|---|---|---|---|---|---|---|---|---|
| resource | active | Entwurf: mutanten_lauf.py — allgemeiner Mutantenläufer aus der Plantabelle | 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. |
|
2026-09-27 | 2026-10-09 |
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, 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. |
| Welche pytest-Konfiguration gilt? | Quelle: pytest 9.1.1, _pytest/config/findpaths.py (determine_setup, locate_config, get_common_ancestor, load_config_dict_from_file; SHA-256 535d149e6f3962e4…, in den Venvs trading und mattermost gleich) und _pytest/config/__init__.py (Vorgabe von confcutdir, ExitCode), nachgelesen am 2026-10-06; die Abschnittszeile in tox.ini und setup.cfg aus iniconfig 2.3.0 (_parse.py, _parseline) im selben Venv. Projektvorgabe seit 2026-10-06: gesucht wird wie pytest, aber nie oberhalb der Wurzel (Abschnitt 3, „Konfiguration“). |
2 Dateiformat mutanten.toml, Format 1
TOML, gelesen mit tomllib. Literale Zeichenketten in '''…''' nehmen Backslashes wörtlich; deshalb dieses Format und nicht JSON.
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.
- Vorbedingungen und Format. Alle Befunde sammeln, nicht beim ersten abbrechen: Wurzel fehlt oder ist kein Ordner;
.gitin der Wurzel; ein Pfad ausohneexistiert; TOML nicht lesbar; jede Formatregel aus Abschnitt 2; ein Interpreter fehlt oder ist nicht ausführbar;--nurnennt eine unbekannte ID. Gibt es einen Befund, endet der Lauf hier mit Exit 2. - Muster. Für jeden ausgewählten Mutanten: Datei als Bytes lesen, streng als UTF-8 dekodieren (sonst Befund), an
\ntrennen, die Zeilenfolge ausausan jeder Startposition suchen (überlappende Treffer zählen mit). Genau ein Treffer ist gut. Sonst StatusMUSTERmit der Trefferzahl; enthält die Datei ein\r, steht der Hinweis „Datei hat CR“ dabei.ausgleichwirdist ein Formatbefund aus Schritt 1 („wirkungslos“). - Grundlinie. Alle genannten Tests der ausgewählten Mutanten ohne Mutant fahren, gruppiert nach Interpreter. Jeder muss
GRUENsein. Fehlt einer, ist er abgewählt (etwa durch den Markerintegration), übersprungen oder rot, ist das ein Befund mit der Meldung, und der Lauf endet mit Exit 2, bevor ein Mutant läuft. Bricht pytest für einen Interpreter ab (Rückgabewert 3 oder 4), steht statt der Zeilen je Test ein Befund für diesen Interpreter (unten, „Abbruch von pytest“). - Mutanten in der Reihenfolge der Datei,
MUSTER-Fälle ausgelassen. Je Mutant:- SHA-256 der Originalbytes merken, die Zeilen ersetzen, mit
\nzusammenfügen, als UTF-8-Bytes schreiben. - Die genannten Tests fahren, gruppiert nach Interpreter.
- Im
finallydie 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.
- SHA-256 der Originalbytes merken, die Zeilen ersetzen, mit
- Zusammenfassung und Exit-Code.
pytest-Aufruf: <python> -m pytest -q -p no:cacheprovider -p _mutanten_lauf_plugin <konfiguration…> <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_collectreportnur beifailed:{"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.
Konfiguration (seit 2026-10-06): Ohne Vorgabe sucht pytest seine Konfiguration vom gemeinsamen Vorfahren der Testpfade aufwärts bis zur Wurzel des Dateisystems. Liegt die Kopie unter dem Windows-Temp, fand pytest dort eine fremde pyproject.toml mit addopts = "--new-first --failed-first"; die Optionen brauchen den Cache, den -p no:cacheprovider abschaltet, pytest brach mit Rückgabewert 4 ab, und der Läufer meldete jeden Test der Grundlinie als FEHLT (Plankritik 2 von C1 im Projekt After Install, 2026-10-05). Seitdem bestimmt der Läufer je Aufruf, was pytest selbst wählen würde, sucht aber nie oberhalb der Wurzel:
- Die Wurzel wird zuerst mit
realpathaufgelöst. pytest rechnet vom Arbeitsordner aus, den der Kernel ohne Verknüpfungen meldet; über einen Symlink angegeben, wurden die Knotennamen sonst zu::test_…(Prüfung vom 2026-10-06, Treiber-Fall 41). - Reihenfolge der Namen und Bedingungen wie in pytest 9.1.1:
pytest.toml,.pytest.toml,pytest.ini,.pytest.iniimmer;pyproject.tomlmit[tool.pytest]oder[tool.pytest.ini_options];tox.inimit[pytest];setup.cfgmit[tool:pytest]; ohne Treffer die erstepyproject.tomlüberhaupt. Erst vom gemeinsamen Vorfahren aus, ohne Treffer und ohnesetup.pydazwischen noch einmal von jedem Testordner aus. Die Abschnittszeile intox.iniundsetup.cfgerkennt der Läufer wie_parselinein iniconfig 2.3.0:[in Spalte 0, Kommentare nach#und;entfernt, dann]am Ende. Ein Pfad, dessen Name zu lang ist, gilt wie beisafe_existsin pytest als nicht vorhanden. - Gefunden:
-c <datei>. pytest setztrootdirundconfcutdirdann selbst auf ihren Ordner, wie bei der eigenen Suche. - Nicht gefunden:
-c <leere pytest.ini im Plugin-Ordner> --rootdir <r> --confcutdir <r>,rist der Ordner mitsetup.pyoder sonst die Wurzel. Ohne--confcutdirlägeconfcutdirim Plugin-Ordner, und pytest lüdeconftest.py-Dateien oberhalb der Wurzel. - Die Kopfzeilen
pytest-konfiguration (Grundlinie, <python>): …nennen je Interpreter, was für seinen Aufruf in der Grundlinie gilt. - Gegen pytest selbst geprüft wird das mit
mutanten_lauf_probe_konfig.py(Treiber-Fall 43): 26 Aufbauten, jepytest_konfig_bestimmengegendetermine_setupund ein echtespytest --coohne-cgegen einen Lauf mit den Argumenten des Läufers unter fremder Konfiguration. Die Probe sichert ihre Voraussetzung mit: Ohne-cendet pytest mit 0 (außer bei fehlenden Knoten), und wo der Aufbau einconftest.pyhat, ist eines geladen. - Benannte Lücken: Eine
tox.inioder einsetup.cfg, die iniconfig nicht lesen kann, übergeht der Läufer, pytest bricht dort mit 4 ab; eine nicht lesbarepyproject.tomloderpytest.tomlgilt dagegen als Treffer, damit pytest den Fehler selbst meldet. Einsetup.cfgmit[pytest], bei pytest ein Fehler, gilt hier als keine Konfiguration. Ohne Konfiguration in der Wurzel istconfig.inipathdie leere Datei; ini-Werte vom Typpaths, die über-ooderPYTEST_ADDOPTSkommen, gelten dann relativ zum Plugin-Ordner. Liegtrootdirunter der Wurzel (Konfiguration odersetup.pyin einem Unterordner der Tests), bildet pytest die Knotennamen relativ dazu, undrot_beipasst nicht mehr: Die Grundlinie meldetFEHLT, die Kopfzeile nennt die gefundene Datei; heute betrifft das kein Projekt im Vault. Zeilen trennt der Läufer wie iniconfig mitsplitlines, also auch an Seitenvorschub und Unicode-Zeilentrennern.
Abbruch von pytest (seit 2026-10-06): Endet ein pytest-Aufruf mit Rückgabewert 3 (INTERNAL_ERROR) oder 4 (USAGE_ERROR, auch bei einem Testknoten, den es nicht gibt), hält der Läufer die letzten 20 nicht leeren Zeilen von stdout und stderr fest. In der Grundlinie wird daraus je Interpreter ein Befund Grundlinie: pytest-Bedienfehler (Rueckgabewert 4) mit <python>; Ende der Ausgabe: … (bei 3: pytest-Abbruch mit internem Fehler) statt einer FEHLT-Zeile je Test; der Bericht führt die Tests mit FEHLT und der Meldung „pytest abgebrochen, siehe Befunde“. Unter einem Mutanten tragen die Tests ohne Bericht und ohne Sammelfehler eine kurze Fassung als Meldung, die Ursache vorn, damit sie in die 120 Zeichen der Mutantenzeile passt: pytest-Bedienfehler (Rueckgabewert 4): <zeile>. Die Zeile ist bei 4 die erste mit „error“ außer der Gebrauchszeile ERROR: usage: von argparse, bei 3 die letzte mit INTERNALERROR> (danach steht noch die Summenzeile no tests ran …, Treiber-Fall 46), ohne Treffer die letzte überhaupt. Die lange Fassung steht im Bericht beim Test unter ausgabe. Diese Tests machen den Mutanten wie bisher UNKLAR. Ein Sammelfehler bleibt ROT, auch wenn pytest dabei mit 4 endet: Ein Knoten in einer Datei, die sich nicht sammeln lässt, ergibt zusätzlich found no collectors for … (Treiber-Fall 11, am 2026-10-06 an einer ersten Fassung gesehen, die jeden Abbruch als UNKLAR zählte).
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, außer pytest ist für diesen Interpreter abgebrochen; dann steht der eine Befund aus „Abbruch von pytest“.
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 |
Modus-Zeile (seit 2026-09-28, Nachbesserung N5): Der Wrapper mutanten_lauf_kopie.sh schreibt vor dem eigentlichen Lauf eine eigene Zeile, unabhängig von mutanten_lauf.py selbst: modus: voll oder modus: muster (nur Musterpruefung, kein Mutant gefahren). Damit weiß eine Linse, die nur die Rohausgabe sieht, ob überhaupt ein Mutant gefahren ist, bevor sie ein OK bewertet.
SIGTERM (seit 2026-09-28, Nachbesserung N4): mutanten_lauf.py registriert einen eigenen Handler für SIGTERM, der eine Ausnahme auslöst, damit die laufenden finally-Blöcke greifen: der zuletzt angewendete Mutant wird per SHA-Probe zurückgesetzt, ein laufender pytest-Prozess wird über seine Prozessgruppe beendet, und die Temp-Ordner (mutanten_lauf_home_*, mutanten_lauf_plugin_*) sowie die Ergebnisdatei werden geräumt. Vor dem Ende steht die Zeile ABBRUCH: SIGTERM, Mutant <id> zurueckgesetzt (oder ohne den Mutant-Teil, wenn beim Signal noch keiner lief). Der Exit-Code ist in diesem Fall 3. Geprüft durch Treiber-Fall 35.
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", "ausgabe" (nur bei einem Abbruch von pytest)}]}], "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, Abschnitt 4).
5 Abdichtung
Jeder pytest-Aufruf bekommt eine Kopie der Umgebung mit diesen Änderungen:
HOMEzeigt auf einen frischen, leeren Temp-Ordner. Zugänge, die unter~/.configliegen (Scharfschalter, IBKR-Live-Zugang, Scalable), sind damit nicht erreichbar.http_proxy,https_proxy,all_proxyund dieselben in Großschrift zeigen aufhttp://127.0.0.1:9;no_proxyundNO_PROXYwerden entfernt. Bibliotheken, die Proxy-Variablen lesen, laufen damit gegen einen geschlossenen Port.PYTHONDONTWRITEBYTECODE=1,COLUMNS=300,MUTANTEN_LAUF_ERGEBNIS=<temp-datei>,PYTHONPATHmit dem Plugin-Ordner vorn.
Netzsperre (seit 2026-09-28, Nachbesserung N1): Der Wrapper mutanten_lauf_kopie.sh startet mutanten_lauf.py selbst unter unshare --map-current-user --net, nicht erst den einzelnen pytest-Aufruf. Gemessen in WSL Debian als Benutzer claude (util-linux 2.41.5): Die UID bleibt innen wie außen 1000, es gibt nur die Schnittstelle lo, connect auf eine Adresse außerhalb von localhost endet mit OSError (errno 101) oder gaierror. Nicht -r (macht UID 0, könnte Rechte-Tests der Mutanten selbst verfälschen). Fehlt unshare ohne Netz, bricht der Wrapper vorher mit MUTANTEN_LAUF_FEHLER: unshare ohne Netz nicht moeglich ab, statt einen ungesperrten Lauf durchzulassen. Seit 2026-10-09 (Paket RB3 der Trading Plattform): lo ist im Namensraum oben. Der Wrapper startet unshare … --keep-caps, fährt lo mit ip hoch und geht über setpriv (alle vererbbaren und ambienten Fähigkeiten abgeworfen) per exec in den Läufer über. Eine Vorprüfung verlangt ip, setpriv, python3, ein nutzbares lo und CapEff und CapAmb gleich 0, sonst MUTANTEN_LAUF_FEHLER und Exit 2. Eine Verbindung auf 127.0.0.1 erreicht damit Dienste im Namensraum, nicht die des Rechners; nach außen bleibt es bei errno 101. Treiber: mutanten_lauf_kopie_treiber.py (W1 bis W13), Plan docs/plans/2026-10-09-mutanten-kopie-loopback.md und seine Nachbesserung 1. Die Fähigkeiten prüft der Wrapper nur an der Probe (CapEff, CapAmb), nicht am Läufer. Weil die Vorprüfung vor der Suche nach mutanten.toml steht, endet er ohne ip, setpriv, python3 oder nutzbares lo auch ohne mutanten.toml mit Exit 2 statt mit KEINE_MUTANTEN_TOML.
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]; für die Fälle 36 bis 42 je ein eigenes als Unterordner k/ eines Rahmen-Ordners), 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 |
| 35 | ein Mutant hängt, der Läufer bekommt nach drei Sekunden SIGTERM (eigenes TMPDIR), seit 2026-09-28 |
Zeile ABBRUCH: SIGTERM, kein Prozess mit HOME im Fall-Tmpdir, keine mutanten_lauf_*-Reste, Zieldatei byte-gleich |
| 36 | Wurzel ohne eigene Konfiguration, darüber eine fremde pyproject.toml mit addopts = "--new-first --failed-first" (der Fall der Plankritik 2 von C1), seit 2026-10-06 |
Exit 0, zweimal OK, Kopfzeile „keine in der Wurzel, leere Datei“ |
| 37 | Wurzel mit eigener pyproject.toml (Marker integration abgewählt), darüber dieselbe fremde |
sauberer Fall Exit 0 mit Kopfzeile k/pyproject.toml (aus der Wurzel); Test mit Marker in der Grundlinie Exit 2 und ABGEWAEHLT, die eigene Konfiguration gilt also |
| 38 | eigene Konfiguration mit addopts = "--gibt-es-nicht" |
Exit 2, pytest-Bedienfehler (Rueckgabewert 4) und --gibt-es-nicht aus der pytest-Ausgabe, keine Zeile ist FEHLT |
| 39 | Wurzel ohne eigene Konfiguration, darüber ein conftest.py, das beim Laden abbricht |
Exit 0, zweimal OK; prüft --confcutdir |
| 40 | ein Mutant schreibt in die eigene pyproject.toml addopts = "--gibt-es-nicht" |
Exit 1; die Mutantenzeile selbst trägt UNKLAR, pytest-Bedienfehler (Rueckgabewert 4) und --gibt-es-nicht; im Bericht steht beim Test ausgabe mit „Ende der Ausgabe:“ |
| 41 | Wurzel über einen Symlink angegeben (link/k → echt/k), seit der Nachbesserung vom 2026-10-06 |
Exit 0, zweimal OK |
| 42 | ein conftest.py der Wurzel wirft in pytest_collection_modifyitems |
Exit 2, pytest-Abbruch mit internem Fehler (Rueckgabewert 3) und der Text der Ausnahme, keine Zeile ist FEHLT |
| 43 | mutanten_lauf_probe_konfig.py: 26 Aufbauten gegen determine_setup und gegen echte pytest --co-Läufe, mit Selbstprüfung der Voraussetzung |
Exit 0, abweichungen: 0 |
| 44 | zwei Interpretergruppen (python_je_datei), nur in einer ein Knoten, den es nicht gibt; diese Gruppe einmal an erster, einmal an letzter Stelle |
je Exit 2, genau ein Befund mit dem Interpreter dieser Gruppe, keiner für die andere, keine Zeile ist FEHLT; genau zwei Kopfzeilen, je eine je Interpreter |
| 45 | ein Knoten mit einem Dateinamen über 255 Zeichen | Exit 2 mit einem Befund Grundlinie: pytest-…, kein unerwarteter Abbruch |
| 46 | ein Mutant lässt ein conftest.py der Wurzel in pytest_collection_modifyitems werfen |
Exit 1, die Mutantenzeile trägt UNKLAR, pytest-Abbruch mit internem Fehler (Rueckgabewert 3): und den Text der Ausnahme; im Bericht ausgabe mit „Ende der Ausgabe:“ |
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).