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]>
This commit is contained in:
1 parent
ed55848b00
commit
30ed04a643
8 files changed
+3457
-19
No files matched your search
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Treiber fuer mutanten_lauf.py: 35 Angriffsfaelle auf einer Wegwerfkopie.
|
||||
"""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
|
||||
@@ -16,9 +16,23 @@ 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
|
||||
@@ -30,6 +44,7 @@ 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 = []
|
||||
@@ -1010,11 +1025,301 @@ def fall_35(basis, projektpfad):
|
||||
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_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,
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user