Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 5069f8994ccf2532dd70e8b6ac1542e684b2bd76
parent 73018db4c2daf5dca526c63f4741a58326bd55c6
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 02:07:51 -0400

common: the run and mcp tests fail instead of hanging

mcp.test.ts listened for the CLI's exit only after its read loop, so a CLI
that refused at once (no `mcp` row) exited before the listener and the test
never ended; the exit promise is now made at spawn and ends the read loop.
The run-operation tests that start a job carry a 60 s timeout. Both found by
the bite checks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mcommon/bin/mcp.test.ts | 11++++++++---
Mcommon/bin/run-operation.test.ts | 13+++++++++----
2 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/common/bin/mcp.test.ts b/common/bin/mcp.test.ts @@ -22,10 +22,13 @@ const TSX = path.join(HERE, "..", "node_modules", ".bin", "tsx"); const DIR = mkdtempSync(path.join(os.tmpdir(), "archilyzer-mcp-")); after(() => rmSync(DIR, { recursive: true, force: true })); -test("archilyzer mcp answers initialize on stdout, with its argv passed through", async () => { +test("archilyzer mcp answers initialize on stdout, with its argv passed through", { timeout: 60_000 }, async () => { const child = spawn(TSX, [path.join(HERE, "archilyzer.ts"), "mcp", "--local", DIR], { stdio: ["pipe", "pipe", "pipe"], }); + // Listened for from the start: a CLI that refuses at once exits before any + // later listener could see it. + const exited = new Promise<number | null>((r) => child.on("exit", (c) => r(c))); let stdout = ""; let stderr = ""; child.stdout.on("data", (b) => (stdout += b)); @@ -39,11 +42,13 @@ test("archilyzer mcp answers initialize on stdout, with its argv passed through" })}\n`, ); const deadline = Date.now() + 30_000; - while (!stdout.includes("\n") && Date.now() < deadline) { + let ended = false; + void exited.then(() => (ended = true)); + while (!stdout.includes("\n") && !ended && Date.now() < deadline) { await new Promise((r) => setTimeout(r, 50)); } child.stdin.end(); - const code = await new Promise<number | null>((r) => child.on("exit", (c) => r(c))); + const code = await exited; const first = stdout.split("\n")[0]; const msg = JSON.parse(first) as { id: number; result?: { serverInfo?: { name?: string } } }; assert.equal(msg.id, 1); diff --git a/common/bin/run-operation.test.ts b/common/bin/run-operation.test.ts @@ -8,6 +8,11 @@ // set before anything imports it. Settings are re-read on every call, so each // test writes the file it needs. // +// Every test that could start a job carries a timeout. A lane whose gate is +// shut HOLDS rather than failing: with only unref'd poll timers left, node +// cancels the remaining tests (exit 1), and the timeout bounds the case where +// something else keeps the loop alive. +// // The operation that RUNS here is diarization over videos that have a // transcript and no audio: each classifies `missing-input` (with re-download // off), which is counted and never dispatched — no engine, no model, no @@ -105,7 +110,7 @@ test("sync, the scan, downloads and transcription are refused with a sentence, n assert.deepEqual(runs.sort(), ["attribution-diarized", "attribution-text", "diarization", "digest"]); }); -test("an unknown channel, a switched-off operation, a paused lane and a stray --lane are refused", async () => { +test("an unknown channel, a switched-off operation, a paused lane and a stray --lane are refused", { timeout: 60_000 }, async () => { settings(); await seed("known", ["v1"]); let c = capture(); @@ -130,7 +135,7 @@ test("an unknown channel, a switched-off operation, a paused lane and a stray -- assert.match(c.o.stderr, /--lane is the digest engine lane/); }); -test("the media guard refuses an unmounted channel before any job exists", async () => { +test("the media guard refuses an unmounted channel before any job exists", { timeout: 60_000 }, async () => { settings(); const slug = "moved"; const channelDir = path.join(getPaths().channelsDir, slug); @@ -150,7 +155,7 @@ test("the media guard refuses an unmounted channel before any job exists", async assert.deepEqual(jobRecords(), before, "no job record for a refused run"); }); -test("diarization runs through the editor's job body: record, log, summary, snapshot", async () => { +test("diarization runs through the editor's job body: record, log, summary, snapshot", { timeout: 60_000 }, async () => { settings(); await seed("chan", ["v1", "v2"]); const before = new Set(jobRecords()); @@ -173,7 +178,7 @@ test("diarization runs through the editor's job body: record, log, summary, snap ); }); -test("ids scope the run, and ids with no data dir are named", async () => { +test("ids scope the run, and ids with no data dir are named", { timeout: 60_000 }, async () => { settings(); await seed("scoped", ["v1", "v2", "v3"]); const { o, out } = capture();