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:
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();