Erster Stand des öffentlichen Ausschnitts aus dem Vault. Die .gitignore arbeitet als Positivliste: ausgeschlossen ist alles, eingeschlossen nur, was ausdrücklich wieder aufgenommen wird. Notizen, Projekte, Finanzen, das Sitzungsgedächtnis und settings.local.json bleiben damit außen vor. Der Kundenname in AI/Regelwerk/seo.md ist durch eine neutrale Bezeichnung ersetzt; die Regeln selbst sind unverändert. Co-Authored-By: Claude Opus 5 <[email protected]>
166 lines
7.0 KiB
Python
166 lines
7.0 KiB
Python
"""Mutantenlauf auf der materialisierten Kopie: Schlaegt jeder neue Test an, wenn sein Gegenstand kaputt ist?
|
|
|
|
Laeuft in WSL; Aufruf: python mutanten_py.py <wsl-pfad-der-kopie>. Je Mutant: Datei als Bytes sichern, genau eine Stelle ersetzen, die genannten Tests
|
|
fahren, Datei aus der Sicherung zurueckspielen. Erwartet ist ROT; ein gruener Mutant ist ein Befund.
|
|
Zuerst die Grundlinie: Ohne Mutant muessen dieselben Tests gruen sein.
|
|
"""
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
MAT = Path(sys.argv[1]) # WSL-Pfad der Kopie mit dem angewendeten Plan
|
|
PY = str(Path.home() / "venvs/trading/bin/python")
|
|
|
|
MUTANTEN = [
|
|
(
|
|
"lies_lauf trennt mit der Zeilentrennung von str",
|
|
"trading/dashboard/laeufe.py",
|
|
' with pfad.open("rb") as strom:\n for roh in strom:\n if not roh.strip():',
|
|
' with pfad.open("rb") as strom:\n for roh in [z.encode("utf-8") for z in strom.read().decode("utf-8").splitlines()]:\n if not roh.strip():',
|
|
["tests/test_dashboard_laeufe.py::test_die_einzelansicht_trennt_nicht_an_u2028"],
|
|
),
|
|
(
|
|
"_ist_protokolldatei laesst alles durch",
|
|
"trading/dashboard/laeufe.py",
|
|
" return pfad.is_file() and not pfad.is_symlink()",
|
|
" return True",
|
|
[
|
|
"tests/test_dashboard_laeufe.py::test_ein_ordner_mit_der_endung_jsonl_ist_kein_lauf",
|
|
"tests/test_dashboard_laeufe.py::test_eine_symbolische_verknuepfung_ist_kein_lauf",
|
|
],
|
|
),
|
|
(
|
|
"_ist_protokolldatei folgt Verknuepfungen",
|
|
"trading/dashboard/laeufe.py",
|
|
" return pfad.is_file() and not pfad.is_symlink()",
|
|
" return pfad.is_file()",
|
|
["tests/test_dashboard_laeufe.py::test_eine_symbolische_verknuepfung_ist_kein_lauf"],
|
|
),
|
|
(
|
|
"letzte_zeilen gibt ein Bruchstueck ohne Trenner zurueck",
|
|
"trading/dashboard/laeufe.py",
|
|
" if not trenner:\n return []",
|
|
" if not trenner:\n return [_bruchstueck]",
|
|
["tests/test_dashboard_laeufe.py::test_eine_letzte_zeile_ueber_dem_block_ohne_zeilenende_liefert_nichts"],
|
|
),
|
|
(
|
|
"Eimeranfang mit festem Stundenschritt",
|
|
"trading/dashboard/markt.py",
|
|
" anfang_min = HANDELSSTART_MIN + nummer * aufloesung_min",
|
|
" anfang_min = HANDELSSTART_MIN + nummer * 60",
|
|
["tests/test_dashboard_markt.py::test_15_und_5_minuten_teilen_den_handelstag_ohne_rest"],
|
|
),
|
|
(
|
|
"Tagesachse in der Achsenzone statt in der Handelszone",
|
|
"trading/dashboard/markt.py",
|
|
" return zeitpunkt.tz_convert(HANDELSZONE).date().isoformat()",
|
|
" return zeitpunkt.tz_convert(zone).date().isoformat()",
|
|
["tests/test_dashboard_markt.py::test_eine_tageskerze_bekommt_den_new_yorker_kalendertag_als_string"],
|
|
),
|
|
(
|
|
"Tagesachse in UTC",
|
|
"trading/dashboard/markt.py",
|
|
" return zeitpunkt.tz_convert(HANDELSZONE).date().isoformat()",
|
|
" return zeitpunkt.date().isoformat()",
|
|
["tests/test_dashboard_markt.py::test_eine_tageskerze_bekommt_den_new_yorker_kalendertag_als_string"],
|
|
),
|
|
(
|
|
"Handelstag mit 391 Kerzen",
|
|
"trading/dashboard/markt.py",
|
|
"KERZEN_JE_HANDELSTAG = 390",
|
|
"KERZEN_JE_HANDELSTAG = 391",
|
|
["tests/test_dashboard_markt.py::test_der_handelstag_hat_390_minuten_und_die_lesespanne_folgt_daraus"],
|
|
),
|
|
(
|
|
"Kalenderfaktor 1,5 statt 1,6",
|
|
"trading/dashboard/markt.py",
|
|
"KALENDERFAKTOR_MINUTEN = 1.6",
|
|
"KALENDERFAKTOR_MINUTEN = 1.5",
|
|
["tests/test_dashboard_markt.py::test_der_handelstag_hat_390_minuten_und_die_lesespanne_folgt_daraus"],
|
|
),
|
|
(
|
|
"lies_kerzen liest die Metadaten ungeschuetzt",
|
|
"trading/dashboard/markt.py",
|
|
" meta = _meta(katalog, bar_type)\n bis = _bis(ende, meta)",
|
|
" meta = katalog.meta(bar_type)\n bis = _bis(ende, meta)",
|
|
["tests/test_dashboard_markt.py::test_lies_kerzen_uebersteht_eine_unlesbare_metadatei"],
|
|
),
|
|
(
|
|
"katalog() prueft nur die Wurzel",
|
|
"trading/dashboard/app.py",
|
|
' if not (wurzel / "_meta").is_dir():',
|
|
" if not wurzel.is_dir():",
|
|
["tests/test_dashboard_endpunkte.py::test_ein_ordner_ohne_meta_ist_kein_katalog_und_bekommt_keines"],
|
|
),
|
|
(
|
|
"_ersetze laesst den alten Snapshot liegen",
|
|
"trading/dashboard/verteiler.py",
|
|
" if warteschlange.full():\n try:",
|
|
" if warteschlange.full():\n return\n if warteschlange.full():\n try:",
|
|
["tests/test_dashboard_verteiler.py::test_eine_volle_warteschlange_bekommt_den_neuen_snapshot"],
|
|
),
|
|
(
|
|
"killswitch_uebung prueft den Schalter erst nach der Konfiguration",
|
|
"scripts/killswitch_uebung.py",
|
|
" if not parse_args().ausfuehren:\n print(OHNE_SCHALTER, file=sys.stderr)\n return 2\n\n limits = lade_limits(\"config/risk.toml\")",
|
|
" limits = lade_limits(\"config/risk.toml\")\n if not parse_args().ausfuehren:\n print(OHNE_SCHALTER, file=sys.stderr)\n return 2\n",
|
|
[
|
|
"tests/test_skripte_starten.py::test_die_killswitch_uebung_ohne_schalter_endet_mit_exit_2",
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
GRUND = ""
|
|
|
|
|
|
def fahre(tests: list[str]) -> bool:
|
|
"""Exitcode allein ist kein Beleg: Ein Mutant mit Syntaxfehler waere genauso rot.
|
|
Deshalb wird die erste Fehlerzeile mitgeschrieben und in der Ausgabe gezeigt."""
|
|
global GRUND
|
|
ergebnis = subprocess.run(
|
|
[PY, "-m", "pytest", "-q", "-p", "no:cacheprovider", "-x", "--tb=short", *tests],
|
|
cwd=MAT,
|
|
capture_output=True,
|
|
text=True,
|
|
stdin=subprocess.DEVNULL,
|
|
)
|
|
zeilen = [z.strip() for z in ergebnis.stdout.splitlines() if z.startswith("E ")]
|
|
GRUND = " | ".join(zeilen[:2])[:260]
|
|
return ergebnis.returncode == 0
|
|
|
|
|
|
def main() -> int:
|
|
befunde = 0
|
|
alle = sorted({t for m in MUTANTEN for t in m[4]})
|
|
if not fahre(alle):
|
|
print("BEFUND Grundlinie: ohne Mutant sind nicht alle Tests gruen -- der Lauf misst nichts.")
|
|
return 2
|
|
print(f"Grundlinie gruen ({len(alle)} Tests).")
|
|
for name, datei, alt, neu, tests in MUTANTEN:
|
|
pfad = MAT / datei
|
|
sicherung = pfad.read_bytes()
|
|
text = sicherung.decode("utf-8")
|
|
anzahl = text.count(alt)
|
|
if anzahl != 1:
|
|
print(f"BEFUND Mutant '{name}': Stelle steht {anzahl}-mal in {datei}")
|
|
befunde += 1
|
|
continue
|
|
try:
|
|
pfad.write_bytes(text.replace(alt, neu).encode("utf-8"))
|
|
gruen = fahre(tests)
|
|
finally:
|
|
pfad.write_bytes(sicherung)
|
|
if gruen:
|
|
print(f"BEFUND Mutant '{name}' bleibt GRUEN: {tests}")
|
|
befunde += 1
|
|
else:
|
|
print(f"rot {name}\n {GRUND}")
|
|
print(f"{befunde} Befund(e).")
|
|
return 2 if befunde else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|