Files
claude-regelwerk/.vault-tools/plan-kopie/mutanten_lauf_treiber.py
T

1045 lines
34 KiB
Python

#!/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())