#!/usr/bin/env python3 """Treiber fuer mutanten_lauf.py: 35 Angriffsfaelle auf einer Wegwerfkopie. Zweck: Baut in einem Temp-Ordner unter /tmp ein kleines Testprojekt, fuehrt mutanten_lauf.py je Fall mit einer eigenen mutanten.toml aus und vergleicht Exit-Code und ein Meldungsstueck gegen die Erwartung aus dem Entwurf, Abschnitt 6. Nach jedem Fall wird geprueft, dass jede Datei des kleinen Projekts wieder byte-gleich mit dem Ausgangsstand ist. Aufruf: python mutanten_lauf_treiber.py Baut nichts ausserhalb von /tmp, geht nicht ins Netz, raeumt am Ende auf. Spezifikation: .vault-tools/plan-kopie/mutanten_lauf_ENTWURF.md, Abschnitt 6. Fall 35 (Nachbesserung 2026-09-28, N11) prueft das Aufraeumen bei SIGTERM: Ein Mutant haengt, der Laeufer bekommt nach wenigen Sekunden selbst ein SIGTERM. Damit die Pruefung nur die Reste dieses einen Falls sieht, bekommt der Laeufer dafuer ein eigenes TMPDIR (tempfile respektiert die Umgebungsvariable). """ import hashlib import os import shutil import signal import subprocess import sys import tempfile import time from pathlib import Path HIER = Path(__file__).resolve().parent LAEUFER = HIER / "mutanten_lauf.py" INTERPRETER = "/home/claude/venvs/trading/bin/python" FEHLERLISTE = [] def melde(fallname, ok, zusatz=""): if ok: print("{0} ok".format(fallname)) else: print("{0} FEHLER: {1}".format(fallname, zusatz)) FEHLERLISTE.append(fallname) def sha_von(pfad): return hashlib.sha256(pfad.read_bytes()).hexdigest() def snapshot(dateien): return {p: sha_von(p) for p in dateien} def pruefe_unveraendert(dateien, snap, fallname): for p in dateien: if not p.exists(): melde(fallname + " (rueckstellung)", False, "Datei fehlt nach dem Lauf: {0}".format(p)) return if sha_von(p) != snap[p]: melde(fallname + " (rueckstellung)", False, "Datei nicht byte-gleich zurueckgesetzt: {0}".format(p)) return def starte_laeufer(basis, toml_pfad, zusatzargumente=None, bericht_pfad=None, zeitlimit=None): cmd = [INTERPRETER, str(LAEUFER), "--wurzel", str(basis), "--mutanten", str(toml_pfad)] if zeitlimit is not None: cmd += ["--zeitlimit", str(zeitlimit)] if bericht_pfad is not None: cmd += ["--bericht", str(bericht_pfad)] if zusatzargumente: cmd += zusatzargumente return subprocess.run(cmd, capture_output=True, text=True, timeout=120) def schreibe_toml(pfad, text): pfad.write_text(text, encoding="utf-8") def enthaelt(text, teilstueck): return teilstueck in (text or "") # --- Projektaufbau ---------------------------------------------------- def baue_projekt(basis): (basis / "paket").mkdir(parents=True) (basis / "tests").mkdir(parents=True) (basis / "paket" / "__init__.py").write_text("", encoding="utf-8") (basis / "paket" / "modul.py").write_text( "def f1(x):\n" " if x > 0:\n" " return True\n" " return False\n" "\n" "\n" "def f2(y):\n" " if y >= 10:\n" " return \"gross\"\n" " return \"klein\"\n" "\n" "\n" "def f3(z):\n" " if z == 0:\n" " pass # platzhalter\n" " return z\n" "\n" "\n" "def f4(w):\n" " if w == 1:\n" " pass # platzhalter\n" " return w\n" "\n" "\n" "def f5(a, b):\n" " resultat = a + b\n" " return resultat\n", encoding="utf-8", ) (basis / "paket" / "modul_syntax.py").write_text( "def f6(n):\n" " return n * 2\n", encoding="utf-8", ) (basis / "paket" / "modul_zeit.py").write_text( "def f7(n):\n" " return n + 1\n", encoding="utf-8", ) (basis / "paket" / "modul_crlf.py").write_bytes( b"def f8(n):\r\n return n\r\n", ) (basis / "paket" / "modul_binaer.py").write_bytes( b"\xff\xfe nicht utf-8 \x80\x81\n", ) (basis / "tests" / "__init__.py").write_text("", encoding="utf-8") (basis / "tests" / "test_modul.py").write_text( "from paket.modul import f1, f2, f5\n" "\n" "\n" "def test_f1_positiv():\n" " assert f1(5) is True\n" "\n" "\n" "def test_f1_negativ():\n" " assert f1(-3) is False\n" "\n" "\n" "def test_f2_gross():\n" " assert f2(15) == \"gross\"\n" "\n" "\n" "def test_f2_klein():\n" " assert f2(1) == \"klein\"\n" "\n" "\n" "def test_f5_summe():\n" " assert f5(2, 3) == 5\n", encoding="utf-8", ) (basis / "tests" / "test_modul_syntax.py").write_text( "from paket.modul_syntax import f6\n" "\n" "\n" "def test_f6():\n" " assert f6(3) == 6\n", encoding="utf-8", ) (basis / "tests" / "test_zeit.py").write_text( "from paket.modul_zeit import f7\n" "\n" "\n" "def test_f7():\n" " assert f7(1) == 2\n", encoding="utf-8", ) (basis / "tests" / "test_faelle.py").write_text( "import pytest\n" "\n" "\n" "def test_immer_rot():\n" " assert False\n" "\n" "\n" "def test_immer_uebersprungen():\n" " pytest.skip(\"fuer den Treiber-Fall 14\")\n" "\n" "\n" "@pytest.mark.integration\n" "def test_wird_abgewaehlt():\n" " assert True\n", encoding="utf-8", ) (basis / "tests" / "test_abdichtung.py").write_text( "import os\n" "\n" "\n" "def test_abdichtung_home_und_proxy():\n" " home = os.environ.get(\"HOME\", \"\")\n" " proxy = os.environ.get(\"https_proxy\", \"\")\n" " assert \"mutanten_lauf_home_\" in home and proxy == \"http://127.0.0.1:9\"\n", encoding="utf-8", ) (basis / "pyproject.toml").write_text( "[tool.pytest.ini_options]\n" "addopts = '-m \"not integration\"'\n" "markers = [\n" " \"integration: wird vom Treiber absichtlich abgewaehlt\",\n" "]\n", encoding="utf-8", ) getrackte_dateien = [ basis / "pyproject.toml", basis / "paket" / "__init__.py", basis / "paket" / "modul.py", basis / "paket" / "modul_syntax.py", basis / "paket" / "modul_zeit.py", basis / "paket" / "modul_crlf.py", basis / "paket" / "modul_binaer.py", basis / "tests" / "__init__.py", basis / "tests" / "test_modul.py", basis / "tests" / "test_modul_syntax.py", basis / "tests" / "test_zeit.py", basis / "tests" / "test_faelle.py", basis / "tests" / "test_abdichtung.py", ] return getrackte_dateien # --- TOML-Bausteine ----------------------------------------------------- def kopf(python=INTERPRETER, zusatz=""): return "format = 1\npython = \"{0}\"\n{1}\n".format(python, zusatz) def mutant_d1_d2(): return ( kopf() + "[[mutant]]\n" "id = \"D1\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return True\n" "'''\n" "wird = '''\n" " return False\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" "\n" "[[mutant]]\n" "id = \"D2\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return \"gross\"\n" "'''\n" "wird = '''\n" " return \"klein\"\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f2_gross\"]\n" ) def mutant_luecke(id_, rot_bei_liste): rot_bei_text = ", ".join('"{0}"'.format(x) for x in rot_bei_liste) return ( "[[mutant]]\n" "id = \"{0}\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return True\n" "'''\n" "wird = '''\n" " return False\n" "'''\n" "rot_bei = [{1}]\n" ).format(id_, rot_bei_text) def mutant_korrekt(id_): return ( "[[mutant]]\n" "id = \"{0}\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return \"gross\"\n" "'''\n" "wird = '''\n" " return \"klein\"\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f2_gross\"]\n" ).format(id_) # --- Faelle --------------------------------------------------------------- def fall_01(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, mutant_d1_d2()) ergebnis = starte_laeufer(basis, toml) ok = ( ergebnis.returncode == 0 and ergebnis.stdout.count(" OK ") >= 2 and "D1 OK" in ergebnis.stdout and "D2 OK" in ergebnis.stdout ) melde("Fall 1", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_02(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, kopf() + mutant_luecke("L1", ["tests/test_modul.py::test_f2_gross", "tests/test_modul.py::test_f1_positiv"])) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 1 and "L1 LUECKE" in ergebnis.stdout melde("Fall 2", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_03(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, kopf() + mutant_luecke("L2", ["tests/test_modul.py::test_f1_positiv", "tests/test_modul.py::test_f2_gross"])) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 1 and "L2 LUECKE" in ergebnis.stdout melde("Fall 3", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_04(basis, projektpfad): toml_a = projektpfad / "mutanten_4a.toml" schreibe_toml(toml_a, kopf() + mutant_luecke("L3", ["tests/test_modul.py::test_f2_gross", "tests/test_modul.py::test_f1_positiv"]) + "\n" + mutant_korrekt("K3")) ergebnis_a = starte_laeufer(basis, toml_a) ok_a = ( ergebnis_a.returncode == 1 and "L3 LUECKE" in ergebnis_a.stdout and "K3 OK" in ergebnis_a.stdout ) toml_b = projektpfad / "mutanten_4b.toml" schreibe_toml(toml_b, kopf() + mutant_korrekt("K4") + "\n" + mutant_luecke("L4", ["tests/test_modul.py::test_f2_gross", "tests/test_modul.py::test_f1_positiv"])) ergebnis_b = starte_laeufer(basis, toml_b) ok_b = ( ergebnis_b.returncode == 1 and "L4 LUECKE" in ergebnis_b.stdout and "K4 OK" in ergebnis_b.stdout ) melde("Fall 4", ok_a and ok_b, "a: exit={0}\n{1}\nb: exit={2}\n{3}".format( ergebnis_a.returncode, ergebnis_a.stdout, ergebnis_b.returncode, ergebnis_b.stdout)) def fall_05(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M5\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return None\n" "'''\n" "wird = '''\n" " return False\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" )) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 1 and "M5 MUSTER" in ergebnis.stdout and "Trefferzahl=0" in ergebnis.stdout melde("Fall 5", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_06(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M6\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " pass # platzhalter\n" "'''\n" "wird = '''\n" " pass\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" )) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 1 and "M6 MUSTER" in ergebnis.stdout and "Trefferzahl=2" in ergebnis.stdout melde("Fall 6", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_07(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M7\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return True\n" "'''\n" "wird = '''\n" " return False\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" )) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 1 and "M7 MUSTER" in ergebnis.stdout and "Trefferzahl=0" in ergebnis.stdout melde("Fall 7", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_08(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M8\"\n" "datei = \"paket/modul_crlf.py\"\n" "aus = '''\n" " return n\n" "'''\n" "wird = '''\n" " return 0\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" )) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 1 and "M8 MUSTER" in ergebnis.stdout and "Datei hat CR" in ergebnis.stdout melde("Fall 8", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_09(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M9\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " if y >= 10:\n" " return \"gross\"\n" "'''\n" "wird = '''\n" " if y >= 10:\n" " return \"mittel\"\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f2_gross\"]\n" )) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 0 and "M9 OK" in ergebnis.stdout melde("Fall 9", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_10(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M10\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " resultat = a + b\n" "'''\n" "wird = \"\"\n" "rot_bei = [\"tests/test_modul.py::test_f5_summe\"]\n" )) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 0 and "M10 OK" in ergebnis.stdout melde("Fall 10", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_11(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M11\"\n" "datei = \"paket/modul_syntax.py\"\n" "aus = '''\n" " return n * 2\n" "'''\n" "wird = '''\n" " return n *\n" "'''\n" "rot_bei = [\"tests/test_modul_syntax.py::test_f6\"]\n" )) ergebnis = starte_laeufer(basis, toml) ok = ( ergebnis.returncode == 0 and "M11 OK" in ergebnis.stdout and "Sammelfehler" in ergebnis.stdout ) melde("Fall 11", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_12(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M12\"\n" "datei = \"paket/modul_zeit.py\"\n" "aus = '''\n" " return n + 1\n" "'''\n" "wird = '''\n" " while True:\n" " pass\n" "'''\n" "rot_bei = [\"tests/test_zeit.py::test_f7\"]\n" )) ergebnis = starte_laeufer(basis, toml, zeitlimit=5) ok = ergebnis.returncode == 1 and "M12 ZEIT" in ergebnis.stdout melde("Fall 12", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_13(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, mutant_luecke("M13", ["tests/test_faelle.py::test_immer_rot"])) schreibe_toml(toml, kopf() + mutant_luecke("M13", ["tests/test_faelle.py::test_immer_rot"])) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 2 melde("Fall 13", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_14(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, kopf() + mutant_luecke("M14", ["tests/test_faelle.py::test_immer_uebersprungen"])) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 2 melde("Fall 14", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_15(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, kopf() + mutant_luecke("M15", ["tests/test_faelle.py::test_wird_abgewaehlt"])) ergebnis = starte_laeufer(basis, toml) ausgabe = ergebnis.stdout + ergebnis.stderr ok = ergebnis.returncode == 2 and ("abgew" in ausgabe.lower() or "ABGEWAEHLT" in ausgabe) melde("Fall 15", ok, "exit={0}\n{1}".format(ergebnis.returncode, ausgabe)) def fall_16(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, kopf() + mutant_luecke("M16", ["tests/test_modul.py::test_gibt_es_nicht"])) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 2 melde("Fall 16", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_17(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, mutant_d1_d2()) git_ordner = basis / ".git" git_ordner.mkdir() ergebnis_ordner = starte_laeufer(basis, toml) git_ordner.rmdir() ok_ordner = ergebnis_ordner.returncode == 2 git_datei = basis / ".git" git_datei.write_text("kein echtes repo", encoding="utf-8") ergebnis_datei = starte_laeufer(basis, toml) git_datei.unlink() ok_datei = ergebnis_datei.returncode == 2 melde("Fall 17", ok_ordner and ok_datei, "ordner exit={0}, datei exit={1}".format( ergebnis_ordner.returncode, ergebnis_datei.returncode)) def fall_18(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, kopf(zusatz="ohne = [\"geheim/geheimnis.toml\"]\n") + mutant_d1_d2().split("\n", 2)[-1]) geheim_ordner = basis / "geheim" geheim_ordner.mkdir() (geheim_ordner / "geheimnis.toml").write_text("x = 1\n", encoding="utf-8") ergebnis = starte_laeufer(basis, toml) shutil.rmtree(geheim_ordner) ok = ergebnis.returncode == 2 melde("Fall 18", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_19(basis, projektpfad): toml_a = projektpfad / "mutanten_19a.toml" schreibe_toml(toml_a, ( kopf() + "[[mutant]]\n" "id = \"M19a\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return True\n" "'''\n" "wird = '''\n" " return False\n" "'''\n" "rotbei = [\"tests/test_modul.py::test_f1_positiv\"]\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" )) ergebnis_a = starte_laeufer(basis, toml_a) ok_a = ergebnis_a.returncode == 2 toml_b = projektpfad / "mutanten_19b.toml" schreibe_toml(toml_b, "unbekannt = 1\n" + mutant_d1_d2()) ergebnis_b = starte_laeufer(basis, toml_b) ok_b = ergebnis_b.returncode == 2 melde("Fall 19", ok_a and ok_b, "a exit={0}, b exit={1}".format(ergebnis_a.returncode, ergebnis_b.returncode)) def fall_20(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, kopf() + mutant_korrekt("GLEICH") + "\n" + mutant_korrekt("GLEICH")) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 2 melde("Fall 20", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_21(basis, projektpfad): toml_a = projektpfad / "mutanten_21a.toml" schreibe_toml(toml_a, ( kopf() + "[[mutant]]\n" "id = \"M21a\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return True\n" "'''\n" "wird = '''\n" " return False\n" "'''\n" "rot_bei = []\n" )) ergebnis_a = starte_laeufer(basis, toml_a) ok_a = ergebnis_a.returncode == 2 toml_b = projektpfad / "mutanten_21b.toml" schreibe_toml(toml_b, ( kopf() + "[[mutant]]\n" "id = \"M21b\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return True\n" "'''\n" "wird = '''\n" " return False\n" "'''\n" "rot_bei = \"tests/test_modul.py::test_f1_positiv\"\n" )) ergebnis_b = starte_laeufer(basis, toml_b) ok_b = ergebnis_b.returncode == 2 melde("Fall 21", ok_a and ok_b, "a exit={0}, b exit={1}".format(ergebnis_a.returncode, ergebnis_b.returncode)) def fall_22(basis, projektpfad): varianten = { "22a (..)": "paket/../paket/modul.py", "22b (absolut)": "/etc/passwd", "22c (fehlt)": "paket/existiert_nicht.py", } alle_ok = True zusatz = [] for name, datei in varianten.items(): toml = projektpfad / "mutanten_{0}.toml".format(name.split()[0]) schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M22\"\n" "datei = \"{0}\"\n" "aus = '''\n" " return True\n" "'''\n" "wird = '''\n" " return False\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" ).format(datei)) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 2 alle_ok = alle_ok and ok zusatz.append("{0}: exit={1}".format(name, ergebnis.returncode)) melde("Fall 22", alle_ok, "; ".join(zusatz)) def fall_23(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M23\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return True\n" "'''\n" "wird = '''\n" " return False\n" "'''\n" "rot_bei = [\"UNBEKANNT::test_f1_positiv\"]\n" )) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 2 melde("Fall 23", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_24(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M24\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " return True\n" "'''\n" "wird = '''\n" " return True\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" )) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 2 melde("Fall 24", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_25(basis, projektpfad): zusatz = [] alle_ok = True toml = projektpfad / "mutanten_25a.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M25a\"\n" "datei = \"paket/modul.py\"\n" "aus = \"\"\n" "wird = '''\n" " return False\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" )) e = starte_laeufer(basis, toml) ok = e.returncode == 2 alle_ok = alle_ok and ok zusatz.append("aus leer: exit={0}".format(e.returncode)) toml = projektpfad / "mutanten_25b.toml" schreibe_toml(toml, "format = 2\npython = \"{0}\"\n\n".format(INTERPRETER) + mutant_korrekt("M25b")) e = starte_laeufer(basis, toml) ok = e.returncode == 2 alle_ok = alle_ok and ok zusatz.append("format=2: exit={0}".format(e.returncode)) toml = projektpfad / "mutanten_25c.toml" schreibe_toml(toml, "python = \"{0}\"\n\n".format(INTERPRETER) + mutant_korrekt("M25c")) e = starte_laeufer(basis, toml) ok = e.returncode == 2 alle_ok = alle_ok and ok zusatz.append("format fehlt: exit={0}".format(e.returncode)) toml = projektpfad / "mutanten_25d.toml" schreibe_toml(toml, "format = 1\npython = \"bin/python\"\n\n" + mutant_korrekt("M25d")) e = starte_laeufer(basis, toml) ok = e.returncode == 2 alle_ok = alle_ok and ok zusatz.append("python relativ: exit={0}".format(e.returncode)) toml = projektpfad / "mutanten_25e.toml" schreibe_toml(toml, "format = 1\npython = \"/nicht/vorhanden/python\"\n\n" + mutant_korrekt("M25e")) e = starte_laeufer(basis, toml) ok = e.returncode == 2 alle_ok = alle_ok and ok zusatz.append("python existiert nicht: exit={0}".format(e.returncode)) melde("Fall 25", alle_ok, "; ".join(zusatz)) def fall_26(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, "format = 1\npython = \"{0}\"\n[[mutant\n".format(INTERPRETER)) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 2 melde("Fall 26", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_27(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, kopf() + "mutant = []\n") ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 2 melde("Fall 27", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_28(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, mutant_d1_d2()) ergebnis = starte_laeufer(basis, toml, zusatzargumente=["--nur", "UNBEKANNTEID"]) ok = ergebnis.returncode == 2 melde("Fall 28", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_29(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, mutant_d1_d2()) ergebnis = starte_laeufer(basis, toml, zusatzargumente=["--nur", "D1"]) ok = ( ergebnis.returncode == 0 and "D1 OK" in ergebnis.stdout and "D2" not in ergebnis.stdout.replace("mutanten gesamt: 2, ausgewaehlt: 1", "") ) melde("Fall 29", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_30(basis, projektpfad): toml_sauber = projektpfad / "mutanten.toml" schreibe_toml(toml_sauber, mutant_d1_d2()) e1 = starte_laeufer(basis, toml_sauber, zusatzargumente=["--nur-muster"]) ok1 = e1.returncode == 0 toml_luecke = projektpfad / "mutanten_6.toml" schreibe_toml(toml_luecke, ( kopf() + "[[mutant]]\n" "id = \"M6\"\n" "datei = \"paket/modul.py\"\n" "aus = '''\n" " pass # platzhalter\n" "'''\n" "wird = '''\n" " pass\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" )) e2 = starte_laeufer(basis, toml_luecke, zusatzargumente=["--nur-muster"]) ok2 = e2.returncode == 1 melde("Fall 30", ok1 and ok2, "sauber exit={0}, fall6 exit={1}".format(e1.returncode, e2.returncode)) def fall_31(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M31\"\n" "datei = \"paket/modul_binaer.py\"\n" "aus = '''\n" " irrelevant\n" "'''\n" "wird = '''\n" " irrelevant2\n" "'''\n" "rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n" )) ergebnis = starte_laeufer(basis, toml) ausgabe = ergebnis.stdout + ergebnis.stderr ok = ergebnis.returncode in (1, 2) and ("UTF-8" in ausgabe or "utf-8" in ausgabe) melde("Fall 31", ok, "exit={0}\n{1}".format(ergebnis.returncode, ausgabe)) def fall_32(basis, projektpfad): toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M32\"\n" "datei = \"tests/test_abdichtung.py\"\n" "aus = '''\n" " assert \"mutanten_lauf_home_\" in home and proxy == \"http://127.0.0.1:9\"\n" "'''\n" "wird = '''\n" " assert False\n" "'''\n" "rot_bei = [\"tests/test_abdichtung.py::test_abdichtung_home_und_proxy\"]\n" )) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 0 and "M32 OK" in ergebnis.stdout melde("Fall 32", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) def fall_33(basis, projektpfad): import json as jsonmod toml = projektpfad / "mutanten.toml" schreibe_toml(toml, mutant_luecke("L1", ["tests/test_modul.py::test_f2_gross", "tests/test_modul.py::test_f1_positiv"])) schreibe_toml(toml, kopf() + mutant_luecke("L1", ["tests/test_modul.py::test_f2_gross", "tests/test_modul.py::test_f1_positiv"])) bericht_pfad = projektpfad / "bericht_33.json" ergebnis = starte_laeufer(basis, toml, bericht_pfad=bericht_pfad) ok = ergebnis.returncode == 1 if ok: try: daten = jsonmod.loads(bericht_pfad.read_text(encoding="utf-8")) ok = ( isinstance(daten, dict) and any(m.get("id") == "L1" and m.get("status") == "LUECKE" for m in daten.get("mutanten", [])) ) except (ValueError, OSError) as exc: ok = False ergebnis = subprocess.CompletedProcess(ergebnis.args, ergebnis.returncode, ergebnis.stdout + "\nJSON-Fehler: " + str(exc), ergebnis.stderr) melde("Fall 33", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) try: bericht_pfad.unlink() except OSError: pass def fall_34(basis, projektpfad): # Ein blosser Symlink auf die Python-Binaerdatei bricht die # Venv-Erkennung (site.py sucht pyvenv.cfg im Verzeichnis des # Symlinks, nicht im Zielverzeichnis) - pytest waere dann nicht # importierbar. Deshalb wird das ganze Venv-Verzeichnis verlinkt. alt_python_dir = Path(tempfile.mkdtemp(prefix="mutanten_lauf_fall34_")) venv_verzeichnis = Path(INTERPRETER).parent.parent venv_link = alt_python_dir / "venv_alt" alt_python = venv_link / "bin" / "python" try: os.symlink(venv_verzeichnis, venv_link) toml = projektpfad / "mutanten.toml" schreibe_toml(toml, ( kopf(zusatz="\n[python_je_datei]\n\"tests/test_modul.py\" = \"{0}\"\n".format(alt_python)) + mutant_d1_d2().split("\n", 2)[-1] )) ergebnis = starte_laeufer(basis, toml) ok = ergebnis.returncode == 0 and "D1 OK" in ergebnis.stdout and "D2 OK" in ergebnis.stdout melde("Fall 34", ok, "exit={0}\n{1}".format(ergebnis.returncode, ergebnis.stdout)) finally: shutil.rmtree(alt_python_dir, ignore_errors=True) def fall_35(basis, projektpfad): # N11: Ein Mutant haengt (Endlosschleife), der Laeufer selbst bekommt nach wenigen # Sekunden SIGTERM. Erwartet: der Laeufer raeumt auf und meldet ABBRUCH: SIGTERM, # kein pytest-Prozess dieses Falls bleibt am Leben, keine Temp-Reste bleiben liegen, # und die Zieldatei ist danach byte-gleich mit dem Ausgangsstand (generisch geprueft # durch pruefe_unveraendert im main()). toml = projektpfad / "mutanten_35.toml" schreibe_toml(toml, ( kopf() + "[[mutant]]\n" "id = \"M35\"\n" "datei = \"paket/modul_zeit.py\"\n" "aus = '''\n" " return n + 1\n" "'''\n" "wird = '''\n" " while True:\n" " pass\n" "'''\n" "rot_bei = [\"tests/test_zeit.py::test_f7\"]\n" )) eigenes_tmpdir = Path(tempfile.mkdtemp(prefix="mutanten_lauf_fall35_tmpdir_")) env = os.environ.copy() env["TMPDIR"] = str(eigenes_tmpdir) cmd = [INTERPRETER, str(LAEUFER), "--wurzel", str(basis), "--mutanten", str(toml), "--zeitlimit", "60"] ok = True zusatz = "" try: proc = subprocess.Popen(cmd, cwd=str(basis), env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) time.sleep(3) proc.send_signal(signal.SIGTERM) try: stdout, _ = proc.communicate(timeout=30) except subprocess.TimeoutExpired: proc.kill() stdout, _ = proc.communicate() ok = False zusatz += "Laeufer endete nicht nach SIGTERM; " if "ABBRUCH: SIGTERM" not in stdout: ok = False zusatz += "keine ABBRUCH-Zeile; " # kein Prozess dieses Falls lebt danach: Sein HOME liegt unter eigenes_tmpdir, # und diese Umgebungsvariable geht an den pytest-Unterprozess mit. uebrige_pids = [] for envdatei in Path("/proc").glob("[0-9]*/environ"): try: inhalt = envdatei.read_bytes() except OSError: continue if str(eigenes_tmpdir).encode() in inhalt: uebrige_pids.append(envdatei.parent.name) if uebrige_pids: ok = False zusatz += "Prozesse mit HOME im Fall-Tmpdir noch da: {0}; ".format(uebrige_pids) # keine Temp-Ordner und keine Ergebnisdatei aus diesem Lauf uebrige_ordner = ( list(eigenes_tmpdir.glob("mutanten_lauf_home_*")) + list(eigenes_tmpdir.glob("mutanten_lauf_plugin_*")) + list(eigenes_tmpdir.glob("mutanten_lauf_ergebnis_*")) ) if uebrige_ordner: ok = False zusatz += "Temp-Reste: {0}; ".format(uebrige_ordner) melde("Fall 35", ok, zusatz + "stdout(Ende)={0}".format(stdout[-500:])) finally: shutil.rmtree(eigenes_tmpdir, ignore_errors=True) FAELLE = [ fall_01, fall_02, fall_03, fall_04, fall_05, fall_06, fall_07, fall_08, fall_09, fall_10, fall_11, fall_12, fall_13, fall_14, fall_15, fall_16, fall_17, fall_18, fall_19, fall_20, fall_21, fall_22, fall_23, fall_24, fall_25, fall_26, fall_27, fall_28, fall_29, fall_30, fall_31, fall_32, fall_33, fall_34, fall_35, ] def main(): basis = Path(tempfile.mkdtemp(prefix="mutanten_lauf_treiber_")) projektpfad = basis try: getrackte_dateien = baue_projekt(basis) snap = snapshot(getrackte_dateien) for funktion in FAELLE: vor_lauf = len(FEHLERLISTE) try: funktion(basis, projektpfad) except Exception as exc: melde(funktion.__name__, False, "Ausnahme: {0}".format(exc)) pruefe_unveraendert(getrackte_dateien, snap, funktion.__name__) anzahl_fehler = len(FEHLERLISTE) print("gerechnete Fehler: {0}".format(anzahl_fehler)) return 0 if anzahl_fehler == 0 else 1 finally: shutil.rmtree(basis, ignore_errors=True) if __name__ == "__main__": sys.exit(main())