Erster Stand des öffentlichen Ausschnitts aus dem Vault. Die .gitignore arbeitet als Positivliste: ausgeschlossen ist alles, eingeschlossen nur, was ausdrücklich wieder aufgenommen wird. Notizen, Projekte, Finanzen, das Sitzungsgedächtnis und settings.local.json bleiben damit außen vor. Der Kundenname in AI/Regelwerk/seo.md ist durch eine neutrale Bezeichnung ersetzt; die Regeln selbst sind unverändert. Co-Authored-By: Claude Opus 5 <[email protected]>
169 lines
6.8 KiB
Python
169 lines
6.8 KiB
Python
"""Regressionsnachweis fuer welle_vergleich.py: Grundlinie zuerst, dann jeder Angriffsfall einzeln auf
|
|
einer frischen Wegwerfkopie. Je Fall werden Exit-Code und ausloesende Meldung geprueft.
|
|
Aufruf: python welle_vergleich_treiber.py Exit 0, wenn jeder Fall so ausgeht wie erwartet.
|
|
"""
|
|
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
WERKZEUG = Path(__file__).parent / "welle_vergleich.py"
|
|
|
|
|
|
def git(ordner: Path, *args: str) -> None:
|
|
subprocess.run(["git", "-C", str(ordner), *args], check=True, capture_output=True, stdin=subprocess.DEVNULL)
|
|
|
|
|
|
def schreibe(pfad: Path, text: str) -> None:
|
|
pfad.parent.mkdir(parents=True, exist_ok=True)
|
|
pfad.write_bytes(text.encode("utf-8"))
|
|
|
|
|
|
def baue(wurzel: Path) -> tuple[Path, Path, Path]:
|
|
"""Repo mit drei committeten Dateien, danach die 'Welle': a.py geaendert, sub/neu.py neu, weg.py geloescht,
|
|
doku.md geaendert (Prosa). Die Kopie traegt denselben Stand."""
|
|
projekt, mat = wurzel / "projekt", wurzel / "mat"
|
|
projekt.mkdir()
|
|
git(projekt, "init", "-q")
|
|
git(projekt, "config", "user.email", "[email protected]")
|
|
git(projekt, "config", "user.name", "Treiber")
|
|
git(projekt, "config", "core.autocrlf", "false")
|
|
schreibe(projekt / "a.py", "eins\nzwei\ndrei\n")
|
|
schreibe(projekt / "b.py", "bleibt\n")
|
|
schreibe(projekt / "weg.py", "weg\n")
|
|
schreibe(projekt / "doku.md", "alt\n")
|
|
git(projekt, "add", "-A")
|
|
git(projekt, "commit", "-q", "-m", "basis")
|
|
schreibe(projekt / "a.py", "EINS\nzwei\nDREI\n")
|
|
schreibe(projekt / "sub" / "neu.py", "neu eins\nneu zwei\n")
|
|
(projekt / "weg.py").unlink()
|
|
schreibe(projekt / "doku.md", "neue Prosa des Executers\n")
|
|
schreibe(mat / "a.py", "EINS\nzwei\nDREI\n")
|
|
schreibe(mat / "b.py", "bleibt\n")
|
|
schreibe(mat / "sub" / "neu.py", "neu eins\nneu zwei\n")
|
|
schreibe(mat / "doku.md", "anderer Stand in der Kopie\n")
|
|
task = wurzel / "task-01.md"
|
|
schreibe(
|
|
task,
|
|
"# Task\n\n## Zieldateien\n\n"
|
|
f"- `{projekt}\\a.py` (geändert)\n"
|
|
f"- `{projekt}\\sub\\neu.py` (neu)\n"
|
|
f"- `{projekt}\\weg.py` (gelöscht)\n"
|
|
f"- `{projekt}\\doku.md` (geändert)\n\n"
|
|
"Keine andere Datei wird angefasst.\n\n## Regeln\n\n- `nicht/gemeint.py` (kein Ziel)\n",
|
|
)
|
|
return projekt, mat, task
|
|
|
|
|
|
def fall_grundlinie(projekt, mat, task, wurzel):
|
|
pass
|
|
|
|
|
|
def fall_fremde_datei(projekt, mat, task, wurzel):
|
|
schreibe(projekt / "fremd.py", "x\n")
|
|
|
|
|
|
def fall_ziel_unveraendert(projekt, mat, task, wurzel):
|
|
schreibe(projekt / "a.py", "eins\nzwei\ndrei\n")
|
|
|
|
|
|
def fall_abweichung_erste_zeile(projekt, mat, task, wurzel):
|
|
schreibe(projekt / "a.py", "Eins\nzwei\nDREI\n")
|
|
|
|
|
|
def fall_abweichung_letzte_zeile(projekt, mat, task, wurzel):
|
|
schreibe(projekt / "a.py", "EINS\nzwei\nDrei\n")
|
|
|
|
|
|
def fall_abweichung_letzte_zieldatei(projekt, mat, task, wurzel):
|
|
schreibe(projekt / "sub" / "neu.py", "neu eins\nneu ZWEI\n")
|
|
|
|
|
|
def fall_crlf(projekt, mat, task, wurzel):
|
|
schreibe(projekt / "a.py", "EINS\r\nzwei\r\nDREI\r\n")
|
|
|
|
|
|
def fall_fehlt_in_kopie(projekt, mat, task, wurzel):
|
|
(mat / "sub" / "neu.py").unlink()
|
|
|
|
|
|
def fall_ohne_zieldateien(projekt, mat, task, wurzel):
|
|
schreibe(task, "# Task\n\n## Regeln\n\nnichts\n")
|
|
|
|
|
|
def fall_kopie_fehlt(projekt, mat, task, wurzel):
|
|
shutil.rmtree(mat)
|
|
|
|
|
|
def fall_nicht_geloescht(projekt, mat, task, wurzel):
|
|
schreibe(projekt / "weg.py", "weg\n")
|
|
|
|
|
|
def fall_prosa_unveraendert(projekt, mat, task, wurzel):
|
|
schreibe(projekt / "doku.md", "alt\n")
|
|
|
|
|
|
def fall_ziel_ausserhalb(projekt, mat, task, wurzel):
|
|
text = task.read_text(encoding="utf-8")
|
|
schreibe(task, text.replace("## Regeln", "") .replace("Keine andere Datei", "- `C:\\anderswo\\x.py` (neu)\n\nKeine andere Datei"))
|
|
|
|
|
|
def fall_laengere_datei(projekt, mat, task, wurzel):
|
|
schreibe(projekt / "a.py", "EINS\nzwei\nDREI\nvier\n")
|
|
|
|
|
|
FAELLE = [
|
|
("Grundlinie", fall_grundlinie, 0, "0 Befund(e)."),
|
|
("fremde Datei im Baum", fall_fremde_datei, 2, "fremd.py: geaendert"),
|
|
("Zieldatei unveraendert", fall_ziel_unveraendert, 2, "a.py: Zieldatei ist unveraendert"),
|
|
("Abweichung in der ersten Zeile", fall_abweichung_erste_zeile, 2, "a.py: weicht vom Plan ab -- Zeile 1"),
|
|
("Abweichung in der letzten Zeile", fall_abweichung_letzte_zeile, 2, "a.py: weicht vom Plan ab -- Zeile 3"),
|
|
("Abweichung in der letzten Zieldatei", fall_abweichung_letzte_zieldatei, 2, "sub/neu.py: weicht vom Plan ab -- Zeile 2"),
|
|
("CRLF statt LF", fall_crlf, 2, "nur die Zeilenenden weichen ab"),
|
|
("Datei fehlt in der Kopie", fall_fehlt_in_kopie, 2, "sub/neu.py: fehlt in der Kopie"),
|
|
("Taskdatei ohne Zieldateien", fall_ohne_zieldateien, 2, "keine Zieldateien gefunden"),
|
|
("Kopie fehlt", fall_kopie_fehlt, 2, "fehlt -- ohne sie gibt es keinen Byte-Vergleich"),
|
|
("Loeschung nicht ausgefuehrt", fall_nicht_geloescht, 2, "weg.py: soll geloescht sein, steht aber noch im Baum"),
|
|
("Prosa-Datei unveraendert", fall_prosa_unveraendert, 2, "doku.md: Zieldatei ist unveraendert"),
|
|
("Zieldatei ausserhalb des Projekts", fall_ziel_ausserhalb, 2, "liegt nicht unter dem Projekt"),
|
|
# Die angehaengte Zeile steht der leeren Restzeile der Kopie gegenueber, deshalb "Zeile 4" und nicht "Laenge".
|
|
("Datei laenger als der Plan", fall_laengere_datei, 2, "a.py: weicht vom Plan ab -- Zeile 4: Baum b'vier'"),
|
|
]
|
|
|
|
|
|
def main() -> int:
|
|
fehler = 0
|
|
for name, angriff, exit_erwartet, meldung in FAELLE:
|
|
wurzel = Path(tempfile.mkdtemp(prefix="wv_"))
|
|
try:
|
|
projekt, mat, task = baue(wurzel)
|
|
angriff(projekt, mat, task, wurzel)
|
|
lauf = subprocess.run(
|
|
[sys.executable, "-X", "utf8", str(WERKZEUG), "--projekt", str(projekt), "--mat", str(mat),
|
|
"--prosa", "doku.md", "--", str(task)],
|
|
capture_output=True, text=True, encoding="utf-8", errors="replace", stdin=subprocess.DEVNULL,
|
|
)
|
|
ausgabe = lauf.stdout + lauf.stderr
|
|
gut = lauf.returncode == exit_erwartet and meldung in ausgabe
|
|
if name != "Grundlinie" and gut:
|
|
# Genau ein Befund je Fall, ausser wo der Angriff zwangslaeufig zwei ausloest.
|
|
pass
|
|
print(f"{'ok ' if gut else 'FEHLER'} {name}: Exit {lauf.returncode} (erwartet {exit_erwartet})")
|
|
if not gut:
|
|
fehler += 1
|
|
print(" erwartete Meldung: " + meldung)
|
|
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.")
|
|
return 2 if fehler else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|