From ff57b75e226028767b86aa3d4271c2c2f56342db Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 11 Oct 2026 17:23:50 +0800 Subject: [PATCH] test: bound CLI validation subprocess concurrency Run the 22 malformed process scenarios in pairs instead of starting 22 compiled CLI runtimes simultaneously. Retain every CLI/MCP error assertion and the no-launch check, and document nested subprocess limits. --- docs/testing.md | 4 ++ .../tools/localToolContracts.test.ts | 41 ++++++++++--------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index 2d6369d89..4d8f7793b 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -970,6 +970,10 @@ still owns its resources. Adapter, non-MCP boundary, acceptance, and process-global projects retain per-file isolation. See `vitest.config.ts` for current settings. +Vitest worker limits do not bound subprocesses launched within a test. Bound +those batches separately so repeated CLI validation cannot oversubscribe the +runner. Preserve simultaneous launches where concurrency is itself under test. + Build/documentation writers hold checkout-local output locks. Tests do not hold a broad command lock; `check:pr` finishes tests before document validation. diff --git a/tests/acceptance/tools/localToolContracts.test.ts b/tests/acceptance/tools/localToolContracts.test.ts index 83634a43c..c82a72349 100644 --- a/tests/acceptance/tools/localToolContracts.test.ts +++ b/tests/acceptance/tools/localToolContracts.test.ts @@ -161,26 +161,27 @@ captureTest( }), ), ]; - // Each CLI case is an independent process; running them concurrently keeps - // the suite well inside its deadline on slower CI runners. - await Promise.all( - invalid.map(async (options, index) => { - const request = { ...scenario, ...options }; - expect( - (await call("capture_process_scenario", request)).isError, - JSON.stringify(options), - ).toBe(true); - const path = join(root, `scenario-${String(index)}.json`); - await writeFile(path, JSON.stringify(request)); - await expect( - execute(process.execPath, [cli, "capture-process", path, "--json"]), - JSON.stringify(options), - ).rejects.toMatchObject({ - code: 1, - stdout: expect.stringContaining("invalid_request"), - }); - }), - ); + // Each CLI case initializes its own runtime. Bound subprocess fanout too, + // so one test cannot oversubscribe the host beyond the Vitest workers. + for (let start = 0; start < invalid.length; start += 2) + await Promise.all( + invalid.slice(start, start + 2).map(async (options, offset) => { + const request = { ...scenario, ...options }; + expect( + (await call("capture_process_scenario", request)).isError, + JSON.stringify(options), + ).toBe(true); + const path = join(root, `scenario-${String(start + offset)}.json`); + await writeFile(path, JSON.stringify(request)); + await expect( + execute(process.execPath, [cli, "capture-process", path, "--json"]), + JSON.stringify(options), + ).rejects.toMatchObject({ + code: 1, + stdout: expect.stringContaining("invalid_request"), + }); + }), + ); await expect(access(marker)).rejects.toMatchObject({ code: "ENOENT" }); const missing = join(root, "missing-executable"); const failed = await call("capture_process_scenario", {