feat: stdio server spawn-test (#2) #11
Reference in New Issue
Block a user
Delete Branch "feat/2-stdio-spawn-test"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
spawn_test(data, timeout)tobcc_core.py: spawns a stdio server for up to 3 s, captures stderr, classifies asok/exited/crashed/not_found/not_applicable.SpawnTester(QThread)inbcc.pyand a Test launch button inServerEditor(stdio only, visible when command resolves ok/warn).Design decisions
stdin=PIPE(never written): MCP servers block on JSON-RPC stdin, so still-running after timeout reliably means healthy.stdin=DEVNULLsends EOF and a well-behaved server exits 0, being misclassified asexited.shutil.which(augmented_path())beforePopen— no PATH ambiguity.start_new_session=True+os.killpgon POSIX: kills the whole process group sonpx/uvxgrandchildren (the actual server process) don'''t get orphaned.stdout=DEVNULL: a PIPE we don'''t drain deadlocks at ~64 KB.Local GUI test
Ran
python bcc.pylocally:python -c "import time; time.sleep(60)") shows green "started · still running after 3s".Test plan
python -m pytestlocally — 65 passedruff check . && ruff format --check .— cleanCloses #2
[Supervisor review — fix before merge] 1. stderr silently dropped
_on_spawn_doneonly appends captured stderr when the Details panel is already open, andrefresh_dependency()rewritesdiag_texton any field change, wiping the appended text. Result: on a crash with the panel closed, stderr is lost and the AC from issue #2 (stderr[Supervisor review — fix before merge] 2. Drain-cap deadlock misclassifies crashes
_drainstops reading once_STDERR_CAP(4 KB) is reached. A process that writes more than 4 KB of stderr then blocks on the full pipe buffer, never exits, and is misclassified as "ok" (still running) instead of "crashed".Fix: keep reading until EOF regardless of the cap; discard bytes beyond the cap (buffer the cap, drain the rest to
/dev/null). Add a test: process that writes ~64 KB to stderr then exits non-zero → outcome "crashed".[Supervisor review — follow-up ok] 3. Test timing flakiness
The crashed/exited tests use 0.3–0.5 s timeouts; a slow CI runner plus interpreter startup could flip an outcome (a Python subprocess that takes >300 ms to start up would time out and be classified "ok" instead of "crashed" or "exited").
Proposal: File as a follow-up issue and bump those timeouts to 1–2 s in a small patch. The "ok" (timeout) path can stay short since expiry means success.
[Supervisor review — follow-up ok] 4. Windows process tree
proc.kill()on Windows won't reach npx/uvx grandchildren (already noted in the PR as best-effort). When filed as a follow-up, reference issue #9 — same concern family as not blanket-killingclaude*processes when restarting Claude Desktop.Proposal: File as a follow-up issue tied to issue #9 scoping. No code change needed here.
Both fixes from items 1 and 2 are addressed in commit
231403c:Fix 1 — stderr persistence:
self._last_spawn: dict | None = NonetoServerEditor.__init__._full_diag_text()helper appends the stderr section whenever diag text is generated — sorefresh_dependency()and_toggle_diag()both include it without clobbering._on_spawn_donestores the result inself._last_spawnand auto-opens the Details panel on non-ok outcomes (same pattern as the existingauto_openfor missing commands).load_entry()clears_last_spawnwhen switching servers.Fix 2 — drain-cap deadlock:
_drainnow reads until EOF unconditionally; only the first_STDERR_CAPbytes are kept.test_spawn_test_large_stderr_does_not_deadlock: 64 KB stderr +exit(3)→ outcome \crashed, returncode 3,len(stderr) <= _STDERR_CAP.Items 3 and 4 filed as follow-up issues: #12 (test timeout flakiness) and #13 (Windows process-tree kill, cross-referenced with #9).
66 tests pass, ruff clean.