"""Mutantenlauf Frontend auf der materialisierten Kopie (Windows-Python, vitest je Datei). Je Mutant: Datei als Bytes sichern, genau eine Stelle ersetzen, die Testdatei fahren, zurueckspielen. Erwartet ist ROT; ein gruener Mutant ist ein Befund. Zuerst die Grundlinie. """ import subprocess import sys from pathlib import Path DASH = Path(sys.argv[1]) / "dashboard" # Windows-Pfad der Kopie mit dem angewendeten Plan MUTANTEN = [ ( "stufe() gibt den rohen Index zurueck", "src/components/LaufKampagne.tsx", " const wert = aktion - (verteilung.length - 1) / 2;", " const wert = aktion;", "src/components/LaufKampagne.test.tsx", ), ( "stufe() ohne Pruefung der Verteilung", "src/components/LaufKampagne.tsx", " if (!Array.isArray(verteilung) || verteilung.length % 2 === 0) return STRICH;", " if (!Array.isArray(verteilung)) return STRICH;", "src/components/LaufKampagne.test.tsx", ), ( "stufe() ohne Pruefung des Bereichs", "src/components/LaufKampagne.tsx", " if (aktion < 0 || aktion >= verteilung.length) return STRICH;\n", "", "src/components/LaufKampagne.test.tsx", ), ( "Stille Fenster als Ertrag formatiert", "src/components/LaufKampagne.tsx", " wert={anteil(abschluss.anteil_fenster_ohne_trade)}", " wert={prozent(abschluss.anteil_fenster_ohne_trade)}", "src/components/LaufKampagne.test.tsx", ), ( "close() merkt sich das Ende nicht", "src/lib/api.ts", " beendet = true;\n", "", "src/lib/api.test.ts", ), ( "close() raeumt den Zeitgeber nicht", "src/lib/api.ts", " if (zeitgeber !== null) clearTimeout(zeitgeber);\n", "", "src/lib/api.test.ts", ), ( # Der Backslash kommt aus chr(92): Eine Escape-Folge im eigenen Quelltext wertet sonst # irgendeine Schicht aus -- genau das ist bei der Umsetzung passiert. "Lizenztext mit doppeltem Backslash (Escape-Folge als Klartext auf der Seite)", "src/components/Markt.tsx", "Charts" + chr(92) + "u2122 Copyright (" + chr(92) + "u0441) 2025", "Charts" + chr(92) * 2 + "u2122 Copyright (" + chr(92) * 2 + "u0441) 2025", "src/components/Markt.test.tsx", ), ] def fahre(testdatei: str) -> tuple[bool, str]: ergebnis = subprocess.run( f"npx vitest run {testdatei}", cwd=DASH, shell=True, capture_output=True, text=True, encoding="utf-8", errors="replace", stdin=subprocess.DEVNULL, ) ausgabe = ergebnis.stdout + ergebnis.stderr rote = [z.strip() for z in ausgabe.splitlines() if z.strip().startswith(("×", "FAIL", "✗")) or " > " in z and "FAIL" in z] return ergebnis.returncode == 0, "; ".join(rote[:4]) def main() -> int: befunde = 0 for testdatei in sorted({m[4] for m in MUTANTEN}): gruen, _ = fahre(testdatei) if not gruen: print(f"BEFUND Grundlinie: {testdatei} ist ohne Mutant nicht gruen") return 2 print("Grundlinie gruen.") for name, datei, alt, neu, testdatei in MUTANTEN: pfad = DASH / 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, rote = fahre(testdatei) finally: pfad.write_bytes(sicherung) if gruen: print(f"BEFUND Mutant '{name}' bleibt GRUEN ({testdatei})") befunde += 1 else: print(f"rot {name} [{rote}]") print(f"{befunde} Befund(e).") return 2 if befunde else 0 if __name__ == "__main__": sys.exit(main())