Abgleich mit dem Vault-Stand a201514
16 Datei(en) geändert. Erzeugt von .vault-tools/auto-push/werkzeuge_spiegel.py.
This commit is contained in:
1 parent
fb8bc625e3
commit
4d90e6be60
16 files changed
+781
-36
No files matched your search
@@ -0,0 +1,246 @@
|
||||
"""Regressionsnachweis fuer die Zielzuordnung von plan_materialisiere.py und plan_bloecke.py.
|
||||
|
||||
Anlass 2026-10-17 (Plankritik von Paket R1 im Edge-Stack, Befunde 1 und 5): Die Ueberschrift
|
||||
"#### Stelle 30 — `host/logrotate/edge-traefik`" nennt einen Pfad ohne Endung. PFAD erkannte ihn nicht,
|
||||
das Ziel blieb beim vorigen (`tests/systemd/test-units.sh`), und der Block "Die ganze Datei ist neu"
|
||||
ueberschrieb es mit Exit 0 und "0 Befund(e)". Die Faelle unten bauen diese Form nach, an erster und an
|
||||
letzter Stelle, ueber eine pfadlose Unterueberschrift hinweg und fuer jede schreibende Marke, dazu die
|
||||
Ueberschriften aus bestehenden Plaenen, die kein Befund sein duerfen (Ordner, absoluter Pfad, erkannte
|
||||
Zieldatei neben einem Pfad ohne Endung, Token mit Leerraum).
|
||||
Seit dem Nachtrag vom selben Tag ist eine Datei ohne Endung Ziel, wenn sie genau so unter "## Zieldateien"
|
||||
steht, auch als absoluter Eintrag; ein Name ohne Schraegstrich, der in der Kopie liegt oder der Dateiname
|
||||
eines Eintrags ist, ist ein Befund (vorher ging `Dockerfile` still ins vorige Ziel). Die Faelle dazu stehen
|
||||
am Ende der Liste; "{PROJEKT}" in einer Taskdatei ist der absolute Projektordner des Falls.
|
||||
Grundlinie zuerst, dann jeder Fall einzeln in einem frischen Wegwerfordner. Je Fall werden Exit-Code,
|
||||
ausloesende Meldung und bei plan_materialisiere.py der Inhalt jeder Datei der Kopie geprueft.
|
||||
Aufruf: python plan_ziel_treiber.py [--werkzeuge <ordner>] Vorgabe: der Ordner dieses Skripts.
|
||||
Exit 0, wenn jeder Fall so ausgeht wie erwartet, sonst 2.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
# Projekt jedes Falls: eine Datei mit Endung, eine ohne, eine Doku, zwei ohne Endung im Wurzelordner
|
||||
BASIS = {
|
||||
"tests/a.sh": "eins\nzwei\ndrei\n",
|
||||
"host/logrotate/edge-traefik": "alt\n",
|
||||
"README.md": "# Projekt\n\n- host/ Ordner\n",
|
||||
"Dockerfile": "FROM alt\n",
|
||||
".gitignore": "data/\n",
|
||||
}
|
||||
Z = "```"
|
||||
|
||||
|
||||
def aus_wird(ueberschrift: str, alt: str, neu: str) -> str:
|
||||
return f"{ueberschrift}\n\nAus:\n\n{Z}text\n{alt}\n{Z}\n\nwird:\n\n{Z}text\n{neu}\n{Z}\n\n"
|
||||
|
||||
|
||||
def ganz(ueberschrift: str, text: str) -> str:
|
||||
return f"{ueberschrift}\n\nDie ganze Datei ist neu:\n\n{Z}text\n{text}\n{Z}\n\n"
|
||||
|
||||
|
||||
def anhaengen(ueberschrift: str, text: str) -> str:
|
||||
return f"{ueberschrift}\n\nHäng diesen Block ans Ende der Datei:\n\n{Z}text\n{text}\n{Z}\n\n"
|
||||
|
||||
|
||||
def befehl(ueberschrift: str) -> str:
|
||||
return f"{ueberschrift}\n\nAufruf:\n\n{Z}bash\nbash tests/.work/r1-01/umgebung.sh\n{Z}\n\n"
|
||||
|
||||
|
||||
def zieldateien(*pfade: str) -> str:
|
||||
return "# Task 01\n\n## Zieldateien\n\n" + "".join(f"- `{p}` (geändert)\n" for p in pfade) + "\n## Prüfstand\n\n"
|
||||
|
||||
|
||||
KOPF = "# Task 01\n\n## Prüfstand\n\n"
|
||||
STELLE_A = aus_wird("#### Stelle 1 — `tests/a.sh`", "zwei", "ZWEI")
|
||||
OHNE = "`host/logrotate/edge-traefik`"
|
||||
MELDUNG = f"nennt {OHNE} und keine erkannte Zieldatei"
|
||||
GEPLANT = zieldateien("tests/a.sh", "host/logrotate/edge-traefik")
|
||||
|
||||
# Name, Werkzeug, Taskdatei, erwarteter Exit, erwartete Meldung, Zahl der BEFUND-Zeilen,
|
||||
# erwartete Dateien nach dem Lauf (nur "mat"; jede nicht genannte Datei bleibt wie in BASIS)
|
||||
FAELLE = [
|
||||
("Grundlinie", "mat", KOPF + STELLE_A, 0, "0 Befund(e).", 0,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
# Die Form aus R1: Pfad ohne Endung nach einem erkannten Ziel (Schaden an letzter Stelle)
|
||||
("R1-Form: ganze Datei, letzte Stelle", "mat",
|
||||
KOPF + STELLE_A + ganz(f"#### Stelle 2 — {OHNE}", "neu"), 2, MELDUNG, 1,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
# Dieselbe Form als erste Stelle: vorher kein Ziel, der Block ging still als "uebergangen" durch
|
||||
("ganze Datei, erste Stelle", "mat",
|
||||
KOPF + ganz(f"#### Stelle 1 — {OHNE}", "neu") + aus_wird("#### Stelle 2 — `tests/a.sh`", "zwei", "ZWEI"), 2, MELDUNG, 1,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
("Aus/wird unter Pfad ohne Endung", "mat",
|
||||
KOPF + aus_wird("#### Stelle 1 — `tests/a.sh`", "eins", "EINS") + aus_wird(f"#### Stelle 2 — {OHNE}", "zwei", "ZWEI"),
|
||||
2, MELDUNG, 1, {"tests/a.sh": "EINS\nzwei\ndrei\n"}),
|
||||
("anhaengen unter Pfad ohne Endung", "mat",
|
||||
KOPF + STELLE_A + anhaengen(f"#### Stelle 2 — {OHNE}", "vier"), 2, MELDUNG, 1,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
# Das Ziel bestimmt die letzte Ueberschrift mit Pfad; eine pfadlose Unterueberschrift aendert daran nichts
|
||||
("ueber pfadlose Unterueberschrift", "mat",
|
||||
KOPF + STELLE_A + f"### Stelle 2 — {OHNE}\n\n" + ganz("#### Neue Fassung", "neu"), 2, MELDUNG, 1,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
("zwei Stellen, zwei Befunde", "mat",
|
||||
KOPF + ganz(f"#### Stelle 1 — {OHNE}", "neu") + STELLE_A + ganz(f"#### Stelle 3 — {OHNE}", "neu"), 2, "2 Befund(e).", 2,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
# Kein Befund: eine Ueberschrift mit erkannter Zieldatei hebt den Pfad ohne Endung davor auf
|
||||
("Pfad ohne Endung, danach erkanntes Ziel", "mat",
|
||||
KOPF + f"### Hinweis zu {OHNE}\n\nProsa.\n\n" + STELLE_A, 0, "0 Befund(e).", 0,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
("Pfad ohne Endung ueber einem Befehl", "mat",
|
||||
KOPF + befehl("#### Messordner `tests/.work/r1-01`") + STELLE_A, 0, "0 Befund(e).", 0,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
# Kein Befund: Ueberschriften aus bestehenden Plaenen (Paket E2 Task 367, S3 Task 298, NF Task 703)
|
||||
("Ordner mit Schraegstrich", "mat",
|
||||
KOPF + STELLE_A + aus_wird("#### Änderung 2 — dass `host/` nicht der einzige Ort ist", "drei", "DREI"), 0, "0 Befund(e).", 0,
|
||||
{"tests/a.sh": "eins\nZWEI\nDREI\n"}),
|
||||
("absoluter Pfad", "mat",
|
||||
KOPF + STELLE_A + aus_wird("#### Änderung 2 — ein Wegwerf-`/proc` statt zweier Dateien", "drei", "DREI"), 0, "0 Befund(e).", 0,
|
||||
{"tests/a.sh": "eins\nZWEI\nDREI\n"}),
|
||||
("erkannte Zieldatei neben Pfad ohne Endung", "mat",
|
||||
KOPF + STELLE_A + aus_wird("#### Änderung in `README.md`: `glattstellen/beginn` ohne Schlüssel", "host/ Ordner", "host/ Ordner, neu"),
|
||||
0, "0 Befund(e).", 0, {"tests/a.sh": "eins\nZWEI\ndrei\n", "README.md": "# Projekt\n\n- host/ Ordner, neu\n"}),
|
||||
("Token mit Leerraum", "mat",
|
||||
KOPF + STELLE_A + aus_wird("#### Endpunkt `GET /api/paper`", "drei", "DREI"), 0, "0 Befund(e).", 0,
|
||||
{"tests/a.sh": "eins\nZWEI\nDREI\n"}),
|
||||
# plan_bloecke.py, Modus vor: Der Aus-Block steht zufaellig auch im vorigen Ziel, deshalb vorher still.
|
||||
# Je Stelle zwei Befunde (Aus und wird); keiner davon darf zusaetzlich gegen das vorige Ziel laufen.
|
||||
("bloecke: Grundlinie", "vor", KOPF + STELLE_A, 0, "0 Befund(e).", 0, None),
|
||||
("bloecke: letzte Stelle", "vor",
|
||||
KOPF + STELLE_A + aus_wird(f"#### Stelle 2 — {OHNE}", "drei", "DREI"), 2, MELDUNG, 2, None),
|
||||
("bloecke: erste Stelle", "vor",
|
||||
KOPF + aus_wird(f"#### Stelle 1 — {OHNE}", "drei", "DREI") + STELLE_A, 2, MELDUNG, 2, None),
|
||||
("bloecke: ueber pfadlose Unterueberschrift", "vor",
|
||||
KOPF + STELLE_A + f"### Stelle 2 — {OHNE}\n\n" + aus_wird("#### Änderung", "drei", "DREI"), 2, MELDUNG, 2, None),
|
||||
("bloecke: Ordner, absolut, Leerraum, erkannte Zieldatei", "vor",
|
||||
KOPF + STELLE_A + aus_wird("#### Änderung 2 — dass `host/` nicht der einzige Ort ist", "drei", "DREI")
|
||||
+ aus_wird("#### Änderung 3 — Wegwerf-`/proc`", "eins", "EINS")
|
||||
+ aus_wird("#### Endpunkt `GET /api/paper`", "eins", "EINS")
|
||||
+ aus_wird("#### Änderung in `README.md`: `glattstellen/beginn`", "host/ Ordner", "host/ Ordner, neu"),
|
||||
0, "0 Befund(e).", 0, None),
|
||||
# Modus nach: Der Baum traegt schon den wird-Text im vorigen Ziel
|
||||
("bloecke nach: letzte Stelle", "nach",
|
||||
KOPF + aus_wird("#### Stelle 1 — `tests/a.sh`", "eins", "EINS") + aus_wird(f"#### Stelle 2 — {OHNE}", "drei", "DREI"),
|
||||
2, MELDUNG, 2, None),
|
||||
# Nachtrag: Datei ohne Endung unter "## Zieldateien" ist Ziel (R1 Stelle 30 mit Eintrag), letzte und erste Stelle
|
||||
("Zieldateien: ganze Datei ohne Endung, letzte Stelle", "mat",
|
||||
GEPLANT + STELLE_A + ganz(f"#### Stelle 2 — {OHNE}", "neu"), 0, "0 Befund(e).", 0,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n", "host/logrotate/edge-traefik": "neu\n"}),
|
||||
("Zieldateien: ganze Datei ohne Endung, erste Stelle", "mat",
|
||||
GEPLANT + ganz(f"#### Stelle 1 — {OHNE}", "neu") + aus_wird("#### Stelle 2 — `tests/a.sh`", "zwei", "ZWEI"),
|
||||
0, "0 Befund(e).", 0, {"tests/a.sh": "eins\nZWEI\ndrei\n", "host/logrotate/edge-traefik": "neu\n"}),
|
||||
("Zieldateien: Aus/wird in Datei ohne Endung", "mat",
|
||||
GEPLANT + STELLE_A + aus_wird(f"#### Stelle 2 — {OHNE}", "alt", "ALT"), 0, "0 Befund(e).", 0,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n", "host/logrotate/edge-traefik": "ALT\n"}),
|
||||
# "./Dockerfile" in der Ueberschrift, "Dockerfile" unter Zieldateien
|
||||
("Zieldateien: ./Dockerfile", "mat",
|
||||
zieldateien("tests/a.sh", "Dockerfile") + STELLE_A + ganz("#### Stelle 2 — `./Dockerfile`", "FROM neu"),
|
||||
0, "0 Befund(e).", 0, {"tests/a.sh": "eins\nZWEI\ndrei\n", "Dockerfile": "FROM neu\n"}),
|
||||
# Absoluter Eintrag: relativ zum Ordner vor docs/plans (Trading Plattform, Task 90: `.gitignore`)
|
||||
("Zieldateien: absoluter Eintrag", "mat",
|
||||
zieldateien("tests/a.sh", "{PROJEKT}\\.gitignore") + STELLE_A + aus_wird("### Schritt 2 — `.gitignore`", "data/", "data/\nartefakte/"),
|
||||
0, "0 Befund(e).", 0, {"tests/a.sh": "eins\nZWEI\ndrei\n", ".gitignore": "data/\nartefakte/\n"}),
|
||||
# Vorrang: Ein PFAD-Treffer in derselben Ueberschrift bleibt Ziel, auch neben einer Zieldatei ohne Endung
|
||||
("Zieldateien: PFAD-Treffer hat Vorrang", "mat",
|
||||
GEPLANT + STELLE_A + aus_wird(f"#### Änderung in `README.md`, Zeile {OHNE}", "host/ Ordner", "host/ Ordner, neu"),
|
||||
0, "0 Befund(e).", 0, {"tests/a.sh": "eins\nZWEI\ndrei\n", "README.md": "# Projekt\n\n- host/ Ordner, neu\n"}),
|
||||
# Ein Muster unter Zieldateien ist kein Ziel; der Pfad bleibt ein Befund
|
||||
("Zieldateien: Muster ist kein Ziel", "mat",
|
||||
zieldateien("tests/a.sh", "host/logrotate/*") + STELLE_A + ganz("#### Stelle 2 — `host/logrotate/*`", "neu"),
|
||||
2, "nennt `host/logrotate/*` und keine erkannte Zieldatei", 1, {"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
# Name ohne Schraegstrich: liegt in der Kopie oder ist Dateiname eines Eintrags -> Befund statt stillem Ziel
|
||||
("Dockerfile ohne Eintrag, letzte Stelle", "mat",
|
||||
KOPF + STELLE_A + ganz("#### Stelle 2 — `Dockerfile`", "FROM neu"), 2, "nennt `Dockerfile` und keine erkannte Zieldatei", 1,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
("Dockerfile ohne Eintrag, erste Stelle", "mat",
|
||||
KOPF + ganz("#### Stelle 1 — `Dockerfile`", "FROM neu") + aus_wird("#### Stelle 2 — `tests/a.sh`", "zwei", "ZWEI"),
|
||||
2, "nennt `Dockerfile` und keine erkannte Zieldatei", 1, {"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
("Dateiname eines Eintrags ohne Pfad", "mat",
|
||||
GEPLANT + STELLE_A + ganz("#### Stelle 2 — `edge-traefik`", "neu"), 2, "nennt `edge-traefik` und keine erkannte Zieldatei", 1,
|
||||
{"tests/a.sh": "eins\nZWEI\ndrei\n"}),
|
||||
# Kein Befund: ein Bezeichner ohne Datei dieses Namens (bestehende Plaene: `main`, `pytest`, `Order`)
|
||||
("Bezeichner ohne Datei", "mat",
|
||||
KOPF + STELLE_A + aus_wird("#### Änderung 2 — `main` ruft `pruefe` auf", "drei", "DREI"), 0, "0 Befund(e).", 0,
|
||||
{"tests/a.sh": "eins\nZWEI\nDREI\n"}),
|
||||
("bloecke: Zieldateien, Datei ohne Endung", "vor",
|
||||
GEPLANT + STELLE_A + aus_wird(f"#### Stelle 2 — {OHNE}", "alt", "ALT"), 0, "0 Befund(e).", 0, None),
|
||||
("bloecke: Zieldateien, absoluter Eintrag", "vor",
|
||||
zieldateien("tests/a.sh", "{PROJEKT}\\.gitignore") + STELLE_A + aus_wird("### Schritt 2 — `.gitignore`", "data/", "data/\nartefakte/"),
|
||||
0, "0 Befund(e).", 0, None),
|
||||
("bloecke: Dockerfile ohne Eintrag", "vor",
|
||||
KOPF + STELLE_A + aus_wird("#### Stelle 2 — `Dockerfile`", "drei", "DREI"), 2, "nennt `Dockerfile` und keine erkannte Zieldatei", 2, None),
|
||||
]
|
||||
|
||||
|
||||
def schreibe(pfad: Path, text: str) -> None:
|
||||
pfad.parent.mkdir(parents=True, exist_ok=True)
|
||||
pfad.write_bytes(text.encode("utf-8"))
|
||||
|
||||
|
||||
def fahre(werkzeuge: Path, art: str, wurzel: Path, plan: Path) -> subprocess.CompletedProcess:
|
||||
if art == "mat":
|
||||
befehl = [str(werkzeuge / "plan_materialisiere.py"), str(plan), str(wurzel / "kopie"), "task-01.md"]
|
||||
else:
|
||||
befehl = [str(werkzeuge / "plan_bloecke.py"), art, str(plan / "task-01.md"), "--projekt", str(wurzel / "kopie")]
|
||||
return subprocess.run([sys.executable, "-X", "utf8", "-B", *befehl], capture_output=True, text=True,
|
||||
encoding="utf-8", errors="replace", stdin=subprocess.DEVNULL)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
zerleger = argparse.ArgumentParser()
|
||||
zerleger.add_argument("--werkzeuge", default=str(Path(__file__).resolve().parent))
|
||||
werkzeuge = Path(zerleger.parse_args().werkzeuge)
|
||||
fehler = 0
|
||||
for name, art, task, exit_erwartet, meldung, anzahl, dateien in FAELLE:
|
||||
wurzel = Path(tempfile.mkdtemp(prefix="pz_"))
|
||||
try:
|
||||
basis = dict(BASIS)
|
||||
if art == "nach":
|
||||
# Stand nach der Welle: Stelle 1 angewendet, Stelle 2 im vorigen Ziel statt in edge-traefik
|
||||
basis["tests/a.sh"] = "EINS\nzwei\nDREI\n"
|
||||
for rel, text in basis.items():
|
||||
schreibe(wurzel / "kopie" / rel, text)
|
||||
# {PROJEKT}: fuer plan_bloecke der Ordner hinter --projekt; fuer plan_materialisiere ein eigener
|
||||
# Projektordner, unter dessen docs/plans die Taskdatei liegt (die Kopie liegt woanders)
|
||||
plan = wurzel / "plan"
|
||||
if "{PROJEKT}" in task:
|
||||
projekt = wurzel / ("projekt" if art == "mat" else "kopie")
|
||||
plan = projekt / "docs" / "plans" / "p" if art == "mat" else plan
|
||||
task = task.replace("{PROJEKT}", str(projekt))
|
||||
schreibe(plan / "task-01.md", task)
|
||||
lauf = fahre(werkzeuge, art, wurzel, plan)
|
||||
ausgabe = lauf.stdout + lauf.stderr
|
||||
gruende = []
|
||||
if lauf.returncode != exit_erwartet:
|
||||
gruende.append(f"Exit {lauf.returncode}, erwartet {exit_erwartet}")
|
||||
if meldung not in ausgabe:
|
||||
gruende.append(f"Meldung fehlt: {meldung}")
|
||||
if ausgabe.count("BEFUND ") != anzahl:
|
||||
gruende.append(f"{ausgabe.count('BEFUND ')} BEFUND-Zeile(n), erwartet {anzahl}")
|
||||
# Jede Datei der Kopie: erwarteter Inhalt, sonst unveraendert wie BASIS
|
||||
for rel, text in basis.items() if dateien is not None else ():
|
||||
soll = dateien.get(rel, text)
|
||||
ist = (wurzel / "kopie" / rel).read_bytes().decode("utf-8", errors="replace")
|
||||
if ist != soll:
|
||||
gruende.append(f"{rel}: {ist!r}, erwartet {soll!r}")
|
||||
print(f"{'ok ' if not gruende else 'FEHLER'} {name}: Exit {lauf.returncode}")
|
||||
if gruende:
|
||||
fehler += 1
|
||||
for grund in gruende:
|
||||
print(" " + grund)
|
||||
print(" " + ausgabe.strip().replace("\n", "\n "))
|
||||
if name == "Grundlinie":
|
||||
print("Die Grundlinie ist nicht sauber -- der Rest misst nichts.")
|
||||
return 2
|
||||
finally:
|
||||
shutil.rmtree(wurzel, ignore_errors=True)
|
||||
print(f"{fehler} Fall/Faelle mit falschem Ausgang von {len(FAELLE)}.")
|
||||
return 2 if fehler else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user