14 KiB
Testing
Für Endnutzer-Doku siehe die README und
FEATURES.md. Diese Seite ist für die Weiterentwicklung von
nub selbst: automatisierte Tests, manuelle Testrezepte pro Feature, und
was aktuell (noch) nicht abgedeckt ist.
Automatisierte Tests
go build ./...
go vet ./...
go test ./...
Deckt ab: SSE-Fragmentierung/Tool-Call-Akkumulation (Golden File), Agent-Loop
gegen einen Fake-Provider (Multi-Turn, Tool-Roundtrip, Stream-Fehler),
Datei-Tools inkl. Pfad-Escape via ../ und Symlink, bash-Timeout mit
Prozessgruppen-Kill, Architektur-Test für E-04 (kein Kern-Paket importiert
internal/ui), Session-Baum (Resume-Roundtrip, Rewind+Branch, Dateirechte,
Versions-Fehlerfall), Config-Layering-Präzedenz, Context Assembler
(walk_up-Reihenfolge, Budget-Truncation+Warnung, Repomap-Staleness),
Auto-Compaction (Trigger, Turn-Grenzen-Logik, Rewind auf einen Knoten vor
der Compaction), Token-Kalibrierung, todo-Tool, Skill-Discovery (Index ohne
Body, Präzedenz projekt-vor-global), MCP-Bridge (echter In-Process-Server-
Roundtrip über mcp.NewInMemoryTransports, Allowlist, Laufzeitfehler ohne
Panic), Profile-Filterung, TUI (Slash-Command-Dispatch ohne Backend-Kontakt,
Steering- vs. Follow-up-Queue-Semantik, Tree-/Sessions-Modal-Auswahl inkl.
Branch/Wechsel/Löschen mit Rückfrage, Diff-/Fehler-Rendering, /help-
Formatierung, Skills-als-Slash-Commands, Zeilenumbruch, historische
Token-Wiederherstellung), Permission-Policy (Modus-Auflösung, deny_paths/
deny_bash überstimmen auto/ask), Agent-Loop-Permission-Integration
(deny/ask blockieren die Tool-Ausführung nachweislich, nil-Hook -> harter
Fehler), TUI-Rückfrage-Modal (blockiert bis zur Antwort, Kontextabbruch
löst sauber auf), Scaffold/nub init (legt an, überschreibt bestehende
Dateien nie, generierte Config parst und rundtripped korrekt, Git-Exclude
wird gesetzt), question-Tool (nil-Hook -> harter Fehler, leere Frage
abgelehnt, Antwort-Roundtrip inkl. Options, Ask-Fehler landet als
IsError), TUI-Rückfrage-Block für question (blockiert bis zur Antwort,
Kontextabbruch löst sauber auf, Eingabe während offener Frage wird als
Antwort statt als Chat-Nachricht/Slash-Command behandelt, kein Overlay).
Architektur-Nachweis M6 (TUI ist ein reiner Consumer, entfernbar):
TestArchitecture_NoUIImportBelowAgent (internal/agent/arch_test.go)
verbietet jedem Kern-Paket den Import von internal/ui/*, also auch
internal/ui/tui — automatisiert bei jedem Testlauf geprüft. Zusätzlich
manuell verifiziert: internal/ui/tui entfernt, go build ./internal/...
bleibt grün, cmd/nub baut nach Entfernen der TUI-Verzweigung ebenfalls,
-p verhält sich danach identisch.
Manuelle Testrezepte
Die Tests unten gehen von nub -p aus (die beschriebenen →/←-Zeilen auf
stderr sind Print-Modus-Ausgabe). Dieselben Szenarien lassen sich genauso in
der TUI (nub, ohne -p) durchspielen — dort erscheinen Tool-Aufrufe direkt
im Transkript statt auf stderr, und Ctrl+C/Rewind laufen über die im README
beschriebenen Tastenkombinationen/Kommandos.
Aus dem Konzept, Abschnitt 6:
Ein mehrstufiger Task ("lies X, ändere Y, führe die Tests aus") läuft gegen einen echten Endpoint durch.
Ctrl+Cbricht mitten in einembash-Aufruf sauber ab und tötet die Prozessgruppe.
Test 1 — Mehrstufiger Task
In einem kleinen Testrepo (z.B. mit ein paar .go-Dateien und go test
lauffähig):
Lies main.go, füge einen Kommentar über der main-Funktion hinzu der "entry point" sagt, und führe danach go build ./... aus um zu prüfen dass es noch kompiliert.
Erwartet: Das Modell ruft nacheinander read, edit und bash auf (sichtbar
an den →/←-Zeilen auf stderr), am Ende eine Textantwort mit dem Ergebnis.
Danach Ctrl+D, dann nub sessions — die eben gelaufene Session sollte mit
korrekter Node-Anzahl auftauchen. nub resume <id> und eine weitere Nachricht
schicken sollte den Kontext (z.B. den Dateinamen) noch kennen, ohne dass du
ihn wiederholst.
Test 1b — Rewind/Branch
Nach Test 1: die Node-ID kurz vor der letzten Antwort ermitteln (siehe README, Abschnitt „Sessions") und damit fortsetzen:
nub resume <session-id> --branch <node-id>
Mach stattdessen etwas anderes: ...
Erwartet: nub schreibt ab hier einen zweiten Ast. cat .nub/sessions/<id>.jsonl | wc -l zeigt, dass die alten Zeilen erhalten
bleiben (nur neue kommen dazu). Mit dem --branch-Trick lässt sich sowohl
der alte als auch der neue Ast wieder aufrufen.
Test 1c — Auto-Compaction
Eine echte Session lang genug laufen zu lassen, um 75% des Modell-Fensters
zu füllen, dauert. Stattdessen max_context künstlich klein setzen, um den
Trigger nach wenigen Nachrichten auszulösen — in .nub/config.toml:
version = 1
[[endpoint]]
name = "openai"
base_url = "https://api.openai.com/v1"
api_key = "env:OPENAI_API_KEY"
[endpoint.caps]
max_context = 500
Dann nub starten und 4-5 kurze Nachrichten hintereinander schicken.
Erwartet: Nach ein paar Nachrichten erscheint auf stderr
compacted N nodes into a M-token summary. nub läuft danach normal
weiter (das Modell kennt den zusammengefassten Kontext noch grob).
Dann Ctrl+D, Node-ID der allerersten Nachricht ermitteln (siehe
README, „Sessions") und:
nub resume <session-id> --branch <node-id>
Erwartet: Das funktioniert weiterhin, obwohl dieser Knoten längst
wegkompaktiert wurde — die Original-Nodes bleiben im Baum erhalten.
.nub/config.toml danach wieder auf einen realistischen max_context
zurücksetzen (oder den Override entfernen).
Test 2 — Ctrl+C mitten in bash
Führe "sleep 30" aus.
Während → bash {...} läuft: Ctrl+C drücken. Erwartet:
nubbeendet sich zügig (nicht erst nach 30s)- stderr zeigt
aborted, Exit-Code ist3 - Kein Zombie-Prozess bleibt übrig — prüfen mit
ps aux | grep sleepdirekt danach, sollte leer sein
Test 3 — Mehrdeutiger edit-Treffer
Ersetze in main.go den Text "return" durch "return " (falls "return" mehrfach vorkommt)
Erwartet: Falls return mehrfach in der Datei steht, meldet das edit-Tool
einen Fehler mit Trefferzahl statt zu raten — sichtbar als
← edit [error] auf stderr, das Modell sollte daraufhin entweder
replace_all nutzen oder den old_string präzisieren.
Test 4 — Unbekanntes Tool / Fehlerpfad
Kein manueller Test nötig, ist per Fake-Provider automatisiert abgedeckt
(go test ./...).
Test 5 — Kaputter MCP-Server verhindert den Start nicht
version = 1
[[mcp]]
name = "broken"
command = "this-binary-does-not-exist"
nub mcp
Erwartet: warning: mcp server "broken": ... auf stderr, Exit-Code 0, kein
Absturz. Danach echtes nub starten (mit derselben Config) — muss trotz des
kaputten Servers normal hochkommen.
Test 6 — Skill wird erst bei Bedarf geladen
Ein Skill wie im README (Abschnitt „Skills") anlegen, dann im laufenden
nub fragen: Welche Skills hast du verfügbar, und was steht in "refactoring"? — erwartet: Das Modell nennt den Skill aus dem Index, ruft
danach sichtbar → read_skill {"name":"refactoring"} auf, um den Body zu
laden — nicht schon beim Start.
Test 7 — Steering vs. Follow-up, Rewind über die TUI
Nur in der TUI (nub, ohne -p):
- Einen Task starten, der mehrere Tool-Aufrufe braucht (wie Test 1).
Während
running: ...in der Statuszeile steht, eine neue Nachricht eingeben und Enter drücken (Steering). Erwartet: Sie wird eingeschleust, sobald der laufende Tool-Aufruf fertig ist, ohne dass du warten musst. - Denselben Versuch mit Alt+Enter. Erwartet: Statuszeile zeigt
queued: 1, die Nachricht erscheint im Transkript mit⏳-Präfix und wird erst gesendet, wenn der Turn ganz fertig ist (keintool_usemehr offen). /tree(oder/branch) öffnen — Liste aller Nodes erscheint, aktueller Head markiert. Mit ↑/↓ einen früheren Knoten wählen, Enter drücken. Erwartet: Head springt zurück, eine neue Nachricht hängt sich dort an (zweiter Ast, wie in Test 1b, nur ohne CLI-Flag)./exportausführen, geschriebene Datei öffnen — Transkript sollte vollständig als Markdown lesbar sein, inklusive Diffs./helpausführen — jedes Kommando sollte in einer eigenen Zeile mit kurzer Beschreibung erscheinen, nicht als ein langer Fließtext.
Test 8 — Prompt-Cache-Verifikation (M7)
Nur mit echtem OpenAI-Endpoint sinnvoll (lokale Server cachen meist nicht).
OpenAIs automatisches Prompt-Caching greift erst ab >1024 Tokens stabilem
Prefix — ein AGENTS.md/REPOMAP.md im Kontext hilft, diese Schwelle
zuverlässig zu überschreiten.
- In einem Repo mit ein paar hundert Zeilen Kontext (
nub mapvorher laufen lassen, damitREPOMAP.mdexistiert)nub -pstarten. - Zwei, drei kurze Nachrichten nacheinander schicken (z.B. "Was macht go.mod?", dann "Und main.go?").
- Auf stderr nach jedem Turn erscheint jetzt
usage: input=X output=Y cache_read=Z.
Erwartet: Beim ersten Turn ist cache_read meist 0 (nichts zum
Cachen vorhanden). Ab dem zweiten Turn sollte cache_read deutlich
über 0 liegen und nah an input herankommen, weil System-Prompt + bisherige
Historie als stabiler Prefix erneut gesendet werden. In der TUI ist dasselbe
in der Statuszeile sichtbar: tokens: N (cached: M).
Falls cache_read dauerhaft bei 0 bleibt, obwohl der Kontext >1024 Tokens
hat: prüfen, ob Caps.UsageInStream für den aktiven Endpoint tatsächlich
true ist (nub context bzw. die Modell-Registry in
internal/llm/registry) — ohne stream_options.include_usage liefert
OpenAI gar keine Usage-Daten, dann bleibt der Wert immer 0, unabhängig
davon ob gecacht wurde.
Test 9 — Permissions: ask, deny, deny_paths
In .nub/config.toml:
[permissions]
edit = "ask"
bash = "deny"
deny_paths = ["**/secrets.txt"]
- TUI, eine Nachricht schicken, die
editauf einer normalen Datei auslöst. Erwartet: ein umrandeter⚠ Rückfrage: edit-Block erscheint im Transkript (kein Vollbild — Nachrichtenliste, Eingabefeld und Statuszeile bleiben sichtbar), Statuszeile zeigt zusätzlich⚠ permission required. Mitn/Esc ablehnen — Modell bekommt einen Fehler-tool_resultund macht sinnvoll weiter (fragt nach, versucht etwas anderes). Erneut versuchen, mitybestätigen — Edit läuft normal. - Eine Nachricht schicken, die
bashauslösen würde. Erwartet: sofortiger Fehlerpermission denied: bash is not allowed by policy, kein Modal (deny fragt nie). - Eine Datei
secrets.txtanlegen, Modell bitten sie zu lesen/editieren. Erwartet: Fehler durchdeny_paths, unabhängig davon dassread/editsonst aufauto/askstehen — Deny-Pfade überstimmen immer. - Dasselbe im Print-Modus (
nub -p) mitedit = "ask": Erwartet sofortiger Fehlerrequires confirmation ('ask'), which this mode does not supportstatt eines hängenden Prozesses —askist dort laut Konzept (E-11) ein harter Fehler, keine Rückfrage über stdin.
Test 10 — nub init / /init
In einem leeren Testverzeichnis (kein .nub/, kein AGENTS.md):
nub init
Erwartet: .nub/config.toml (mit [permissions]-Beispiel) und AGENTS.md
werden angelegt, beide Pfade auf stdout gemeldet. .git/info/exclude
enthält danach .nub/ (git-Repo vorausgesetzt). Erneut nub init
ausführen — erwartet: beide Dateien werden als skipped (already exists)
gemeldet, Inhalt bleibt unverändert (vorher manuell etwas in AGENTS.md
ändern und danach verifizieren, dass es erhalten bleibt).
Dasselbe in der TUI mit /init — meldet dieselben zwei Pfade als
Command-Output im Transkript statt auf stdout.
Test 11 — question-Tool
- TUI: Modell bitten, vor einer mehrdeutigen Aktion nachzufragen (z.B.
"frag mich per question-Tool, welches Ausgabeformat ich will, mit den
Optionen json und yaml"). Erwartet: ein umrandeter
❓ Rückfrage:-Block erscheint im Transkript (kein Vollbild), Statuszeile zeigt❓ question pending, Eingabefeld-Placeholder wechselt auf den Antwort-Hinweis. Text eintippen und Enter drücken — landet nicht als neue Chat-Nachricht oder Slash-Kommando, sondern als Antwort des Tools; Transkript zeigt danachFrage: … / Antwort: …, Placeholder wechselt zurück, Modell macht mit der Antwort sinnvoll weiter. - Print-Modus (
nub -p): Modell dasselbe fragen lassen. Erwartet sofortiger Fehler ("interactive questions are not supported in this mode…") statt eines hängenden Prozesses — analog zuask-Permissions ist auch dasquestion-Tool im Print-Modus bewusst kein Rückfrage- Mechanismus über stdin, da Print headless laufen soll (E-11). - Skill mit einer Anweisung testen, die das Modell explizit zum
question-Tool greifen lässt (z.B. "frage vor dem Löschen von Dateien immer erst per question-Tool nach") — Verhalten identisch zu 1., da das Tool unabhängig davon nutzbar ist, ob Modell oder Skill-Anweisung den Aufruf veranlasst.
Bekannte Grenzen (Entwicklungsstand)
- Bilder werden nicht gemappt, auch wenn der Endpoint sie unterstützt
- Permissions/
deny_paths/deny_bashgelten nur für die sechs eingebauten Tools (read/glob/grep/write/edit/bash) — MCP- Tools,todoundread_skilllaufen immer alsautound lassen sich aktuell nicht einzeln einschränken - Compaction nutzt immer dasselbe Modell für die Zusammenfassung — keine Möglichkeit, dafür ein günstigeres Modell zu konfigurieren
- Token-Kalibrierung ist rein In-Process — kein persistenter Zustand über Sessions hinweg
- MCP: kein Reconnect zur Laufzeit (E-06 — würde die Tool-Liste ändern und den Prompt-Cache brechen), kein OAuth-Login-Flow getestet, MCP-Prompts/ -Resources werden nicht angebunden, nur Tools
- Skills: kein
nub skill inito.ä., Skills müssen von Hand angelegt werden - TUI: kein Model-Auswahl-Picker (
/model <name>setzt nur direkt, kein Modal mit Liste),/treezeigt Nodes flach sortiert nach Erstellzeit statt als echten Baum mit Einrückung,bash-Streaming-Output läuft nur in die Statuszeile statt live ins Transkript (erst das fertige Ergebnis landet dort), Bildschirm-Resize während eines Modals kann die Breite nicht neu berechnen bis zum nächsten Tastendruck