Mutantenstufe der Plankritik nach der Pruefung vom 2026-09-28: Netzsperre per unshare, ungepufferte Ausgabe, Zeitlimit je Mutant 120 s, Aufraeumen bei SIGTERM, Modus-Zeile, mutanten_status, Faelle und Treiberfall 35
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
1be4f913aa
commit
5e79722c40
8 files changed
+336
-49
No files matched your search
@@ -125,6 +125,16 @@ class RuecksetzenGescheitert(Exception):
|
||||
super().__init__("Ruecksetzen gescheitert bei Mutant {0} ({1})".format(mutant_id, datei))
|
||||
|
||||
|
||||
class SigtermUnterbrechung(Exception):
|
||||
"""Wird aus dem SIGTERM-Handler ausgeloest, damit die finally-Bloecke
|
||||
(Ruecksetzen der Zieldatei, Aufraeumen der Temp-Ordner) laufen. Siehe
|
||||
Nachbesserung 2026-09-28, N4."""
|
||||
|
||||
|
||||
def _sigterm_werfen(signum, frame):
|
||||
raise SigtermUnterbrechung()
|
||||
|
||||
|
||||
def ist_relativer_pfad(pfad):
|
||||
if not isinstance(pfad, str) or pfad == "":
|
||||
return False
|
||||
@@ -442,45 +452,65 @@ def pytest_lauf(python_exe, knoten_liste, wurzel, home_dir, plugin_dir, zeitlimi
|
||||
|
||||
zeit_ueberschritten = False
|
||||
absturz = None
|
||||
proc = None
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
cmd, cwd=str(wurzel), env=env,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
start_new_session=True,
|
||||
)
|
||||
try:
|
||||
proc.communicate(timeout=zeitlimit)
|
||||
except subprocess.TimeoutExpired:
|
||||
zeit_ueberschritten = True
|
||||
try:
|
||||
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
|
||||
except (ProcessLookupError, PermissionError):
|
||||
pass
|
||||
proc.communicate()
|
||||
except OSError as exc:
|
||||
absturz = "pytest-Aufruf gescheitert ({0}): {1}".format(python_exe, exc)
|
||||
proc = subprocess.Popen(
|
||||
cmd, cwd=str(wurzel), env=env,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
start_new_session=True,
|
||||
)
|
||||
except OSError as exc:
|
||||
absturz = "pytest-Aufruf gescheitert ({0}): {1}".format(python_exe, exc)
|
||||
|
||||
meldungen = []
|
||||
try:
|
||||
with open(ergebnis_pfad, "r", encoding="utf-8") as datei:
|
||||
for zeile in datei:
|
||||
zeile = zeile.strip()
|
||||
if not zeile:
|
||||
continue
|
||||
if proc is not None:
|
||||
try:
|
||||
proc.communicate(timeout=zeitlimit)
|
||||
except subprocess.TimeoutExpired:
|
||||
zeit_ueberschritten = True
|
||||
try:
|
||||
meldungen.append(json.loads(zeile))
|
||||
except ValueError:
|
||||
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
|
||||
except (ProcessLookupError, PermissionError):
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
proc.communicate()
|
||||
except BaseException:
|
||||
# Auch ein SIGTERM (SigtermUnterbrechung) darf keinen pytest-Prozess
|
||||
# zurueck lassen: Prozessgruppe beenden, dann weiterwerfen, damit die
|
||||
# finally-Bloecke der Aufrufer (Ruecksetzen der Zieldatei, Aufraeumen
|
||||
# der Temp-Ordner) laufen. Siehe Nachbesserung 2026-09-28, N4.
|
||||
try:
|
||||
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
|
||||
except (ProcessLookupError, PermissionError):
|
||||
pass
|
||||
try:
|
||||
proc.communicate()
|
||||
except Exception:
|
||||
pass
|
||||
raise
|
||||
|
||||
meldungen = []
|
||||
try:
|
||||
with open(ergebnis_pfad, "r", encoding="utf-8") as datei:
|
||||
for zeile in datei:
|
||||
zeile = zeile.strip()
|
||||
if not zeile:
|
||||
continue
|
||||
try:
|
||||
meldungen.append(json.loads(zeile))
|
||||
except ValueError:
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return zeit_ueberschritten, meldungen, absturz
|
||||
finally:
|
||||
# Muss auch bei einem SIGTERM mitten im Lauf greifen (N4): sonst bleibt die
|
||||
# Ergebnisdatei liegen, obwohl der pytest-Prozess schon beendet ist.
|
||||
try:
|
||||
os.remove(ergebnis_pfad)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return zeit_ueberschritten, meldungen, absturz
|
||||
|
||||
|
||||
def gruppen_ausfuehren(gruppen, wurzel, home_dir, plugin_dir, zeitlimit):
|
||||
reports = {}
|
||||
@@ -755,6 +785,7 @@ def ablauf(args, bericht):
|
||||
|
||||
plugin_dir = Path(tempfile.mkdtemp(prefix="mutanten_lauf_plugin_"))
|
||||
home_dir = Path(tempfile.mkdtemp(prefix="mutanten_lauf_home_"))
|
||||
aktueller_mutant_id = None
|
||||
try:
|
||||
(plugin_dir / (PLUGIN_NAME + ".py")).write_text(PLUGIN_QUELLTEXT, encoding="utf-8")
|
||||
|
||||
@@ -784,11 +815,13 @@ def ablauf(args, bericht):
|
||||
print(" " + mutant["hinweis"])
|
||||
continue
|
||||
|
||||
aktueller_mutant_id = mutant["id"]
|
||||
status, test_ergebnisse = mutant_anwenden_und_pruefen(
|
||||
mutant, muster["start"], muster["aus_zeilen"], muster["wird_zeilen"],
|
||||
wurzel, home_dir, plugin_dir,
|
||||
kontext["python_je_datei"], kontext["python"], args.zeitlimit,
|
||||
)
|
||||
aktueller_mutant_id = None
|
||||
eintrag = {
|
||||
"id": mutant["id"],
|
||||
"datei": mutant["datei"],
|
||||
@@ -808,6 +841,14 @@ def ablauf(args, bericht):
|
||||
|
||||
alle_ok = all(m["status"] == "OK" for m in bericht["mutanten"])
|
||||
return 0 if alle_ok else 1
|
||||
except SigtermUnterbrechung:
|
||||
zeile = (
|
||||
"ABBRUCH: SIGTERM, Mutant {0} zurueckgesetzt".format(aktueller_mutant_id)
|
||||
if aktueller_mutant_id else "ABBRUCH: SIGTERM"
|
||||
)
|
||||
print(zeile)
|
||||
bericht["befunde"] = list(befunde) + [zeile]
|
||||
return 3
|
||||
except RuecksetzenGescheitert as exc:
|
||||
bericht["befunde"] = list(befunde) + [str(exc)]
|
||||
return 2
|
||||
@@ -830,6 +871,7 @@ def ausgabe_schreiben(bericht, args):
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
signal.signal(signal.SIGTERM, _sigterm_werfen)
|
||||
args = parse_args(argv)
|
||||
bericht = {
|
||||
"wurzel": args.wurzel,
|
||||
|
||||
Reference in new issue
Block a user