tests: Abnahmetest fuer Phase A der Fassungs-Meldung

Phase A des Umstiegs auf MCP 2026-07-28 ist "observe-only": erst
feststellen, welche Protokollfassungen die Gegenstellen ueberhaupt
verlangen, bevor irgendetwas durchgesetzt wird. Der Vertrag dafuer
steht im Kopf der Datei; dies ist der getrennte Test dazu.

GETRENNT ist hier die Bedingung, nicht die Beschreibung. An derselben
Aufgabe war der Defekt an vier aufeinanderfolgenden Laeufen der
SELBSTTEST, nie der Code - wer implementiert, schreibt den Test nicht.
Deshalb ist er hier im Repo und nicht in der Werkstatt der Erzeuger.

Nachgewiesen unterscheidungsfaehig, in beide Richtungen:
  * Rueckgabe 1 und zwei Fehlschlaege gegen eine absichtlich kaputte
    Fassung,
  * Rueckgabe 0 gegen eine korrekte.
Ein Test, der nur besteht, misst nichts.

Zehn Pruefungen, darunter die zwei, die in Handfassungen zuerst
fallen: der Deckel bei 50 darf nicht zu frueh zuschlagen, und das
Modul darf keine neue globale Variable hinterlassen.

Aufruf: lua5.4 tests/phase_a_acceptance.lua <impl.lua>
This commit is contained in:
Markus Fritsche
2026-08-08 08:02:21 +02:00
parent 250f3d38f0
commit 08e0075d14
+113
View File
@@ -0,0 +1,113 @@
-- Abnahmetest fuer lmcp Phase A (nur beobachten, nie ablehnen).
--
-- Vom Vertrag geschrieben, NICHT von der Implementierung: der Autor dieses
-- Tests hat den zu pruefenden Code nicht gesehen. Genau daran sind die
-- letzten vier Runden gescheitert - der Implementierer hat seine eigenen
-- Hausaufgaben korrigiert, und der Test prueft dann verlaesslich das, was
-- der Code ohnehin tut.
--
-- VERTRAG
-- Eine einzelne, in sich geschlossene Lua-5.4-Datei stellt eine Tabelle M
-- bereit mit:
-- M.report(version, peer, ua) -- Produktionseinstieg
-- M.sink(version, peer, ua) -- ueberschreibbar, wird beim ERSTEN
-- -- Auftreten eines Tripels gerufen
-- * jedes verschiedene Tripel (version, peer, ua) genau EINMAL
-- * version nil oder "" -> nichts
-- * der Entprellungszustand ist ein privates Upvalue: KEINE globale
-- Variable, KEIN Parameter, den der Aufrufer mitgeben muss
-- * gedeckelt bei 50 verschiedenen Tripeln; danach nichts mehr
-- * M.report lehnt NIE etwas ab und liefert immer nil
--
-- Aufruf: lua5.4 phase_a_acceptance.lua <zu-pruefende-datei.lua>
local pfad = arg and arg[1]
if not pfad then
io.stderr:write("usage: lua5.4 phase_a_acceptance.lua <impl.lua>\n")
os.exit(2)
end
local vorher_global = {}
for k in pairs(_G) do vorher_global[k] = true end
local lade = assert(loadfile(pfad))
local M = lade()
if type(M) ~= "table" then
io.stderr:write("FAIL: die Datei liefert keine Tabelle zurueck\n")
os.exit(1)
end
local fehler = 0
local function pruefe(name, bedingung, zusatz)
if bedingung then
print((" [ok ] %s"):format(name))
else
fehler = fehler + 1
print((" [FAIL ] %s%s"):format(name, zusatz and ("" .. zusatz) or ""))
end
end
-- Faenger einhaengen
local gesehen = {}
M.sink = function(v, p, u)
gesehen[#gesehen + 1] = { v = v, p = p, u = u }
end
local function zuruecksetzen() gesehen = {} end
pruefe("M.report existiert und ist aufrufbar", type(M.report) == "function")
if type(M.report) ~= "function" then os.exit(1) end
-- 1. nil und Leerstring melden nichts
zuruecksetzen()
M.report(nil, "peerA", "uaA")
M.report("", "peerA", "uaA")
pruefe("nil und \"\" melden nichts", #gesehen == 0,
("es kamen %d Meldungen"):format(#gesehen))
-- 2. erstes Tripel meldet genau einmal
zuruecksetzen()
M.report("2025-06-18", "peerA", "uaA")
pruefe("erstes Tripel meldet einmal", #gesehen == 1)
-- 3. DER FALL, DER DREI RUNDEN LANG DURCHRUTSCHTE:
-- Entprellung am ECHTEN Einstieg, ohne dass der Aufrufer Zustand mitgibt.
zuruecksetzen()
for _ = 1, 5 do M.report("2025-06-18", "peerA", "uaA") end
pruefe("fuenf gleiche Tripel -> keine weitere Meldung", #gesehen == 0,
("es kamen %d Meldungen; Zustand ueberlebt den Aufruf nicht")
:format(#gesehen))
-- 4. gleiche Fassung, andere Gegenstelle -> meldet wieder
zuruecksetzen()
M.report("2025-06-18", "peerB", "uaA")
pruefe("gleiche Fassung, andere Gegenstelle -> Meldung", #gesehen == 1,
"Entprellung nur auf die Fassung wuerde andere Klienten maskieren")
-- 5. gleiche Fassung und Gegenstelle, anderer User-Agent -> meldet wieder
zuruecksetzen()
M.report("2025-06-18", "peerB", "uaZ")
pruefe("anderer User-Agent -> Meldung", #gesehen == 1)
-- 6. liefert immer nil, lehnt nie ab
local r1 = M.report("2026-07-28", "peerC", "uaC")
local r2 = M.report("2026-07-28", "peerC", "uaC")
pruefe("M.report liefert nil (lehnt nie ab)", r1 == nil and r2 == nil)
-- 7. Deckel bei 50
zuruecksetzen()
for i = 1, 80 do M.report("v" .. i, "peerD", "uaD") end
pruefe("Deckel greift bei 50", #gesehen <= 50,
("es kamen %d Meldungen"):format(#gesehen))
pruefe("Deckel wirft nicht zu frueh", #gesehen >= 40,
("nur %d Meldungen vor dem Deckel"):format(#gesehen))
-- 8. kein globaler Zustand
local neue = {}
for k in pairs(_G) do
if not vorher_global[k] then neue[#neue + 1] = k end
end
pruefe("keine neuen globalen Variablen", #neue == 0,
"hinzugekommen: " .. table.concat(neue, ", "))
print((" %d Pruefungen fehlgeschlagen"):format(fehler))
os.exit(fehler == 0 and 0 or 1)