From 04042cf540a258c2dfe5a185e712bd1e89c9758c Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Date: Wed, 11 Nov 2026 12:37:32 +0000 Subject: [PATCH] test(kuna): scope short deadlines to hanging decompile work Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> --- .../kuna/kunaIndependentDecompileMcp.test.ts | 29 +++++++++- .../providers/kuna/kunaProvider.test.ts | 37 +++++++----- .../kuna/kunaSingleFunctionRuns.test.ts | 57 +++++++++++++++---- 3 files changed, 97 insertions(+), 26 deletions(-) diff --git a/tests/boundary/providers/kuna/kunaIndependentDecompileMcp.test.ts b/tests/boundary/providers/kuna/kunaIndependentDecompileMcp.test.ts index e895c6b81d..ecb5e614e1 100644 --- a/tests/boundary/providers/kuna/kunaIndependentDecompileMcp.test.ts +++ b/tests/boundary/providers/kuna/kunaIndependentDecompileMcp.test.ts @@ -32,7 +32,7 @@ it("runs a cold independent request without starting a project export", async () { REA_KUNA_COMMAND: kuna, REA_KUNA_CACHE_DIR: join(root, "cache"), - REA_KUNA_TIMEOUT_MS: "300", + REA_KUNA_TIMEOUT_MS: "10000", }, { platform: process.platform }, ); @@ -67,6 +67,11 @@ it("runs a cold independent request without starting a project export", async () functions: FUNCTIONS, decompile: "hang", }); + await call("update_analysis_settings", { + changes: [ + { setting: "kuna.function_timeout_ms", action: "set", value: 300 }, + ], + }); const timeout = await harness.client.callTool({ name: "procedure_pseudo_code", arguments: { procedure: "main", analysis: "independent_fresh" }, @@ -84,6 +89,7 @@ it("runs a cold independent request without starting a project export", async () limit: { setting: "kuna.function_timeout_ms", effective_value: 300, + source: "runtime", update_mode: "runtime", pending_restart: false, minimum: 1, @@ -96,6 +102,9 @@ it("runs a cold independent request without starting a project export", async () ({ argv }) => argv[0] === "decompile-project", ), ).toHaveLength(0); + await call("update_analysis_settings", { + changes: [{ setting: "kuna.function_timeout_ms", action: "reset" }], + }); await setFakeKunaBehavior(kuna, { functions: FUNCTIONS }); await call("procedure_assembly", { procedure: "main" }); expect( @@ -231,7 +240,7 @@ it("retains the independent run slot until an unproven stop is resolved", async { REA_KUNA_COMMAND: kuna, REA_KUNA_CACHE_DIR: join(root, "cache"), - REA_KUNA_TIMEOUT_MS: "300", + REA_KUNA_TIMEOUT_MS: "10000", }, { platform: process.platform, @@ -266,6 +275,15 @@ it("retains the independent run slot until an unproven stop is resolved", async expect( (await call("procedure_assembly", { procedure: "broken" })).isError, ).not.toBe(true); + expect( + ( + await call("update_analysis_settings", { + changes: [ + { setting: "kuna.function_timeout_ms", action: "set", value: 300 }, + ], + }) + ).isError, + ).not.toBe(true); const first = await call("procedure_pseudo_code", { procedure: "broken", analysis: "independent_fresh", @@ -314,6 +332,13 @@ it("retains the independent run slot until an unproven stop is resolved", async expect((await call("close_binary", {})).isError).toBe(true); unproven.release(); expect((await call("close_binary", {})).isError).not.toBe(true); + expect( + ( + await call("update_analysis_settings", { + changes: [{ setting: "kuna.function_timeout_ms", action: "reset" }], + }) + ).isError, + ).not.toBe(true); await setFakeKunaBehavior(kuna, { functions: FUNCTIONS }); expect( (await call("open_binary", { path: target, provider_id: "kuna" })) diff --git a/tests/boundary/providers/kuna/kunaProvider.test.ts b/tests/boundary/providers/kuna/kunaProvider.test.ts index cdddd2e2c9..52d82ee3ce 100644 --- a/tests/boundary/providers/kuna/kunaProvider.test.ts +++ b/tests/boundary/providers/kuna/kunaProvider.test.ts @@ -1032,25 +1032,34 @@ it("keeps reporting a failed close and succeeds once a retry proves the stop", a const { open } = await session( "rea-kuna-close-retry-", { project: "hang", decompile: "hang" }, - { REA_KUNA_TIMEOUT_MS: "300" }, + { REA_KUNA_TIMEOUT_MS: "10000" }, unproven.launcher, ); const client = open(); - expect( - await client.execute("procedure_pseudo_code", { procedure: "main" }), - ).toMatchObject({ ok: false, error: { cleanupIncomplete: true } }); - const first = await client.close(); - expect(first).toMatchObject({ - ok: false, - error: { diagnostics: { reason: "decompile stop unproven" } }, - }); - // A retry that still cannot prove the stop keeps failing. - expect((await client.close()).ok).toBe(false); + try { + expect((await client.execute("list_procedures", {})).ok).toBe(true); + expect( + await runWithAnalysisOperationSnapshot( + { revision: 1, overrides: { "kuna.function_timeout_ms": 300 } }, + () => client.execute("procedure_pseudo_code", { procedure: "main" }), + ), + ).toMatchObject({ ok: false, error: { cleanupIncomplete: true } }); + const first = await client.close(); + expect(first).toMatchObject({ + ok: false, + error: { diagnostics: { reason: "decompile stop unproven" } }, + }); + // A retry that still cannot prove the stop keeps failing. + expect((await client.close()).ok).toBe(false); - unproven.release(); - expect(await client.close()).toEqual({ ok: true, value: null }); - expect(await client.close()).toEqual({ ok: true, value: null }); + unproven.release(); + expect(await client.close()).toEqual({ ok: true, value: null }); + expect(await client.close()).toEqual({ ok: true, value: null }); + } finally { + unproven.release(); + expect((await client.close()).ok).toBe(true); + } }); it("reports an abandoned export's unproven cleanup from close", async () => { diff --git a/tests/boundary/providers/kuna/kunaSingleFunctionRuns.test.ts b/tests/boundary/providers/kuna/kunaSingleFunctionRuns.test.ts index 0e807b822c..7c3e777939 100644 --- a/tests/boundary/providers/kuna/kunaSingleFunctionRuns.test.ts +++ b/tests/boundary/providers/kuna/kunaSingleFunctionRuns.test.ts @@ -1,4 +1,5 @@ import { expect, it as test, vi } from "vitest"; +import { runWithAnalysisOperationSnapshot } from "../../../../src/domain/analysisOperationSnapshot.js"; import { awaitAcknowledgement } from "../../../fixtures/lifecycleAcknowledgement.js"; import { KunaRunSlots } from "../../../../src/kuna/KunaRunSlots.js"; @@ -183,29 +184,57 @@ it("keeps the slot of a run whose stop is unproven until close proves it", async const { kuna, open } = await session( "rea-kuna-single-retained-slot-", { project: "hang", decompile: "hang" }, - { REA_KUNA_TIMEOUT_MS: "300" }, + { REA_KUNA_TIMEOUT_MS: "10000" }, unproven.launcher, 1, ); const client = open(); const waiter = new AbortController(); + let queued: ReturnType | undefined; + let restoreAcquire: (() => void) | undefined; try { + expect((await client.execute("list_procedures", {})).ok).toBe(true); expect( - await client.execute("procedure_pseudo_code", { procedure: "main" }), + await runWithAnalysisOperationSnapshot( + { revision: 1, overrides: { "kuna.function_timeout_ms": 300 } }, + () => client.execute("procedure_pseudo_code", { procedure: "main" }), + ), ).toMatchObject({ ok: false, error: { cleanupIncomplete: true } }); // The timed-out tree may still run, so the next run must not start. - const queued = client.execute( + const admission = deferred(); + const acquire = KunaRunSlots.prototype.acquire; + let granted = false; + const requests = vi + .spyOn(KunaRunSlots.prototype, "acquire") + .mockImplementation(function (this: KunaRunSlots, signal) { + const pending = acquire.call(this, signal); + void pending.then(() => { + granted = true; + }); + admission.resolve(); + return pending; + }); + restoreAcquire = () => requests.mockRestore(); + queued = client.execute( "procedure_pseudo_code", { procedure: "parse" }, { signal: waiter.signal }, ); - await new Promise((resolve) => setTimeout(resolve, 300)); + await awaitAcknowledgement( + admission.promise, + queued, + "retained single-function slot", + ); + await new Promise((resolve) => setImmediate(resolve)); + expect(granted).toBe(false); expect(await decompilesOf(kuna)).toHaveLength(1); waiter.abort(); expect(await queued).toMatchObject({ ok: false, error: { _tag: "AnalysisCancelledError" }, }); + restoreAcquire(); + restoreAcquire = undefined; expect((await client.close()).ok).toBe(false); unproven.release(); expect(await client.close()).toEqual({ ok: true, value: null }); @@ -225,6 +254,8 @@ it("keeps the slot of a run whose stop is unproven until close proves it", async waiter.abort(); unproven.release(); await client.close(); + await Promise.allSettled([queued]); + restoreAcquire?.(); } }); @@ -343,13 +374,17 @@ it("does not keep a failed run, so a repeat runs again", async () => { const { kuna, open } = await session( "rea-kuna-single-failed-not-kept-", { project: "hang", decompile: "hang" }, - { REA_KUNA_TIMEOUT_MS: "200" }, + { REA_KUNA_TIMEOUT_MS: "10000" }, ); const client = open(); try { + expect((await client.execute("list_procedures", {})).ok).toBe(true); for (let repeat = 0; repeat < 2; repeat += 1) expect( - await client.execute("procedure_pseudo_code", { procedure: "main" }), + await runWithAnalysisOperationSnapshot( + { revision: 1, overrides: { "kuna.function_timeout_ms": 200 } }, + () => client.execute("procedure_pseudo_code", { procedure: "main" }), + ), ).toMatchObject({ ok: false }); expect(await decompilesOf(kuna)).toHaveLength(2); } finally { @@ -390,13 +425,15 @@ it("names the setting that owns a single-function deadline", async () => { const { open } = await session( "rea-kuna-function-deadline-", { project: "hang", decompile: "hang" }, - { REA_KUNA_TIMEOUT_MS: "300" }, + { REA_KUNA_TIMEOUT_MS: "10000" }, ); const client = open(); try { - const result = await client.execute("procedure_pseudo_code", { - procedure: "main", - }); + expect((await client.execute("list_procedures", {})).ok).toBe(true); + const result = await runWithAnalysisOperationSnapshot( + { revision: 1, overrides: { "kuna.function_timeout_ms": 300 } }, + () => client.execute("procedure_pseudo_code", { procedure: "main" }), + ); if (result.ok) throw new Error("the hanging run answered"); expect(projectAnalysisError(result.error)).toMatchObject({ code: "provider_timeout",