Files
Pascal MichelandClaude Opus 5.5 ff7854cf39 Mutantenlaeufer: pytest-Konfiguration wie pytest, nie oberhalb der Wurzel; Abbruch von pytest mit Ursache statt FEHLT; Gleichwertigkeitsprobe, Treiber 46 Faelle; zwei Pruefrunden
Anlass: Plankritik 2 von C1 (After Install), fremde pyproject.toml im Windows-Temp.
Gegenprobe an sechs mutanten.toml im Vault: gleiche Ergebnisse, C1 unter Windows-Temp jetzt 69 OK statt 101 FEHLT.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 17:41:05 +02:00

1350 lines
48 KiB
Python

#!/usr/bin/env python3
"""Treiber fuer mutanten_lauf.py: 46 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).
Faelle 36 bis 40 (2026-10-06) pruefen die pytest-Konfiguration: Die Wurzel
liegt dafuer als k/ in einem eigenen Rahmen-Ordner, darueber wahlweise eine
fremde pyproject.toml mit addopts, die zu -p no:cacheprovider nicht passen
(wie im Windows-Temp bei der Plankritik 2 von C1), oder ein fremdes conftest.py.
Dazu ein Bedienfehler von pytest in der Grundlinie und unter einem Mutanten.
Faelle 41 bis 45 (Nachbesserung nach der Pruefung vom 2026-10-06): Wurzel ueber
einen Symlink, Rueckgabewert 3, die Gleichwertigkeitsprobe gegen pytest selbst
(mutanten_lauf_probe_konfig.py), ein Abbruch in nur einer von zwei
Interpretergruppen an erster und an letzter Stelle, ein ueberlanger Knoten.
Fall 46 (Nachbesserung 2): Rueckgabewert 3 unter einem Mutanten, Kurzfassung in
der Mutantenzeile und lange Fassung im Bericht.
"""
import hashlib
import json
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"
PROBE = HIER / "mutanten_lauf_probe_konfig.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 36 bis 40: pytest-Konfiguration ---------------------------------
FREMDE_KONFIG = (
"[tool.pytest.ini_options]\n"
"addopts = \"--new-first --failed-first\"\n"
)
FREMDES_CONFTEST = "raise RuntimeError(\"fremdes conftest oberhalb der Wurzel geladen\")\n"
def baue_rahmen(eigene_konfig, fremde_konfig, fremdes_conftest):
rahmen = Path(tempfile.mkdtemp(prefix="mutanten_lauf_treiber_rahmen_"))
if fremde_konfig:
(rahmen / "pyproject.toml").write_text(FREMDE_KONFIG, encoding="utf-8")
if fremdes_conftest:
(rahmen / "conftest.py").write_text(FREMDES_CONFTEST, encoding="utf-8")
wurzel = rahmen / "k"
dateien = baue_projekt(wurzel)
if not eigene_konfig:
(wurzel / "pyproject.toml").unlink()
dateien = [p for p in dateien if p != wurzel / "pyproject.toml"]
return rahmen, wurzel, dateien
def fall_36(basis, projektpfad):
rahmen, wurzel, dateien = baue_rahmen(eigene_konfig=False, fremde_konfig=True, fremdes_conftest=False)
try:
snap = snapshot(dateien)
toml = rahmen / "mutanten.toml"
schreibe_toml(toml, mutant_d1_d2())
ergebnis = starte_laeufer(wurzel, toml)
ok = (
ergebnis.returncode == 0
and "D1 OK" in ergebnis.stdout
and "D2 OK" in ergebnis.stdout
and "keine in der Wurzel, leere Datei" in ergebnis.stdout
)
melde("Fall 36", ok, "exit={0}\n{1}{2}".format(ergebnis.returncode, ergebnis.stdout, ergebnis.stderr))
pruefe_unveraendert(dateien, snap, "Fall 36")
finally:
shutil.rmtree(rahmen, ignore_errors=True)
def fall_37(basis, projektpfad):
rahmen, wurzel, dateien = baue_rahmen(eigene_konfig=True, fremde_konfig=True, fremdes_conftest=False)
try:
snap = snapshot(dateien)
toml = rahmen / "mutanten.toml"
schreibe_toml(toml, mutant_d1_d2())
sauber = starte_laeufer(wurzel, toml)
schreibe_toml(toml, kopf() + mutant_luecke("M37", ["tests/test_faelle.py::test_wird_abgewaehlt"]))
abgewaehlt = starte_laeufer(wurzel, toml)
ausgabe = abgewaehlt.stdout + abgewaehlt.stderr
ok = (
sauber.returncode == 0
and "D1 OK" in sauber.stdout
and "D2 OK" in sauber.stdout
and "k/pyproject.toml (aus der Wurzel)" in sauber.stdout
and abgewaehlt.returncode == 2
and "ABGEWAEHLT" in ausgabe
)
melde("Fall 37", ok, "exit={0}/{1}\n{2}{3}\n{4}".format(
sauber.returncode, abgewaehlt.returncode, sauber.stdout, sauber.stderr, ausgabe))
pruefe_unveraendert(dateien, snap, "Fall 37")
finally:
shutil.rmtree(rahmen, ignore_errors=True)
def fall_38(basis, projektpfad):
rahmen, wurzel, dateien = baue_rahmen(eigene_konfig=True, fremde_konfig=False, fremdes_conftest=False)
try:
(wurzel / "pyproject.toml").write_text(
"[tool.pytest.ini_options]\n"
"addopts = \"--gibt-es-nicht\"\n",
encoding="utf-8",
)
snap = snapshot(dateien)
toml = rahmen / "mutanten.toml"
schreibe_toml(toml, mutant_d1_d2())
ergebnis = starte_laeufer(wurzel, toml)
ausgabe = ergebnis.stdout + ergebnis.stderr
ok = (
ergebnis.returncode == 2
and "pytest-Bedienfehler (Rueckgabewert 4)" in ausgabe
and "--gibt-es-nicht" in ausgabe
and "ist FEHLT" not in ausgabe
)
melde("Fall 38", ok, "exit={0}\n{1}".format(ergebnis.returncode, ausgabe))
pruefe_unveraendert(dateien, snap, "Fall 38")
finally:
shutil.rmtree(rahmen, ignore_errors=True)
def fall_39(basis, projektpfad):
rahmen, wurzel, dateien = baue_rahmen(eigene_konfig=False, fremde_konfig=False, fremdes_conftest=True)
try:
snap = snapshot(dateien)
toml = rahmen / "mutanten.toml"
schreibe_toml(toml, mutant_d1_d2())
ergebnis = starte_laeufer(wurzel, toml)
ok = ergebnis.returncode == 0 and "D1 OK" in ergebnis.stdout and "D2 OK" in ergebnis.stdout
melde("Fall 39", ok, "exit={0}\n{1}{2}".format(ergebnis.returncode, ergebnis.stdout, ergebnis.stderr))
pruefe_unveraendert(dateien, snap, "Fall 39")
finally:
shutil.rmtree(rahmen, ignore_errors=True)
def fall_40(basis, projektpfad):
rahmen, wurzel, dateien = baue_rahmen(eigene_konfig=True, fremde_konfig=False, fremdes_conftest=False)
try:
snap = snapshot(dateien)
toml = rahmen / "mutanten.toml"
schreibe_toml(toml, kopf() + (
"[[mutant]]\n"
"id = \"M40\"\n"
"datei = \"pyproject.toml\"\n"
"aus = '''\n"
"addopts = '-m \"not integration\"'\n"
"'''\n"
"wird = '''\n"
"addopts = \"--gibt-es-nicht\"\n"
"'''\n"
"rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n"
))
bericht = rahmen / "bericht40.json"
ergebnis = starte_laeufer(wurzel, toml, bericht_pfad=bericht)
mutantenzeile = [z for z in ergebnis.stdout.split("\n") if z.startswith("M40 ")]
try:
ausgabe_bericht = json.loads(bericht.read_text(encoding="utf-8"))["mutanten"][0]["tests"][0].get("ausgabe", "")
except (OSError, ValueError, KeyError, IndexError):
ausgabe_bericht = ""
ok = (
ergebnis.returncode == 1
and len(mutantenzeile) == 1
and "M40 UNKLAR" in mutantenzeile[0]
and "pytest-Bedienfehler (Rueckgabewert 4)" in mutantenzeile[0]
and "--gibt-es-nicht" in mutantenzeile[0]
and "Ende der Ausgabe:" in ausgabe_bericht
)
melde("Fall 40", ok, "exit={0}\n{1}{2}".format(ergebnis.returncode, ergebnis.stdout, ergebnis.stderr))
pruefe_unveraendert(dateien, snap, "Fall 40")
finally:
shutil.rmtree(rahmen, ignore_errors=True)
def fall_41(basis, projektpfad):
rahmen = Path(tempfile.mkdtemp(prefix="mutanten_lauf_treiber_symlink_"))
try:
dateien = baue_projekt(rahmen / "echt" / "k")
os.symlink(rahmen / "echt", rahmen / "link")
wurzel = rahmen / "link" / "k"
snap = snapshot(dateien)
toml = rahmen / "mutanten.toml"
schreibe_toml(toml, mutant_d1_d2())
ergebnis = starte_laeufer(wurzel, toml)
ok = ergebnis.returncode == 0 and "D1 OK" in ergebnis.stdout and "D2 OK" in ergebnis.stdout
melde("Fall 41", ok, "exit={0}\n{1}{2}".format(ergebnis.returncode, ergebnis.stdout, ergebnis.stderr))
pruefe_unveraendert(dateien, snap, "Fall 41")
finally:
shutil.rmtree(rahmen, ignore_errors=True)
def fall_42(basis, projektpfad):
rahmen, wurzel, dateien = baue_rahmen(eigene_konfig=True, fremde_konfig=False, fremdes_conftest=False)
try:
(wurzel / "conftest.py").write_text(
"def pytest_collection_modifyitems(items):\n"
" raise RuntimeError(\"interner Fehler fuer Fall 42\")\n",
encoding="utf-8",
)
dateien = dateien + [wurzel / "conftest.py"]
snap = snapshot(dateien)
toml = rahmen / "mutanten.toml"
schreibe_toml(toml, mutant_d1_d2())
ergebnis = starte_laeufer(wurzel, toml)
ausgabe = ergebnis.stdout + ergebnis.stderr
ok = (
ergebnis.returncode == 2
and "pytest-Abbruch mit internem Fehler (Rueckgabewert 3)" in ausgabe
and "interner Fehler fuer Fall 42" in ausgabe
and "ist FEHLT" not in ausgabe
)
melde("Fall 42", ok, "exit={0}\n{1}".format(ergebnis.returncode, ausgabe))
pruefe_unveraendert(dateien, snap, "Fall 42")
finally:
shutil.rmtree(rahmen, ignore_errors=True)
def fall_43(basis, projektpfad):
arbeit = Path(tempfile.mkdtemp(prefix="mutanten_lauf_treiber_probe_"))
try:
ergebnis = subprocess.run(
[INTERPRETER, str(PROBE), str(LAEUFER), str(arbeit)],
capture_output=True, text=True, timeout=900,
)
ok = ergebnis.returncode == 0 and ", abweichungen: 0" in ergebnis.stdout
melde("Fall 43", ok, "exit={0}\n{1}{2}".format(ergebnis.returncode, ergebnis.stdout, ergebnis.stderr))
finally:
shutil.rmtree(arbeit, ignore_errors=True)
def fall_44(basis, projektpfad):
zweit_dir = Path(tempfile.mkdtemp(prefix="mutanten_lauf_fall44_"))
venv_link = zweit_dir / "venv_zwei"
zweit_python = venv_link / "bin" / "python"
try:
os.symlink(Path(INTERPRETER).parent.parent, venv_link)
toml = projektpfad / "mutanten.toml"
zusatz = "\n[python_je_datei]\n\"tests/test_zeit.py\" = \"{0}\"\n".format(zweit_python)
befund_zwei = "Grundlinie: pytest-Bedienfehler (Rueckgabewert 4) mit {0};".format(zweit_python)
befund_eins = "Grundlinie: pytest-Bedienfehler (Rueckgabewert 4) mit {0};".format(INTERPRETER)
reihenfolgen = [
["tests/test_zeit.py::test_gibt_es_nicht", "tests/test_modul.py::test_f1_positiv"],
["tests/test_modul.py::test_f1_positiv", "tests/test_zeit.py::test_gibt_es_nicht"],
]
for stelle, rot_bei in zip(("erste", "letzte"), reihenfolgen):
schreibe_toml(toml, kopf(zusatz=zusatz) + mutant_luecke("M44", rot_bei))
ergebnis = starte_laeufer(basis, toml)
ausgabe = ergebnis.stdout + ergebnis.stderr
kopfzeilen = [z for z in ergebnis.stdout.split("\n") if z.startswith("pytest-konfiguration (Grundlinie, ")]
ok = (
ergebnis.returncode == 2
and ausgabe.count(befund_zwei) == 1
and befund_eins not in ausgabe
and "ist FEHLT" not in ausgabe
and "test_f1_positiv ist" not in ausgabe
and len(kopfzeilen) == 2
and sum(1 for z in kopfzeilen if z.startswith("pytest-konfiguration (Grundlinie, {0}): ".format(INTERPRETER))) == 1
and sum(1 for z in kopfzeilen if z.startswith("pytest-konfiguration (Grundlinie, {0}): ".format(zweit_python))) == 1
)
melde("Fall 44 ({0} Stelle)".format(stelle), ok, "exit={0}\n{1}".format(ergebnis.returncode, ausgabe))
finally:
shutil.rmtree(zweit_dir, ignore_errors=True)
def fall_45(basis, projektpfad):
toml = projektpfad / "mutanten.toml"
schreibe_toml(toml, kopf() + mutant_luecke("M45", ["tests/" + "a" * 300 + ".py::test_x"]))
ergebnis = starte_laeufer(basis, toml)
ausgabe = ergebnis.stdout + ergebnis.stderr
ok = (
ergebnis.returncode == 2
and "Grundlinie: pytest-" in ausgabe
and "unerwarteter Abbruch" not in ausgabe
)
melde("Fall 45", ok, "exit={0}\n{1}".format(ergebnis.returncode, ausgabe[-2000:]))
def fall_46(basis, projektpfad):
rahmen, wurzel, dateien = baue_rahmen(eigene_konfig=True, fremde_konfig=False, fremdes_conftest=False)
try:
(wurzel / "conftest.py").write_text("X = 1\n", encoding="utf-8")
dateien = dateien + [wurzel / "conftest.py"]
snap = snapshot(dateien)
toml = rahmen / "mutanten.toml"
schreibe_toml(toml, kopf() + (
"[[mutant]]\n"
"id = \"M46\"\n"
"datei = \"conftest.py\"\n"
"aus = '''\n"
"X = 1\n"
"'''\n"
"wird = '''\n"
"def pytest_collection_modifyitems(items):\n"
" raise RuntimeError(\"intern Fall 46\")\n"
"'''\n"
"rot_bei = [\"tests/test_modul.py::test_f1_positiv\"]\n"
))
bericht = rahmen / "bericht46.json"
ergebnis = starte_laeufer(wurzel, toml, bericht_pfad=bericht)
mutantenzeile = [z for z in ergebnis.stdout.split("\n") if z.startswith("M46 ")]
try:
ausgabe_bericht = json.loads(bericht.read_text(encoding="utf-8"))["mutanten"][0]["tests"][0].get("ausgabe", "")
except (OSError, ValueError, KeyError, IndexError):
ausgabe_bericht = ""
ok = (
ergebnis.returncode == 1
and len(mutantenzeile) == 1
and "M46 UNKLAR" in mutantenzeile[0]
and "pytest-Abbruch mit internem Fehler (Rueckgabewert 3): " in mutantenzeile[0]
and "intern Fall 46" in mutantenzeile[0]
and "Ende der Ausgabe:" in ausgabe_bericht
)
melde("Fall 46", ok, "exit={0}\n{1}{2}".format(ergebnis.returncode, ergebnis.stdout, ergebnis.stderr))
pruefe_unveraendert(dateien, snap, "Fall 46")
finally:
shutil.rmtree(rahmen, 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, fall_36, fall_37, fall_38, fall_39, fall_40,
fall_41, fall_42, fall_43, fall_44, fall_45, fall_46,
]
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())