Erste Fassung: Claude-Werkzeuge aus dem Vault
.vault-tools (ohne trading-plattform-pruefung und watermarks-remover), .claude/agents, .claude/skills, .claude/workflows, .claude/settings.json. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
fa1c26b407
137 files changed
+43926
No files matched your search
@@ -0,0 +1,718 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Treiber fuer mutanten_lauf_kopie.sh: Faelle W1 bis W13 (lo im Netz-Namensraum).
|
||||
|
||||
Zweck: Baut je Fall unter /tmp ein kleines Projekt (kopie/) und einen
|
||||
Planordner (plan/) mit mutanten.toml, ruft mutanten_lauf_kopie.sh mit beiden
|
||||
Ordnern als Windows-Pfaden auf, wie plankritik.js es tut, und vergleicht
|
||||
Exit-Code und Ausgabezeilen mit der Erwartung aus dem Plan
|
||||
docs/plans/2026-10-09-mutanten-kopie-loopback.md. Jeder Fall raeumt seine
|
||||
Ordner selbst auf. Nach jedem Fall prueft der Treiber, dass die uebergebene
|
||||
Kopie byte-gleich ist und kein Ordner /tmp/plankritik-mut-* liegen blieb.
|
||||
|
||||
Aufruf (in WSL): python mutanten_lauf_kopie_treiber.py [--skript <pfad>]
|
||||
Ohne --skript faehrt er mutanten_lauf_kopie.sh neben sich, mit --skript eine
|
||||
andere Fassung (Regressionsnachweis: Stand aus git show HEAD, kaputte
|
||||
Varianten). W7 ist die Grundlinie und laeuft zuerst; ist sie rot, messen die
|
||||
anderen Faelle wenig.
|
||||
|
||||
Pfade: Das Skript wandelt seine Argumente mit wslpath -u um. Der Treiber gibt
|
||||
ihm deshalb wslpath -w seiner /tmp-Ordner und prueft vor dem ersten Fall,
|
||||
dass die Rundreise denselben Pfad ergibt.
|
||||
|
||||
Netz: Die Tests verbinden sich nur mit 127.0.0.1 und, nur im Netz-Namensraum
|
||||
des Skripts, mit 192.0.2.1 (TEST-NET-1). Der Test dazu prueft zuerst, dass es
|
||||
keine Route ausser ueber lo gibt; ohne Namensraum scheitert er dort, bevor er
|
||||
eine Verbindung versucht.
|
||||
|
||||
Faelle W10 bis W13 (Nachbesserung 1, Plan
|
||||
docs/plans/2026-10-09-mutanten-kopie-loopback-nachbesserung-1.md) ersetzen im
|
||||
Attrappen-Ordner einzelne Werkzeuge durch kleine sh-Skripte, nach den
|
||||
Angriffen A1, A3 und A4 des Pruefers vom 2026-10-09: ip scheitert in der
|
||||
Probe (W10), ip meldet Erfolg und lo bleibt aus (W11), ip scheitert erst im
|
||||
Hauptaufruf (W12), setpriv wirft keine Faehigkeit ab (W13).
|
||||
|
||||
Nicht im Treiber: ob plankritik.js das Skript so aufruft wie hier
|
||||
(MSYS_NO_PATHCONV, cygpath -m -l); ob die Probe bei Exit 0 mit falscher
|
||||
Ausgabe scheitert.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
HIER = Path(__file__).resolve().parent
|
||||
SKRIPT_STANDARD = HIER / "mutanten_lauf_kopie.sh"
|
||||
INTERPRETER = "/home/claude/venvs/trading/bin/python"
|
||||
BASH = shutil.which("bash")
|
||||
# Was mutanten_lauf_kopie.sh ueber PATH aufruft (Stand 2026-10-09); daraus
|
||||
# bauen W4, W5, W9 und W10 bis W13 ihre Attrappen-Ordner.
|
||||
WERKZEUGE = (
|
||||
"unshare", "wslpath", "mktemp", "cp", "chmod", "find", "rm", "timeout",
|
||||
"sh", "true", "ip", "setpriv", "python3",
|
||||
)
|
||||
|
||||
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 kurz(ergebnis):
|
||||
return "exit={0}\n{1}{2}".format(ergebnis.returncode, ergebnis.stdout, ergebnis.stderr)
|
||||
|
||||
|
||||
def sha_von(pfad):
|
||||
return hashlib.sha256(pfad.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def plankritik_ordner():
|
||||
return set(Path("/tmp").glob("plankritik-mut-*"))
|
||||
|
||||
|
||||
def windows_pfad(pfad):
|
||||
ergebnis = subprocess.run(["wslpath", "-w", str(pfad)], capture_output=True, text=True, timeout=30)
|
||||
return ergebnis.stdout.strip()
|
||||
|
||||
|
||||
def linux_pfad(pfad):
|
||||
ergebnis = subprocess.run(["wslpath", "-u", pfad], capture_output=True, text=True, timeout=30)
|
||||
return ergebnis.stdout.strip()
|
||||
|
||||
|
||||
# --- Projektbausteine -------------------------------------------------------
|
||||
|
||||
GRUNDDATEIEN = {
|
||||
"pytest.ini": "[pytest]\n",
|
||||
"paket/__init__.py": "",
|
||||
"tests/__init__.py": "",
|
||||
}
|
||||
|
||||
MODUL = (
|
||||
"def f1(x):\n"
|
||||
" if x > 0:\n"
|
||||
" return True\n"
|
||||
" return False\n"
|
||||
)
|
||||
|
||||
TEST_MODUL = (
|
||||
"from paket.modul import f1\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"def test_f1_positiv():\n"
|
||||
" assert f1(5) is True\n"
|
||||
)
|
||||
|
||||
DIENST = (
|
||||
"import socket\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"def antwort_ueber_lo(nachricht):\n"
|
||||
" server = socket.socket()\n"
|
||||
" server.bind((\"127.0.0.1\", 0))\n"
|
||||
" server.listen(1)\n"
|
||||
" client = socket.create_connection(server.getsockname(), timeout=5)\n"
|
||||
" verbindung, _ = server.accept()\n"
|
||||
" client.sendall(nachricht)\n"
|
||||
" daten = verbindung.recv(1024)\n"
|
||||
" for sock in (client, verbindung, server):\n"
|
||||
" sock.close()\n"
|
||||
" return daten\n"
|
||||
)
|
||||
|
||||
TEST_DIENST = (
|
||||
"from paket.dienst import antwort_ueber_lo\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"def test_echo_ueber_lo():\n"
|
||||
" assert antwort_ueber_lo(b\"hallo\") == b\"hallo\"\n"
|
||||
)
|
||||
|
||||
NETZ = (
|
||||
"AUSSEN = (\"192.0.2.1\", 9)\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"def aussen_adresse():\n"
|
||||
" return AUSSEN\n"
|
||||
)
|
||||
|
||||
TEST_NETZ = (
|
||||
"import errno\n"
|
||||
"import socket\n"
|
||||
"\n"
|
||||
"import pytest\n"
|
||||
"\n"
|
||||
"from paket.netz import aussen_adresse\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"def test_keine_route_nach_aussen():\n"
|
||||
" with open(\"/proc/net/route\", encoding=\"ascii\") as datei:\n"
|
||||
" zeilen = datei.read().splitlines()[1:]\n"
|
||||
" schnittstellen = [zeile.split()[0] for zeile in zeilen if zeile.strip()]\n"
|
||||
" assert all(name == \"lo\" for name in schnittstellen), \"Routen ausser lo: {0}\".format(schnittstellen)\n"
|
||||
" sock = socket.socket()\n"
|
||||
" sock.settimeout(5)\n"
|
||||
" try:\n"
|
||||
" sock.connect(aussen_adresse())\n"
|
||||
" except OSError as exc:\n"
|
||||
" assert exc.errno == errno.ENETUNREACH, \"erwartet ENETUNREACH: {0!r}\".format(exc)\n"
|
||||
" else:\n"
|
||||
" pytest.fail(\"Verbindung nach aussen gelungen\")\n"
|
||||
" finally:\n"
|
||||
" sock.close()\n"
|
||||
)
|
||||
|
||||
RECHTE = (
|
||||
"def status_feld(name):\n"
|
||||
" with open(\"/proc/self/status\", encoding=\"utf-8\", errors=\"replace\") as datei:\n"
|
||||
" for zeile in datei:\n"
|
||||
" schluessel, _, wert = zeile.partition(\":\")\n"
|
||||
" if schluessel == name:\n"
|
||||
" return wert.strip()\n"
|
||||
" raise KeyError(name)\n"
|
||||
)
|
||||
|
||||
HAENGEN = (
|
||||
"def schritt_a(n):\n"
|
||||
" return n + 1\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"def schritt_b(n):\n"
|
||||
" return n + 2\n"
|
||||
)
|
||||
|
||||
TEST_HAENGEN = (
|
||||
"from paket.haengen import schritt_a, schritt_b\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"def test_schritt_a():\n"
|
||||
" assert schritt_a(1) == 2\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"def test_schritt_b():\n"
|
||||
" assert schritt_b(1) == 3\n"
|
||||
)
|
||||
|
||||
SCHLEIFE = " while True:\n pass\n"
|
||||
|
||||
PRUEFZEILE_AUSSEN = " assert info.value.errno in (errno.ECONNREFUSED, errno.ENETUNREACH), repr(info.value)\n"
|
||||
|
||||
|
||||
def text_test_rechte(uid):
|
||||
return (
|
||||
"from paket.rechte import status_feld\n"
|
||||
"\n"
|
||||
"UID_AUSSEN = \"" + str(uid) + "\"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"def test_keine_faehigkeiten_gleiche_uid():\n"
|
||||
" for feld in (\"CapInh\", \"CapPrm\", \"CapEff\", \"CapAmb\"):\n"
|
||||
" wert = status_feld(feld)\n"
|
||||
" assert int(wert, 16) == 0, \"{0} ist {1}\".format(feld, wert)\n"
|
||||
" assert status_feld(\"Uid\").split()[0] == UID_AUSSEN\n"
|
||||
)
|
||||
|
||||
|
||||
def text_test_aussen(port):
|
||||
return (
|
||||
"import errno\n"
|
||||
"import socket\n"
|
||||
"\n"
|
||||
"import pytest\n"
|
||||
"\n"
|
||||
"PORT_AUSSEN = " + str(port) + "\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"def test_dienst_des_rechners_unerreichbar():\n"
|
||||
" sock = socket.socket()\n"
|
||||
" sock.settimeout(5)\n"
|
||||
" try:\n"
|
||||
" with pytest.raises(OSError) as info:\n"
|
||||
" sock.connect((\"127.0.0.1\", PORT_AUSSEN))\n"
|
||||
" finally:\n"
|
||||
" sock.close()\n"
|
||||
+ PRUEFZEILE_AUSSEN
|
||||
)
|
||||
|
||||
|
||||
def kopf():
|
||||
return "format = 1\npython = \"{0}\"\n\n".format(INTERPRETER)
|
||||
|
||||
|
||||
def mutant(id_, datei, aus, wird, rot_bei):
|
||||
return (
|
||||
"[[mutant]]\n"
|
||||
"id = \"" + id_ + "\"\n"
|
||||
"datei = \"" + datei + "\"\n"
|
||||
"aus = '''\n" + aus + "'''\n"
|
||||
"wird = '''\n" + wird + "'''\n"
|
||||
"rot_bei = [\"" + rot_bei + "\"]\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
|
||||
class Fall:
|
||||
"""Ein Fall unter /tmp: kopie/ mit den Projektdateien, plan/ mit mutanten.toml."""
|
||||
|
||||
def __init__(self, dateien, mutanten):
|
||||
self.basis = Path(tempfile.mkdtemp(prefix="mkopie_treiber_"))
|
||||
self.kopie = self.basis / "kopie"
|
||||
self.plan = self.basis / "plan"
|
||||
self.plan.mkdir()
|
||||
alle = dict(GRUNDDATEIEN)
|
||||
alle.update(dateien)
|
||||
self.snap = {}
|
||||
for relpfad, inhalt in alle.items():
|
||||
pfad = self.kopie / relpfad
|
||||
pfad.parent.mkdir(parents=True, exist_ok=True)
|
||||
pfad.write_text(inhalt, encoding="utf-8")
|
||||
self.snap[pfad] = sha_von(pfad)
|
||||
(self.plan / "mutanten.toml").write_text(kopf() + mutanten, encoding="utf-8")
|
||||
|
||||
def pruefe_kopie(self, fallname):
|
||||
for pfad, sha in self.snap.items():
|
||||
if not pfad.exists() or sha_von(pfad) != sha:
|
||||
melde(fallname + " (kopie)", False, "uebergebene Kopie veraendert: {0}".format(pfad))
|
||||
return
|
||||
|
||||
def aufraeumen(self):
|
||||
shutil.rmtree(self.basis, ignore_errors=True)
|
||||
|
||||
|
||||
def starte_skript(skript, fall, modus="voll", pfad=None):
|
||||
env = dict(os.environ)
|
||||
if pfad is not None:
|
||||
env["PATH"] = pfad
|
||||
cmd = [BASH, str(skript), windows_pfad(fall.kopie), windows_pfad(fall.plan), modus]
|
||||
return subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=180)
|
||||
|
||||
|
||||
# --- Faelle ----------------------------------------------------------------
|
||||
|
||||
def fahre_einfach(name, skript, dateien, mutanten, mutant_id):
|
||||
fall = Fall(dateien, mutanten)
|
||||
try:
|
||||
ergebnis = starte_skript(skript, fall)
|
||||
ok = (
|
||||
ergebnis.returncode == 0
|
||||
and "{0} OK".format(mutant_id) in ergebnis.stdout
|
||||
and "EXIT mutanten_lauf: 0" in ergebnis.stdout
|
||||
)
|
||||
melde(name, ok, kurz(ergebnis))
|
||||
fall.pruefe_kopie(name)
|
||||
finally:
|
||||
fall.aufraeumen()
|
||||
|
||||
|
||||
def fall_einfach():
|
||||
return Fall(
|
||||
{"paket/modul.py": MODUL, "tests/test_modul.py": TEST_MODUL},
|
||||
mutant("MW7", "paket/modul.py", " return True\n", " return False\n",
|
||||
"tests/test_modul.py::test_f1_positiv"),
|
||||
)
|
||||
|
||||
|
||||
def fall_w7(skript):
|
||||
# Grundlinie wie bisher: kein Server-Test, Modus voll und muster.
|
||||
fall = fall_einfach()
|
||||
try:
|
||||
voll = starte_skript(skript, fall)
|
||||
muster = starte_skript(skript, fall, modus="muster")
|
||||
ok = (
|
||||
voll.returncode == 0
|
||||
and "modus: voll" in voll.stdout
|
||||
and "MW7 OK" in voll.stdout
|
||||
and "EXIT mutanten_lauf: 0" in voll.stdout
|
||||
and muster.returncode == 0
|
||||
and "modus: muster" in muster.stdout
|
||||
and "EXIT mutanten_lauf: 0" in muster.stdout
|
||||
)
|
||||
melde("W7", ok, "voll " + kurz(voll) + "\nmuster " + kurz(muster))
|
||||
fall.pruefe_kopie("W7")
|
||||
finally:
|
||||
fall.aufraeumen()
|
||||
|
||||
|
||||
def fall_w1(skript):
|
||||
# Ein Test startet einen Server auf 127.0.0.1 und tauscht Daten aus.
|
||||
fahre_einfach(
|
||||
"W1", skript,
|
||||
{"paket/dienst.py": DIENST, "tests/test_dienst.py": TEST_DIENST},
|
||||
mutant("MW1", "paket/dienst.py", " return daten\n", " return daten[::-1]\n",
|
||||
"tests/test_dienst.py::test_echo_ueber_lo"),
|
||||
"MW1",
|
||||
)
|
||||
|
||||
|
||||
def fall_w2(skript):
|
||||
# Verbindung nach aussen scheitert mit ENETUNREACH; der Mutant zielt auf
|
||||
# 127.0.0.1:9 und faellt nur, wenn lo oben ist (ECONNREFUSED).
|
||||
fahre_einfach(
|
||||
"W2", skript,
|
||||
{"paket/netz.py": NETZ, "tests/test_netz.py": TEST_NETZ},
|
||||
mutant("MW2", "paket/netz.py", "AUSSEN = (\"192.0.2.1\", 9)\n", "AUSSEN = (\"127.0.0.1\", 9)\n",
|
||||
"tests/test_netz.py::test_keine_route_nach_aussen"),
|
||||
"MW2",
|
||||
)
|
||||
|
||||
|
||||
def fall_w3(skript):
|
||||
# CapInh, CapPrm, CapEff, CapAmb gleich 0 und dieselbe uid wie aussen.
|
||||
fahre_einfach(
|
||||
"W3", skript,
|
||||
{"paket/rechte.py": RECHTE, "tests/test_rechte.py": text_test_rechte(os.getuid())},
|
||||
mutant("MW3", "paket/rechte.py", " return wert.strip()\n", " return \"1\"\n",
|
||||
"tests/test_rechte.py::test_keine_faehigkeiten_gleiche_uid"),
|
||||
"MW3",
|
||||
)
|
||||
|
||||
|
||||
def baue_attrappen(ordner, ohne):
|
||||
ordner.mkdir()
|
||||
for name in WERKZEUGE:
|
||||
if name != ohne:
|
||||
os.symlink(shutil.which(name), ordner / name)
|
||||
|
||||
|
||||
def fahre_ohne_werkzeug(name, skript, werkzeug):
|
||||
# Erst ein Kontrolllauf mit allen Attrappen (muss gruen sein), dann einer
|
||||
# ohne das eine Werkzeug (muss vor dem Kopieren mit Exit 2 enden).
|
||||
fall = fall_einfach()
|
||||
try:
|
||||
alle = fall.basis / "attrappen_alle"
|
||||
ohne = fall.basis / "attrappen_ohne"
|
||||
baue_attrappen(alle, None)
|
||||
baue_attrappen(ohne, werkzeug)
|
||||
kontrolle = starte_skript(skript, fall, pfad=str(alle))
|
||||
vorher = plankritik_ordner()
|
||||
ergebnis = starte_skript(skript, fall, pfad=str(ohne))
|
||||
neu = sorted(str(p) for p in plankritik_ordner() - vorher)
|
||||
ok = (
|
||||
kontrolle.returncode == 0
|
||||
and "MW7 OK" in kontrolle.stdout
|
||||
and ergebnis.returncode == 2
|
||||
and "MUTANTEN_LAUF_FEHLER: {0} fehlt".format(werkzeug) in ergebnis.stdout
|
||||
and "modus:" not in ergebnis.stdout
|
||||
and "EXIT mutanten_lauf" not in ergebnis.stdout
|
||||
and not neu
|
||||
)
|
||||
melde(name, ok, "kontrolle exit={0}, ohne {1} exit={2}, neue Kopien={3}\nkontrolle {4}\nohne {1} {5}".format(
|
||||
kontrolle.returncode, werkzeug, ergebnis.returncode, neu, kurz(kontrolle), kurz(ergebnis)))
|
||||
fall.pruefe_kopie(name)
|
||||
finally:
|
||||
fall.aufraeumen()
|
||||
|
||||
|
||||
def fall_w4(skript):
|
||||
fahre_ohne_werkzeug("W4", skript, "ip")
|
||||
|
||||
|
||||
def fall_w5(skript):
|
||||
fahre_ohne_werkzeug("W5", skript, "setpriv")
|
||||
|
||||
|
||||
def fall_w9(skript):
|
||||
fahre_ohne_werkzeug("W9", skript, "python3")
|
||||
|
||||
|
||||
def baue_attrappen_mit_ersatz(ordner, ersatz):
|
||||
# Wie baue_attrappen, aber jedes Werkzeug aus ersatz ist ein kleines
|
||||
# sh-Skript statt eines Verweises auf das echte Werkzeug (Bauweise aus den
|
||||
# Angriffen des Pruefers vom 2026-10-09).
|
||||
ordner.mkdir()
|
||||
for name in WERKZEUGE:
|
||||
pfad = ordner / name
|
||||
if name in ersatz:
|
||||
pfad.write_text("#!/bin/sh\n" + ersatz[name] + "\n", encoding="utf-8")
|
||||
pfad.chmod(0o755)
|
||||
else:
|
||||
os.symlink(shutil.which(name), pfad)
|
||||
|
||||
|
||||
def fahre_probe_scheitert(name, skript, ersatz, merkmal):
|
||||
# Die Vorpruefung muss scheitern: Exit 2 mit der Meldung der Probe und dem
|
||||
# Merkmal des Angriffs, ohne Modus-Zeile, ohne EXIT-Zeile, ohne Kopie.
|
||||
fall = fall_einfach()
|
||||
try:
|
||||
ordner = fall.basis / "attrappen"
|
||||
baue_attrappen_mit_ersatz(ordner, ersatz)
|
||||
vorher = plankritik_ordner()
|
||||
ergebnis = starte_skript(skript, fall, pfad=str(ordner))
|
||||
neu = sorted(str(p) for p in plankritik_ordner() - vorher)
|
||||
ok = (
|
||||
ergebnis.returncode == 2
|
||||
and "MUTANTEN_LAUF_FEHLER: lo im Netz-Namensraum nicht nutzbar" in ergebnis.stdout
|
||||
and merkmal in ergebnis.stdout
|
||||
and "modus:" not in ergebnis.stdout
|
||||
and "EXIT mutanten_lauf" not in ergebnis.stdout
|
||||
and not neu
|
||||
)
|
||||
melde(name, ok, "neue Kopien={0}, {1}".format(neu, kurz(ergebnis)))
|
||||
fall.pruefe_kopie(name)
|
||||
finally:
|
||||
fall.aufraeumen()
|
||||
|
||||
|
||||
def fall_w10(skript):
|
||||
# ip scheitert schon in der Probe (Angriff A1 des Pruefers).
|
||||
fahre_probe_scheitert("W10", skript, {"ip": "echo ip-attrappe scheitert >&2; exit 1"}, "ip-attrappe scheitert")
|
||||
|
||||
|
||||
def fall_w11(skript):
|
||||
# ip meldet Erfolg, lo bleibt aus (A3): Die Verbindung der Probe scheitert
|
||||
# mit Errno 101.
|
||||
fahre_probe_scheitert("W11", skript, {"ip": "exit 0"}, "Errno 101")
|
||||
|
||||
|
||||
def fall_w12(skript):
|
||||
# ip klappt in der Probe und scheitert erst im Hauptaufruf (A4); eine
|
||||
# Zaehlerdatei haelt fest, der wievielte Aufruf es ist. Erwartet:
|
||||
# Modus-Zeile, Meldung des Hauptaufrufs, EXIT-Zeile mit 2, kein Laeufer,
|
||||
# genau zwei Aufrufe von ip, keine Kopie unter /tmp.
|
||||
fall = fall_einfach()
|
||||
try:
|
||||
zaehler = fall.basis / "ip_zaehler"
|
||||
z = shlex.quote(str(zaehler))
|
||||
ip_echt = shlex.quote(shutil.which("ip"))
|
||||
ersatz = {"ip": (
|
||||
"n=0\n"
|
||||
"if [ -f " + z + " ]; then read n < " + z + "; fi\n"
|
||||
"n=$((n + 1))\n"
|
||||
"echo \"$n\" > " + z + "\n"
|
||||
"if [ \"$n\" -ge 2 ]; then echo \"ip-attrappe zweiter Aufruf\" >&2; exit 1; fi\n"
|
||||
"exec " + ip_echt + " \"$@\""
|
||||
)}
|
||||
ordner = fall.basis / "attrappen"
|
||||
baue_attrappen_mit_ersatz(ordner, ersatz)
|
||||
vorher = plankritik_ordner()
|
||||
ergebnis = starte_skript(skript, fall, pfad=str(ordner))
|
||||
neu = sorted(str(p) for p in plankritik_ordner() - vorher)
|
||||
try:
|
||||
aufrufe = zaehler.read_text(encoding="ascii").strip()
|
||||
except OSError:
|
||||
aufrufe = "keine"
|
||||
ok = (
|
||||
ergebnis.returncode == 2
|
||||
and "modus: voll" in ergebnis.stdout
|
||||
and "MUTANTEN_LAUF_FEHLER: lo im Netz-Namensraum nicht hochgefahren" in ergebnis.stdout
|
||||
and "EXIT mutanten_lauf: 2" in ergebnis.stdout
|
||||
and "nicht nutzbar" not in ergebnis.stdout
|
||||
and "MW7" not in ergebnis.stdout
|
||||
and "ip-attrappe zweiter Aufruf" in ergebnis.stderr
|
||||
and aufrufe == "2"
|
||||
and not neu
|
||||
)
|
||||
melde("W12", ok, "ip-aufrufe={0}, neue Kopien={1}, {2}".format(aufrufe, neu, kurz(ergebnis)))
|
||||
fall.pruefe_kopie("W12")
|
||||
finally:
|
||||
fall.aufraeumen()
|
||||
|
||||
|
||||
SETPRIV_OHNE_WIRKUNG = (
|
||||
"while [ \"$#\" -gt 0 ] && [ \"$1\" != \"--\" ]; do shift; done\n"
|
||||
"shift\n"
|
||||
"exec \"$@\""
|
||||
)
|
||||
|
||||
|
||||
def fall_w13(skript):
|
||||
# setpriv wirft keine Faehigkeit ab und startet nur den Befehl hinter --:
|
||||
# Die Faehigkeitspruefung der Probe muss anschlagen.
|
||||
fahre_probe_scheitert("W13", skript, {"setpriv": SETPRIV_OHNE_WIRKUNG}, "Faehigkeiten nicht abgeworfen")
|
||||
|
||||
|
||||
def prozesse_mit_marke(marke):
|
||||
"""pid -> argv aller Prozesse, deren Umgebung die Marke traegt."""
|
||||
gefunden = {}
|
||||
for envdatei in Path("/proc").glob("[0-9]*/environ"):
|
||||
try:
|
||||
if marke.encode("ascii") not in envdatei.read_bytes():
|
||||
continue
|
||||
argv = (envdatei.parent / "cmdline").read_bytes().split(b"\0")
|
||||
except OSError:
|
||||
continue
|
||||
gefunden[envdatei.parent.name] = [teil.decode("utf-8", "replace") for teil in argv if teil]
|
||||
return gefunden
|
||||
|
||||
|
||||
def elternname(pid):
|
||||
try:
|
||||
stat = Path("/proc/{0}/stat".format(pid)).read_text()
|
||||
ppid = stat.rsplit(")", 1)[1].split()[1]
|
||||
return Path("/proc/{0}/comm".format(ppid)).read_text().strip()
|
||||
except (OSError, IndexError):
|
||||
return None
|
||||
|
||||
|
||||
def fall_w6(skript):
|
||||
# Zeitgrenze: zwei haengende Mutanten, BUDGET_S 10, ZEITLIMIT_S 6. Waehrend
|
||||
# des Laufs muss timeout der Elternprozess des Laeufers sein (keine
|
||||
# Zwischenschale); danach lebt kein Prozess des Falls mehr.
|
||||
fall = Fall(
|
||||
{"paket/haengen.py": HAENGEN, "tests/test_haengen.py": TEST_HAENGEN},
|
||||
mutant("MW6a", "paket/haengen.py", " return n + 1\n", SCHLEIFE, "tests/test_haengen.py::test_schritt_a")
|
||||
+ mutant("MW6b", "paket/haengen.py", " return n + 2\n", SCHLEIFE, "tests/test_haengen.py::test_schritt_b"),
|
||||
)
|
||||
try:
|
||||
wert = uuid.uuid4().hex
|
||||
marke = "KOPIE_TREIBER_MARKE=" + wert
|
||||
eigenes_tmpdir = fall.basis / "tmpdir"
|
||||
eigenes_tmpdir.mkdir()
|
||||
env = dict(os.environ)
|
||||
env.update({
|
||||
"BUDGET_S": "10",
|
||||
"ZEITLIMIT_S": "6",
|
||||
"TMPDIR": str(eigenes_tmpdir),
|
||||
"KOPIE_TREIBER_MARKE": wert,
|
||||
})
|
||||
cmd = [BASH, str(skript), windows_pfad(fall.kopie), windows_pfad(fall.plan), "voll"]
|
||||
proc = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
eltern = None
|
||||
pytest_gesehen = False
|
||||
for _ in range(12):
|
||||
time.sleep(0.5)
|
||||
for pid, argv in prozesse_mit_marke(marke).items():
|
||||
if (argv and Path(argv[0]).name.startswith("python")
|
||||
and any(teil.endswith("mutanten_lauf.py") for teil in argv)):
|
||||
eltern = elternname(pid)
|
||||
if "pytest" in argv:
|
||||
pytest_gesehen = True
|
||||
if eltern is not None and pytest_gesehen:
|
||||
break
|
||||
try:
|
||||
stdout, stderr = proc.communicate(timeout=90)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
stdout, stderr = proc.communicate()
|
||||
stderr += "\nTREIBER: Skript nach 90 s nicht beendet"
|
||||
reste = prozesse_mit_marke(marke)
|
||||
tmp_reste = sorted(p.name for p in eigenes_tmpdir.iterdir())
|
||||
ok = (
|
||||
proc.returncode == 124
|
||||
and "ZEITGRENZE" in stdout
|
||||
and "EXIT mutanten_lauf: 124" in stdout
|
||||
and "ABBRUCH: SIGTERM" in stdout
|
||||
and eltern == "timeout"
|
||||
and pytest_gesehen
|
||||
and not reste
|
||||
and not tmp_reste
|
||||
)
|
||||
melde("W6", ok, "exit={0}, eltern des laeufers={1}, pytest gesehen={2}, reste={3}, tmp={4}\n{5}{6}".format(
|
||||
proc.returncode, eltern, pytest_gesehen, reste, tmp_reste, stdout, stderr))
|
||||
fall.pruefe_kopie("W6")
|
||||
finally:
|
||||
fall.aufraeumen()
|
||||
|
||||
|
||||
def fall_w8(skript):
|
||||
# Ein Dienst des Treibers auf 127.0.0.1 darf aus dem Namensraum nicht
|
||||
# erreichbar sein, und bei ihm darf keine Verbindung ankommen.
|
||||
dienst = socket.socket()
|
||||
dienst.bind(("127.0.0.1", 0))
|
||||
dienst.listen(5)
|
||||
dienst.setblocking(False)
|
||||
fall = None
|
||||
try:
|
||||
fall = Fall(
|
||||
{"tests/test_aussen.py": text_test_aussen(dienst.getsockname()[1])},
|
||||
mutant("MW8", "tests/test_aussen.py", PRUEFZEILE_AUSSEN, " assert False\n",
|
||||
"tests/test_aussen.py::test_dienst_des_rechners_unerreichbar"),
|
||||
)
|
||||
ergebnis = starte_skript(skript, fall)
|
||||
try:
|
||||
verbindung, _ = dienst.accept()
|
||||
verbindung.close()
|
||||
angekommen = True
|
||||
except BlockingIOError:
|
||||
angekommen = False
|
||||
ok = (
|
||||
ergebnis.returncode == 0
|
||||
and "MW8 OK" in ergebnis.stdout
|
||||
and "EXIT mutanten_lauf: 0" in ergebnis.stdout
|
||||
and not angekommen
|
||||
)
|
||||
melde("W8", ok, "beim Dienst des Rechners angekommen={0}, {1}".format(angekommen, kurz(ergebnis)))
|
||||
fall.pruefe_kopie("W8")
|
||||
finally:
|
||||
dienst.close()
|
||||
if fall is not None:
|
||||
fall.aufraeumen()
|
||||
|
||||
|
||||
FAELLE = [
|
||||
("W7", fall_w7),
|
||||
("W1", fall_w1),
|
||||
("W2", fall_w2),
|
||||
("W3", fall_w3),
|
||||
("W4", fall_w4),
|
||||
("W5", fall_w5),
|
||||
("W6", fall_w6),
|
||||
("W8", fall_w8),
|
||||
("W9", fall_w9),
|
||||
("W10", fall_w10),
|
||||
("W11", fall_w11),
|
||||
("W12", fall_w12),
|
||||
("W13", fall_w13),
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
zerleger = argparse.ArgumentParser(description="Treiber fuer mutanten_lauf_kopie.sh (Faelle W1 bis W13)")
|
||||
zerleger.add_argument("--skript", default=str(SKRIPT_STANDARD), help="zu pruefende Fassung des Skripts")
|
||||
argumente = zerleger.parse_args()
|
||||
skript = Path(argumente.skript)
|
||||
print("skript: {0}".format(skript))
|
||||
|
||||
fehlend = [name for name in WERKZEUGE if shutil.which(name) is None]
|
||||
if BASH is None:
|
||||
fehlend.append("bash")
|
||||
if fehlend:
|
||||
print("TREIBER_FEHLER: Werkzeuge fehlen: {0}".format(", ".join(fehlend)))
|
||||
return 2
|
||||
if not skript.is_file():
|
||||
print("TREIBER_FEHLER: Skript fehlt: {0}".format(skript))
|
||||
return 2
|
||||
if not Path(INTERPRETER).is_file():
|
||||
print("TREIBER_FEHLER: Interpreter fehlt: {0}".format(INTERPRETER))
|
||||
return 2
|
||||
|
||||
probe = Path(tempfile.mkdtemp(prefix="mkopie_treiber_rundreise_"))
|
||||
try:
|
||||
windows = windows_pfad(probe)
|
||||
zurueck = linux_pfad(windows) if windows else ""
|
||||
finally:
|
||||
probe.rmdir()
|
||||
if not windows or zurueck != str(probe):
|
||||
print("TREIBER_FEHLER: wslpath-Rundreise: {0} -> {1} -> {2}".format(probe, windows, zurueck))
|
||||
return 2
|
||||
print("rundreise: {0} -> {1}".format(probe, windows))
|
||||
|
||||
for name, funktion in FAELLE:
|
||||
vorher = plankritik_ordner()
|
||||
try:
|
||||
funktion(skript)
|
||||
except Exception as exc:
|
||||
melde(name, False, "Ausnahme: {0!r}".format(exc))
|
||||
liegen_geblieben = sorted(str(p) for p in plankritik_ordner() - vorher)
|
||||
if liegen_geblieben:
|
||||
melde(name + " (aufraeumen)", False, "Kopien unter /tmp blieben liegen: {0}".format(liegen_geblieben))
|
||||
|
||||
print("gerechnete Fehler: {0}".format(len(FEHLERLISTE)))
|
||||
return 0 if not FEHLERLISTE else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user