test: Prozessgrenzen-Tests für talk wrap an echter CLI + Daemon
talk läuft als echter Prozess gegen talkd mit Fake-Provider und sink null (Harness aus Task 04, unverändert erweitert): Erfolg mit Dauer (sleep-Ansage), Exit-Code-Durchreichung (sh -c 'exit 2' → 2 + Fehler-Ansage wortgenau), --bar-Shielding-Regression (true --bar → Exit 0 + Erfolg-Ansage, kein talk-Flag-Fehler), Label-Override per TALK_WRAP_LABEL, non-blocking (wrap kehrt vor Ansage-Ende zurück, Befehls-stdout bleibt frei von talk-Zeilen), --wait und Degraded Mode ohne Daemon (Befehls-Code bleibt).
This commit is contained in:
1 parent
51cd5a686c
commit
6877d64042
1 file changed
+168
@@ -0,0 +1,168 @@
|
|||||||
|
// wrap-Prozessgrenzen-Tests (Task 07): talk läuft als echter Prozess,
|
||||||
|
// talkd als echter Daemon, Fake-Provider + sink null wie im Harness —
|
||||||
|
// beobachtet wird nur extern: Exit-Codes, Befehls-stdout, Ansage-Text am
|
||||||
|
// Fake. Die gewrappten Befehle sind echte Binaries (sleep/sh/true), die
|
||||||
|
// Dauern bewusst klein.
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestWrapErfolgMitDauer (Abnahmekriterium): `talk wrap -- sleep 1`
|
||||||
|
// annonciert Erfolg inklusive Dauer und exitet 0.
|
||||||
|
func TestWrapErfolgMitDauer(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
fake := newFakeProvider(t, 0)
|
||||||
|
d := startDaemon(t, root, fake.srv.URL, nil)
|
||||||
|
env := daemonEnv(root, d.port, fake.srv.URL, nil)
|
||||||
|
|
||||||
|
res := runTalk(t, env, "wrap", "--", "sleep", "1")
|
||||||
|
if res.code != 0 {
|
||||||
|
t.Fatalf("talk wrap -- sleep 1: Exit %d, stderr: %s", res.code, res.stderr)
|
||||||
|
}
|
||||||
|
hits := fake.waitCompleted(t, 1)
|
||||||
|
// sleep 1 misst ~1 s (unter Last gern mehr) — Zahl offen, Form nicht.
|
||||||
|
if !regexp.MustCompile(`^sleep erfolgreich\. Dauer: \d+ Sekunden?\.$`).MatchString(hits[0].Text) {
|
||||||
|
t.Errorf("Ansage = %q, will „sleep erfolgreich. Dauer: N Sekunde(n).“", hits[0].Text)
|
||||||
|
}
|
||||||
|
waitStatusIdle(t, d, 3*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWrapFehlschlagCodeDurchgereicht (Abnahmekriterium): scheitert der
|
||||||
|
// Befehl mit Code 2, exitet wrap mit 2 und sagt den Misserfolg mit Code —
|
||||||
|
// ohne Dauer, wortgenau nach Brief.
|
||||||
|
func TestWrapFehlschlagCodeDurchgereicht(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
fake := newFakeProvider(t, 0)
|
||||||
|
d := startDaemon(t, root, fake.srv.URL, nil)
|
||||||
|
env := daemonEnv(root, d.port, fake.srv.URL, nil)
|
||||||
|
|
||||||
|
res := runTalk(t, env, "wrap", "--", "sh", "-c", "exit 2")
|
||||||
|
if res.code != 2 {
|
||||||
|
t.Fatalf("talk wrap -- sh -c 'exit 2': Exit %d, want 2 (Durchreichung)", res.code)
|
||||||
|
}
|
||||||
|
hits := fake.waitCompleted(t, 1)
|
||||||
|
if hits[0].Text != "sh fehlgeschlagen, Code 2." {
|
||||||
|
t.Errorf("Ansage = %q, want %q", hits[0].Text, "sh fehlgeschlagen, Code 2.")
|
||||||
|
}
|
||||||
|
waitStatusIdle(t, d, 3*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWrapFlagShielding (Regression, Task-07-Kern): `talk wrap -- true
|
||||||
|
// --bar` reicht --bar an den Befehl weiter — talk darf --bar NICHT als
|
||||||
|
// eigenes Flag parsen (wäre Exit 2 mit Flag-Fehler). GNU true ignoriert
|
||||||
|
// Operanden, exitet also 0, und die Erfolg-Ansage läuft.
|
||||||
|
func TestWrapFlagShielding(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
fake := newFakeProvider(t, 0)
|
||||||
|
d := startDaemon(t, root, fake.srv.URL, nil)
|
||||||
|
env := daemonEnv(root, d.port, fake.srv.URL, nil)
|
||||||
|
|
||||||
|
res := runTalk(t, env, "wrap", "--", "true", "--bar")
|
||||||
|
if res.code != 0 {
|
||||||
|
t.Fatalf("talk wrap -- true --bar: Exit %d, want 0 (--bar gehört dem Befehl) — stderr: %s", res.code, res.stderr)
|
||||||
|
}
|
||||||
|
if strings.Contains(res.stderr, "flag provided but not defined") {
|
||||||
|
t.Errorf("talk hat --bar als eigenes Flag geparsed — Shielding kaputt: %s", res.stderr)
|
||||||
|
}
|
||||||
|
hits := fake.waitCompleted(t, 1)
|
||||||
|
if !strings.HasPrefix(hits[0].Text, "true erfolgreich.") {
|
||||||
|
t.Errorf("Ansage = %q, will Erfolg für true", hits[0].Text)
|
||||||
|
}
|
||||||
|
waitStatusIdle(t, d, 3*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWrapLabelOverride: TALK_WRAP_LABEL ersetzt den Basisnamen in der
|
||||||
|
// Ansage (Fleet-Muster: Env statt Config-Datei).
|
||||||
|
func TestWrapLabelOverride(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
fake := newFakeProvider(t, 0)
|
||||||
|
d := startDaemon(t, root, fake.srv.URL, nil)
|
||||||
|
env := daemonEnv(root, d.port, fake.srv.URL, map[string]string{"TALK_WRAP_LABEL": "Deployment"})
|
||||||
|
|
||||||
|
res := runTalk(t, env, "wrap", "--", "false")
|
||||||
|
if res.code != 1 {
|
||||||
|
t.Fatalf("talk wrap -- false: Exit %d, want 1", res.code)
|
||||||
|
}
|
||||||
|
hits := fake.waitCompleted(t, 1)
|
||||||
|
if hits[0].Text != "Deployment fehlgeschlagen, Code 1." {
|
||||||
|
t.Errorf("Ansage = %q, want %q", hits[0].Text, "Deployment fehlgeschlagen, Code 1.")
|
||||||
|
}
|
||||||
|
waitStatusIdle(t, d, 3*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWrapNonBlocking: wrap kehrt nach dem enqueue zurück, bevor die
|
||||||
|
// Ansage zu Ende gespielt ist (Ansage dauert 600 ms am Fake) — stdout
|
||||||
|
// gehört dabei allein dem Befehl, die Item-ID geht auf stderr.
|
||||||
|
func TestWrapNonBlocking(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
fake := newFakeProvider(t, 600*time.Millisecond)
|
||||||
|
d := startDaemon(t, root, fake.srv.URL, nil)
|
||||||
|
env := daemonEnv(root, d.port, fake.srv.URL, nil)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
res := runTalk(t, env, "wrap", "--", "sh", "-c", "echo bauausgabe")
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
if res.code != 0 {
|
||||||
|
t.Fatalf("talk wrap: Exit %d, stderr: %s", res.code, res.stderr)
|
||||||
|
}
|
||||||
|
if elapsed >= 500*time.Millisecond {
|
||||||
|
t.Fatalf("talk wrap war blockierend (%s; Ansage dauert 600 ms) — Default muss non-blocking sein", elapsed)
|
||||||
|
}
|
||||||
|
if res.stdout != "bauausgabe\n" {
|
||||||
|
t.Errorf("stdout = %q, will ausschließlich die Befehlsausgabe (Item-ID gehört auf stderr)", res.stdout)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(res.stderr) == "" {
|
||||||
|
t.Errorf("stderr = %q, will mindestens die Item-ID der Ansage", res.stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die Ansage läuft im Daemon trotzdem zu Ende.
|
||||||
|
fake.waitCompleted(t, 1)
|
||||||
|
waitStatusIdle(t, d, 3*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWrapWait: `talk wrap --wait -- …` kehrt erst nach Ende der Ansage
|
||||||
|
// zurück (geerbtes say-Flag, hier: 400 ms Synthese).
|
||||||
|
func TestWrapWait(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
fake := newFakeProvider(t, 400*time.Millisecond)
|
||||||
|
d := startDaemon(t, root, fake.srv.URL, nil)
|
||||||
|
env := daemonEnv(root, d.port, fake.srv.URL, nil)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
res := runTalk(t, env, "wrap", "--wait", "--", "true")
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
if res.code != 0 {
|
||||||
|
t.Fatalf("talk wrap --wait: Exit %d, stderr: %s", res.code, res.stderr)
|
||||||
|
}
|
||||||
|
if elapsed < 350*time.Millisecond {
|
||||||
|
t.Fatalf("talk wrap --wait kam zu früh zurück (%s; Ansage dauert 400 ms)", elapsed)
|
||||||
|
}
|
||||||
|
waitStatusIdle(t, d, 3*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWrapDegradedMode: Daemon tot → Ansage im Degraded Mode (Lock-Datei,
|
||||||
|
// direkt gegen den Fake), und der Exit-Code des Befehls bleibt trotzdem.
|
||||||
|
func TestWrapDegradedMode(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
fake := newFakeProvider(t, 0)
|
||||||
|
env := daemonEnv(root, 1, fake.srv.URL, nil) // Port 1: Daemon garantiert tot
|
||||||
|
|
||||||
|
res := runTalk(t, env, "wrap", "--", "sh", "-c", "exit 3")
|
||||||
|
if res.code != 3 {
|
||||||
|
t.Fatalf("talk wrap (Degraded): Exit %d, want 3 (Befehls-Code gewinnt)", res.code)
|
||||||
|
}
|
||||||
|
hits := fake.waitCompleted(t, 1)
|
||||||
|
if hits[0].Text != "sh fehlgeschlagen, Code 3." {
|
||||||
|
t.Errorf("Ansage = %q, want %q", hits[0].Text, "sh fehlgeschlagen, Code 3.")
|
||||||
|
}
|
||||||
|
if !strings.Contains(res.stderr, "Degraded Mode") {
|
||||||
|
t.Errorf("stderr = %q, will Degraded-Hinweis (nichts bricht still)", res.stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user