Die zweite Sitzung hat die Luecke benannt: Das Arbeitsschloss
schuetzt den Commit und die Stempellaeufe, nicht die Pruefläufe. Wer
misst, waehrend ein anderer speichert, misst einen Stand, den es
nicht mehr gibt.
Am 06./07.09.2026 sind so fuenf Gesamtlaeufe wertlos geworden — rund
zwei Stunden Wartezeit ohne einen einzigen Befund. Am 01.10. dieselbe
Lage, nur anders: zwei Sitzungen im Verzeichnis, eine misst, die
andere speichert. Aufgefallen ist es nur, weil die andere es von
sich aus gesagt hat.
DIE FRAGE IST EINE MESSUNG, KEINE ZUSTAENDIGKEIT
„Haelt jemand das Schloss" waere die falsche Frage, und zwar in
beide Richtungen:
* Sie blockiert zu viel: Wer zwei Stunden am Schloss sitzt, haette
in der Zeit keinen Prueflauf mehr. Eine Sicherung, die das eigene
Arbeiten anhaelt, wird abgeschafft.
* Sie blockiert zu wenig: Filipe am Editor hat kein Schloss, mein
eigenes Werkzeug auch nicht.
Gefragt wird deshalb: Ist der Stand am Ende noch derselbe wie am
Anfang? Das trifft jede Quelle.
AN DER NOTBREMSE, NICHT IN 179 DATEIEN
Gemessen: 179 von 231 Pruef- und Messdateien rufen `notbremse`. Das
ist die eine Stelle, an der alle etwas bekommen — dieselbe
Ueberlegung wie bei der Notbremse selbst. Einzelne Dateien
nachzuruesten hilft nur bis zur naechsten ohne.
Fingerabdruck: Anzahl, Gesamtgroesse und juengste Aenderung von
workspace, assets, server, webdesign und den Seiten im
Wurzelverzeichnis — 670 Dateien in 27 ms. Kein Hash ueber den
Inhalt: Gesucht ist „hat jemand gespeichert", und das aendert immer
mindestens die Zeit.
BILDER ZAEHLEN NICHT. Die Messdateien legen selbst welche ab; eine
Wache, die bei jedem Bildschirmfoto anschlaegt, wird weggeklickt und
nimmt die echte Meldung mit.
SIE HAELT NICHTS AN. Ein Lauf, dessen Grundlage sich verschoben hat,
ist nicht „nicht in Ordnung" — er ist NICHT NACHSEHBAR. Deshalb
Rueckgabewert 3 (nicht 1, das waere eine Aussage ueber den Code; und
nicht 0, ein gruener Haken ueber einen Stand, den es nicht mehr
gibt, waere das Schlimmere) und ein ausdruecklicher Satz, dass das
kein Befund ist.
GEPRUEFT — pruef-arbeitsschloss 35 -> 48 Pruefungen, 0 Fehler:
zwei Aufnahmen hintereinander gleich (sonst waere jede Meldung
wertlos)
geaenderte Datei -> faellt auf
neue Datei -> faellt auf
neues Bild -> faellt NICHT auf
Lauf mit Aenderung -> Rueckgabe 3, sagt NICHT NACHSEHBAR
Lauf ohne Aenderung -> Rueckgabe 0 (Gegenprobe; sonst
waere „endet mit 3" auch wahr, wenn
sie IMMER 3 meldet)
Alles in einem Wegwerf-Verzeichnis mit gefaelschtem Baum — moeglich,
weil die Wache ihre Wurzel aus ihrem EIGENEN Pfad ableitet. Waere
sie festgeschrieben, liesse sich die Wache nicht pruefen, ohne am
echten Haus zu wackeln.
UND AM ECHTEN LAUF NACHGEWIESEN: Mitten in pruef-arten eine Datei
gespeichert -> Rueckgabe 3, mit Groesse und Uhrzeit. Ohne Eingriff
bei neun Laeufen (treff 85, kalender 141, material 159,
erwaehnung 129, arten, dabei-optik, tagesblick, aufgabenbrett,
kopfleiste, seiteninhalt) kein einziger Fehlalarm.
EINE ERWARTUNG VON MIR WAR FALSCH, und die Wache hatte recht: Ich
hatte im gefaelschten Baum eine zaehlende Datei erwartet, es sind
zwei — die Seite UND die Kopie der Wache selbst im server-Ordner.
Der zaehlt mit, und das ist richtig: Eine geaenderte Pruefdatei
verschiebt den Stand genauso wie eine geaenderte Seite.
Co-Authored-By: Claude Opus 5 <[email protected]>
407 lines
19 KiB
JavaScript
407 lines
19 KiB
JavaScript
/* =====================================================================
|
|
pruef-arbeitsschloss.mjs — hält das Schloss, was es verspricht?
|
|
|
|
WARUM ES DAS SCHLOSS GIBT (01.10.2026): Zwei Claude-Sitzungen
|
|
arbeiteten gleichzeitig in DogiHompage, ohne voneinander zu wissen.
|
|
Beide haben `git add -A` benutzt, beide den Stempellauf. Zweimal
|
|
ist es nur gutgegangen.
|
|
|
|
WARUM ES DIESE PRÜFUNG GIBT: Ein Schloss ist eine Sicherung, und
|
|
eine Sicherung, die man nie ausgelöst hat, ist eine Hoffnung. Die
|
|
teuren Fehler liegen bei einem Schloss alle in derselben Richtung —
|
|
es blockiert zu viel:
|
|
|
|
* Es blockiert das EIGENE Arbeiten -> wird abgeschafft.
|
|
* Es blockiert nach einem Absturz dauerhaft -> wird umgangen.
|
|
* Es blockiert, wenn es selbst kaputt ist -> wird entfernt.
|
|
|
|
Deshalb prüft diese Datei NICHT nur, dass es zuschlägt, sondern
|
|
vor allem, dass es in genau diesen drei Fällen DURCHLÄSST.
|
|
|
|
---------------------------------------------------------------------
|
|
ALLES IN EINEM WEGWERF-VERZEICHNIS
|
|
|
|
Die Prüfung legt ein eigenes kleines git-Verzeichnis an, kopiert
|
|
das Schloss und den Haken hinein und spielt dort. Das echte
|
|
`.arbeitsschloss` wird nicht angefasst — sonst nähme eine Prüfung
|
|
jemandem sein Schloss weg, und das wäre genau der Schaden, den sie
|
|
verhindern soll.
|
|
|
|
Aufrufen mit: node server/pruef-arbeitsschloss.mjs
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync, mkdirSync, copyFileSync, writeFileSync,
|
|
readFileSync, readdirSync, existsSync, chmodSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
|
|
let ok_ = 0, fehl = 0, offen = 0;
|
|
const ok = (b, text) => { if (b) { ok_++; console.log(" ok " + text); }
|
|
else { fehl++; console.log(" FEHL " + text); } };
|
|
const unklar = (text) => { offen++; console.log(" -- " + text); };
|
|
|
|
const HIER = dirname(fileURLToPath(import.meta.url));
|
|
const WURZEL = join(HIER, "..");
|
|
const SCHLOSS = join(WURZEL, "tools", "arbeitsschloss.mjs");
|
|
const HAKEN = join(WURZEL, "tools", "git-haken", "pre-commit");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
1. Steht überhaupt alles da?
|
|
--------------------------------------------------------------------- */
|
|
console.log("\n=== 1. Die Teile ===");
|
|
ok(existsSync(SCHLOSS), "tools/arbeitsschloss.mjs ist da");
|
|
ok(existsSync(HAKEN), "tools/git-haken/pre-commit ist da");
|
|
|
|
/* DAS SCHLOSS DARF NICHT IM VERLAUF LANDEN. Committet wäre es eine
|
|
Behauptung von gestern: Nach einem Klon stünde dort, dass jemand
|
|
arbeitet, der längst fertig ist — und dann blockiert es, bis es
|
|
verfällt. */
|
|
{
|
|
const ignoriert = readFileSync(join(WURZEL, ".gitignore"), "utf8");
|
|
ok(/^\.arbeitsschloss$/m.test(ignoriert),
|
|
".arbeitsschloss steht in .gitignore (sonst waere es ein Schloss von gestern)");
|
|
}
|
|
|
|
/* ==== DER HAKEN BRAUCHT LINUX-ZEILENENDEN =========================
|
|
|
|
Er hat KEINE Dateiendung, also greift keine der Regeln in
|
|
.gitattributes von sich aus. Ohne eine eigene Zeile dort wandelt
|
|
git ihn beim Auschecken auf Windows in CRLF -- und auf Linux ist
|
|
`#!/bin/sh` mit einem CR dahinter ein Programmname mit einem
|
|
unsichtbaren Zeichen am Ende („bad interpreter").
|
|
|
|
NACHGEMESSEN AM 01.10.2026, damit hier keine Behauptung steht:
|
|
Auf Windows blockiert auch ein CRLF-Haken richtig (Rueckgabe 1,
|
|
null Commits). Die Regel ist also Vorsorge und keine Reparatur --
|
|
aber ein Haken, der still nicht laeuft, ist genau die Sicherung,
|
|
die aussieht, als waere sie da.
|
|
|
|
UND EINE LEHRE AUS DEM MESSEN SELBST: Meine erste Messung
|
|
meldete 73 CR-Bytes, wo keines war. Gemessen hatte ich mit
|
|
einer Rohrkette aus `tr` und `od`, und `tr` nimmt in dieser
|
|
Shell die Maskierung fuer das Wagenruecklauf-Zeichen nicht als
|
|
Zeichen -- gezaehlt wurden am Ende Zeilenenden.
|
|
|
|
Byteweise wird deshalb hier gemessen, nicht in einer
|
|
Rohrkette: `readFileSync` ohne Zeichensatz gibt Bytes, und
|
|
13 ist 13.
|
|
*/
|
|
{
|
|
const roh = readFileSync(HAKEN);
|
|
ok(!roh.includes(13),
|
|
`der Haken hat reine LF-Zeilenenden (${roh.filter((b) => b === 13).length} CR)`);
|
|
const regeln = readFileSync(join(WURZEL, ".gitattributes"), "utf8");
|
|
ok(/tools\/git-haken\/\*\s+text\s+eol=lf/.test(regeln),
|
|
".gitattributes haelt den Haken auf LF (er hat keine Endung)");
|
|
}
|
|
|
|
/* DER HAKEN MUSS AUCH GEFUNDEN WERDEN. `.git/hooks` wird nicht
|
|
versioniert; ohne `core.hooksPath` liegt der Haken im Baum und tut
|
|
nichts. Das ist die Sorte Sicherung, die aussieht, als wäre sie da. */
|
|
{
|
|
let pfad = "";
|
|
try {
|
|
pfad = execFileSync("git", ["config", "--get", "core.hooksPath"],
|
|
{ cwd: WURZEL, encoding: "utf8" }).trim();
|
|
} catch { pfad = ""; }
|
|
ok(pfad === "tools/git-haken",
|
|
`core.hooksPath zeigt auf den Haken ("${pfad || "nicht gesetzt"}")`
|
|
+ (pfad === "tools/git-haken" ? "" : " — node tools/arbeitsschloss.mjs einrichten"));
|
|
}
|
|
|
|
/* DIE STEMPELWERKZEUGE NEHMEN ES SELBST. Abgeleitet aus dem
|
|
Quelltext, nicht angenommen: Ein Schloss, das man von Hand nehmen
|
|
muss, wird vergessen — und dann umgangen. */
|
|
for (const w of ["workspace-stempel.mjs", "seiten-stempel.mjs"]) {
|
|
const t = readFileSync(join(WURZEL, "tools", w), "utf8");
|
|
ok(/schlossKurz\(/.test(t) && /arbeitsschloss\.mjs/.test(t),
|
|
`tools/${w} nimmt das Schloss selbst`);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
2. Wer bin ich?
|
|
--------------------------------------------------------------------- */
|
|
console.log("\n=== 2. Die Kennung ===");
|
|
const { werIch } = await import("file://" + SCHLOSS.replace(/\\/g, "/"));
|
|
{
|
|
const alt = process.env.ARBEITER;
|
|
delete process.env.ARBEITER;
|
|
const ohneName = werIch();
|
|
ok(ohneName.woher === "Sitzungskennung" && /^claude-[0-9a-f]{8}$/.test(ohneName.name),
|
|
`ohne ARBEITER kommt die Sitzungskennung ("${ohneName.name}")`);
|
|
/* DAS IST DER PUNKT, AN DEM RUNONE HÄNGT: Dort fällt es auf den
|
|
Systembenutzer zurück, und zwei Sitzungen desselben Benutzers
|
|
sehen damit gleich aus — die Sicherung griff ausgerechnet
|
|
zwischen den zwei Fällen nicht, für die sie gebaut wurde. */
|
|
ok(ohneName.name !== process.env.USERNAME,
|
|
"und nicht der Benutzername — zwei Sitzungen desselben Benutzers"
|
|
+ " muessen sich unterscheiden");
|
|
process.env.ARBEITER = "probe-name";
|
|
ok(werIch().name === "probe-name", "ein ausdrueckliches ARBEITER gewinnt");
|
|
if (alt === undefined) delete process.env.ARBEITER; else process.env.ARBEITER = alt;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
3. Das Verhalten — im Wegwerf-Verzeichnis
|
|
--------------------------------------------------------------------- */
|
|
console.log("\n=== 3. Nehmen, sehen, freigeben ===");
|
|
const spiel = mkdtempSync(join(tmpdir(), "schlossprobe-"));
|
|
mkdirSync(join(spiel, "tools", "git-haken"), { recursive: true });
|
|
copyFileSync(SCHLOSS, join(spiel, "tools", "arbeitsschloss.mjs"));
|
|
copyFileSync(HAKEN, join(spiel, "tools", "git-haken", "pre-commit"));
|
|
try { chmodSync(join(spiel, "tools", "git-haken", "pre-commit"), 0o755); } catch { /* egal */ }
|
|
|
|
const SCHLOSSDATEI = join(spiel, ".arbeitsschloss");
|
|
const ICH = "claude-probe01";
|
|
const ANDERER = "claude-probe02";
|
|
|
|
/** Ruft das Schloss im Spielverzeichnis auf. */
|
|
function rufen(befehl, { wer = ICH, grund = "", verfall } = {}) {
|
|
const e = spawnSync(process.execPath,
|
|
[join(spiel, "tools", "arbeitsschloss.mjs"), befehl, grund],
|
|
{ cwd: spiel, encoding: "utf8",
|
|
env: { ...process.env, ARBEITER: wer,
|
|
...(verfall ? { SCHLOSS_VERFALL: String(verfall) } : {}) } });
|
|
return { stand: e.status, aus: (e.stdout || "") + (e.stderr || "") };
|
|
}
|
|
|
|
const schlossSetzen = (wer, vorMinuten = 0, was = "etwas") => writeFileSync(SCHLOSSDATEI,
|
|
`wer=${wer}\nseit=${new Date(Date.now() - vorMinuten * 60000).toISOString()}\n`
|
|
+ `was=${was}\nrechner=probe\n`);
|
|
|
|
ok(rufen("ansehen").aus.trim() === "frei", "ein leeres Verzeichnis ist frei");
|
|
|
|
{
|
|
const r = rufen("nehmen", { grund: "Probe" });
|
|
ok(r.stand === 0 && existsSync(SCHLOSSDATEI), `nehmen legt es an (${r.stand})`);
|
|
ok(/Sitzungskennung|ARBEITER/.test(r.aus), "und sagt, woher die Kennung kommt");
|
|
}
|
|
ok(rufen("ansehen").stand === 1, "danach meldet ansehen: belegt (Stand 1)");
|
|
ok(rufen("nehmen").stand === 0, "zweimal nehmen vom selben ist in Ordnung");
|
|
|
|
/* ---- DER FALL, DER ALLES ENTSCHEIDET -------------------------------
|
|
`fremd` muss zwischen „ich halte es" und „ein anderer haelt es"
|
|
unterscheiden. RunOnes erste Fassung konnte das nicht und hat damit
|
|
genau das richtige Verhalten bestraft. */
|
|
ok(rufen("fremd").stand === 0, "fremd: mein EIGENES Schloss blockiert mich nicht");
|
|
schlossSetzen(ANDERER);
|
|
ok(rufen("fremd").stand === 1, "fremd: ein FREMDES blockiert (Stand 1)");
|
|
ok(rufen("nehmen").stand === 1, "nehmen prallt an einem fremden ab");
|
|
{
|
|
const r = rufen("freigeben");
|
|
ok(r.stand === 1 && /gehört/.test(r.aus),
|
|
"und ein fremdes gibt man nicht einfach frei");
|
|
}
|
|
|
|
/* ---- ES VERFÄLLT. Sonst blockiert ein Absturz dauerhaft. ---------- */
|
|
schlossSetzen(ANDERER, 180);
|
|
ok(rufen("fremd").stand === 0, "ein verfallenes fremdes Schloss blockiert NICHT mehr");
|
|
{
|
|
const r = rufen("nehmen");
|
|
ok(r.stand === 0 && /verfallen/.test(r.aus),
|
|
"es wird uebernommen — und dass da jemand war, steht dabei");
|
|
}
|
|
schlossSetzen(ANDERER, 180);
|
|
ok(rufen("fremd", { verfall: 400 }).stand === 1,
|
|
"Gegenprobe: mit laengerer Frist gilt dasselbe Schloss wieder (SCHLOSS_VERFALL)");
|
|
|
|
/* ---- UNLESBAR IST NICHT BELEGT ------------------------------------
|
|
Ein kaputtes Schloss darf die Arbeit nicht anhalten. */
|
|
writeFileSync(SCHLOSSDATEI, "das ist kein Schloss\n");
|
|
{
|
|
const r = rufen("fremd");
|
|
ok(r.stand === 0, `ein unbrauchbarer Inhalt blockiert nicht (${r.stand})`);
|
|
}
|
|
writeFileSync(SCHLOSSDATEI, `wer=${ANDERER}\nseit=voelliger-unsinn\nwas=x\n`);
|
|
ok(rufen("fremd").stand === 0,
|
|
"und ein unlesbarer Zeitstempel gilt als verfallen, nicht als eben genommen");
|
|
|
|
/* ---- Freigeben ---------------------------------------------------- */
|
|
schlossSetzen(ICH);
|
|
ok(rufen("freigeben").stand === 0 && !existsSync(SCHLOSSDATEI),
|
|
"das eigene gibt man frei, und danach ist die Datei weg");
|
|
ok(rufen("freigeben").stand === 0, "zweimal freigeben ist kein Fehler");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
4. Der git-Haken — am echten git, nicht an der Behauptung
|
|
--------------------------------------------------------------------- */
|
|
console.log("\n=== 4. Der Haken am echten git ===");
|
|
{
|
|
const git = (...a) => spawnSync("git", a, { cwd: spiel, encoding: "utf8",
|
|
env: { ...process.env, ARBEITER: ICH } });
|
|
git("init", "-q");
|
|
git("config", "user.email", "[email protected]");
|
|
git("config", "user.name", "Probe");
|
|
git("config", "core.hooksPath", "tools/git-haken");
|
|
writeFileSync(join(spiel, "a.txt"), "eins\n");
|
|
git("add", "-A");
|
|
const erst = git("commit", "-q", "-m", "erster");
|
|
ok(erst.status === 0, `ohne Schloss laesst der Haken committen (${erst.status})`);
|
|
|
|
writeFileSync(join(spiel, "a.txt"), "zwei\n");
|
|
schlossSetzen(ICH);
|
|
git("add", "-A");
|
|
const eigen = git("commit", "-q", "-m", "mit eigenem Schloss");
|
|
ok(eigen.status === 0,
|
|
`mit dem EIGENEN Schloss ebenfalls (${eigen.status}) — sonst waere ordentliches`
|
|
+ " Abschliessen bestraft");
|
|
|
|
writeFileSync(join(spiel, "a.txt"), "drei\n");
|
|
schlossSetzen(ANDERER, 0, "arbeitet gerade");
|
|
git("add", "-A");
|
|
const fremd = git("commit", "-q", "-m", "darf nicht entstehen");
|
|
const text = (fremd.stdout || "") + (fremd.stderr || "");
|
|
ok(fremd.status === 1, `mit einem FREMDEN Schloss bricht der Commit ab (${fremd.status})`);
|
|
ok(/arbeitet gerade jemand anders/.test(text), "und sagt, warum");
|
|
ok(/claude-probe02/.test(text), "und WER es ist");
|
|
|
|
/* UND DIE ZAHL DER COMMITS BEWEIST ES. Eine Meldung allein könnte
|
|
auch bei einem erfolgreichen Commit erscheinen. */
|
|
const anzahl = Number(spawnSync("git", ["rev-list", "--count", "HEAD"],
|
|
{ cwd: spiel, encoding: "utf8" }).stdout.trim());
|
|
ok(anzahl === 2, `es stehen genau zwei Commits da, nicht drei (${anzahl})`);
|
|
|
|
/* DER NOTAUSGANG MUSS GEHEN. Ein Riegel ohne Notausgang wird
|
|
ausgebaut, nicht benutzt. */
|
|
const zwang = spawnSync("git", ["commit", "-q", "-m", "mit Zwang"],
|
|
{ cwd: spiel, encoding: "utf8",
|
|
env: { ...process.env, ARBEITER: ICH, SCHLOSS_ZWANG: "ja" } });
|
|
ok(zwang.status === 0, `SCHLOSS_ZWANG=ja kommt vorbei (${zwang.status})`);
|
|
|
|
/* UND OHNE DAS SCHLOSS-WERKZEUG DARF ER NICHT BLOCKIEREN: Wer den
|
|
Haken in ein Verzeichnis ohne Schloss mitnimmt, soll arbeiten
|
|
koennen. */
|
|
rmSync(join(spiel, "tools", "arbeitsschloss.mjs"));
|
|
schlossSetzen(ANDERER);
|
|
writeFileSync(join(spiel, "a.txt"), "vier\n");
|
|
git("add", "-A");
|
|
const ohneWerkzeug = git("commit", "-q", "-m", "ohne Werkzeug");
|
|
ok(ohneWerkzeug.status === 0,
|
|
`ohne das Schloss-Werkzeug laesst der Haken durch (${ohneWerkzeug.status})`);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
5. Die Standwache — hat sich der Stand WAEHREND des Laufs geaendert?
|
|
|
|
Das Schloss schuetzt den Commit und die Stempellaeufe. Es schuetzt
|
|
NICHT die Pruefläufe: Wer misst, waehrend ein anderer speichert,
|
|
misst einen Stand, den es nicht mehr gibt. Am 06./07.09.2026 sind
|
|
so fuenf Gesamtlaeufe wertlos geworden, rund zwei Stunden
|
|
Wartezeit ohne einen einzigen Befund.
|
|
|
|
DIE FRAGE IST EINE MESSUNG, KEINE ZUSTAENDIGKEIT. „Haelt jemand
|
|
das Schloss" waere die falsche: Sie blockiert zu viel (zwei
|
|
Stunden Arbeit am Schloss = zwei Stunden ohne Prueflauf) und zu
|
|
wenig (Filipe am Editor hat kein Schloss). Gefragt wird deshalb:
|
|
Ist der Stand am Ende noch derselbe?
|
|
--------------------------------------------------------------------- */
|
|
console.log("\n=== 5. Die Standwache ===");
|
|
{
|
|
const WACHE = join(WURZEL, "server", "helfer-standwache.mjs");
|
|
ok(existsSync(WACHE), "server/helfer-standwache.mjs ist da");
|
|
|
|
/* AN DER NOTBREMSE UND NICHT IN 179 DATEIEN. Abgeleitet aus dem
|
|
Quelltext: Wer eine Notbremse hat, bekommt die Wache mit. */
|
|
const bremse = readFileSync(join(WURZEL, "server", "helfer-notbremse.mjs"), "utf8");
|
|
ok(/standWache\(/.test(bremse) && /helfer-standwache\.mjs/.test(bremse),
|
|
"die Notbremse ruft die Standwache — eine Stelle fuer alle Pruefungen");
|
|
{
|
|
const alle = readdirSync(join(WURZEL, "server"))
|
|
.filter((n) => /^(pruef|mess)-.*\.mjs$/.test(n));
|
|
const mit = alle.filter((n) =>
|
|
/notbremse\(/.test(readFileSync(join(WURZEL, "server", n), "utf8")));
|
|
ok(mit.length > 150,
|
|
`${mit.length} von ${alle.length} Pruef- und Messdateien haben damit eine Standwache`);
|
|
}
|
|
|
|
/* ---- Im Wegwerf-Verzeichnis, mit einem gefaelschten Baum --------
|
|
Moeglich, weil die Wache ihre Wurzel aus ihrem EIGENEN Pfad
|
|
ableitet. Eine festgeschriebene Wurzel waere nicht pruefbar,
|
|
ohne am echten Haus zu wackeln. */
|
|
const spiel2 = mkdtempSync(join(tmpdir(), "standprobe-"));
|
|
mkdirSync(join(spiel2, "server"), { recursive: true });
|
|
mkdirSync(join(spiel2, "workspace"), { recursive: true });
|
|
copyFileSync(WACHE, join(spiel2, "server", "helfer-standwache.mjs"));
|
|
writeFileSync(join(spiel2, "workspace", "a.css"), "a{color:red}\n");
|
|
|
|
const w = await import("file://"
|
|
+ join(spiel2, "server", "helfer-standwache.mjs").replace(/\\/g, "/"));
|
|
|
|
const eins = w.standNehmen();
|
|
/* ZWEI, NICHT EINE: workspace/a.css UND die Kopie der Wache selbst,
|
|
die im server-Ordner liegt. Der server-Ordner zaehlt mit, und das
|
|
ist richtig -- eine geaenderte Pruefdatei verschiebt den Stand
|
|
genauso wie eine geaenderte Seite. (Ich hatte hier 1 erwartet;
|
|
die Wache hatte recht, nicht ich.) */
|
|
ok(eins.anzahl === 2,
|
|
`der gefaelschte Baum hat zwei zaehlende Dateien (${eins.anzahl})`
|
|
+ " -- die Seite und die Wache selbst");
|
|
ok(w.standPruefen(eins, "Probe") === true,
|
|
"ohne Aenderung meldet sie nichts");
|
|
|
|
/* ZWEIMAL HINTEREINANDER GLEICH. Ohne das waere jede Meldung
|
|
wertlos — eine Wache, die bei sich selbst anschlaegt, ist eine
|
|
Warnung, die immer kommt. */
|
|
ok(JSON.stringify({ ...w.standNehmen(), genommen: 0 })
|
|
=== JSON.stringify({ ...eins, genommen: 0 }),
|
|
"zwei Aufnahmen hintereinander sind gleich");
|
|
|
|
/* ---- UND JETZT SPEICHERT JEMAND ------------------------------- */
|
|
writeFileSync(join(spiel2, "workspace", "a.css"), "a{color:blue;font-size:2rem}\n");
|
|
ok(w.standPruefen(eins, "Probe") === false,
|
|
"eine geaenderte Datei faellt auf");
|
|
|
|
const zwei = w.standNehmen();
|
|
writeFileSync(join(spiel2, "workspace", "b.js"), "// neu\n");
|
|
ok(w.standPruefen(zwei, "Probe") === false, "eine NEUE Datei ebenfalls");
|
|
|
|
/* ---- BILDER ZAEHLEN NICHT ------------------------------------
|
|
Die Messdateien legen selbst welche ab (`server/mess-*.png`).
|
|
Eine Wache, die bei jedem Bildschirmfoto anschlaegt, wird
|
|
weggeklickt und nimmt die echte Meldung mit. */
|
|
const drei = w.standNehmen();
|
|
writeFileSync(join(spiel2, "workspace", "bild.png"), "nicht wirklich ein Bild");
|
|
ok(w.standPruefen(drei, "Probe") === true,
|
|
"ein neues Bild faellt NICHT auf — Messungen legen selbst welche ab");
|
|
|
|
/* ---- DER RUECKGABEWERT IST 3, NICHT 1 ------------------------
|
|
„Nicht in Ordnung" waere eine Aussage ueber den Code, und die
|
|
hat so ein Lauf nicht gemacht. 3 ist der dritte Ausgang.
|
|
Geprueft am echten Verhalten: ein eigener Prozess. */
|
|
{
|
|
const script = join(spiel2, "lauf.mjs");
|
|
writeFileSync(script,
|
|
'import { standWache } from "./server/helfer-standwache.mjs";\n'
|
|
+ 'import { writeFileSync } from "node:fs";\n'
|
|
+ 'import { join, dirname } from "node:path";\n'
|
|
+ 'import { fileURLToPath } from "node:url";\n'
|
|
+ 'standWache("Probelauf");\n'
|
|
+ 'const hier = dirname(fileURLToPath(import.meta.url));\n'
|
|
+ 'writeFileSync(join(hier, "workspace", "c.css"), "c{}\\n");\n');
|
|
const e = spawnSync(process.execPath, [script], { cwd: spiel2, encoding: "utf8" });
|
|
ok(e.status === 3,
|
|
`ein Lauf, bei dem sich der Stand aendert, endet mit 3 (${e.status})`);
|
|
ok(/NICHT NACHSEHBAR/.test(e.stderr || ""), "und sagt NICHT NACHSEHBAR");
|
|
ok(/KEIN BEFUND AM CODE/.test(e.stderr || ""),
|
|
"und ausdruecklich, dass das kein Befund am Code ist");
|
|
|
|
/* GEGENPROBE: Ohne Aenderung bleibt es bei 0. Ohne diese Zeile
|
|
waere „endet mit 3" auch dann wahr, wenn sie IMMER 3 meldet. */
|
|
writeFileSync(script,
|
|
'import { standWache } from "./server/helfer-standwache.mjs";\n'
|
|
+ 'standWache("Probelauf ohne Aenderung");\n');
|
|
const e2 = spawnSync(process.execPath, [script], { cwd: spiel2, encoding: "utf8" });
|
|
ok(e2.status === 0, `ein Lauf ohne Aenderung endet mit 0 (${e2.status})`);
|
|
}
|
|
|
|
try { rmSync(spiel2, { recursive: true, force: true }); } catch { /* Rest */ }
|
|
}
|
|
|
|
try { rmSync(spiel, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
|
|
|
console.log(`\n${ok_} Pruefungen, ${fehl} Fehler, ${offen} nicht nachsehbar`);
|
|
console.log(fehl ? "NICHT IN ORDNUNG\n" : "ALLES IN ORDNUNG\n");
|
|
process.exit(fehl ? 1 : 0);
|