From a854331c4cde635636cc2d98cb00efdcc7494a18 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 28 May 2026 14:40:52 +0200 Subject: [PATCH] fix(test): hard kill boundary prep timeouts --- ...e-extension-package-boundary-artifacts.mjs | 7 ++-- ...tension-package-boundary-artifacts.test.ts | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/scripts/prepare-extension-package-boundary-artifacts.mjs b/scripts/prepare-extension-package-boundary-artifacts.mjs index f9aeb0734d2..036e32a8ab5 100644 --- a/scripts/prepare-extension-package-boundary-artifacts.mjs +++ b/scripts/prepare-extension-package-boundary-artifacts.mjs @@ -195,10 +195,11 @@ function abortSiblingSteps(abortController) { } } -function runNodeStep(label, args, timeoutMs, params = {}) { +export function runNodeStep(label, args, timeoutMs, params = {}) { const abortController = params.abortController; + const spawnImpl = params.spawnImpl ?? spawn; return new Promise((resolvePromise, rejectPromise) => { - const child = spawn(process.execPath, args, { + const child = spawnImpl(process.execPath, args, { cwd: repoRoot, env: params.env ? { ...process.env, ...params.env } : process.env, signal: abortController?.signal, @@ -212,8 +213,8 @@ function runNodeStep(label, args, timeoutMs, params = {}) { if (settled) { return; } - child.kill("SIGTERM"); settled = true; + child.kill("SIGKILL"); stdoutWriter.flush(); stderrWriter.flush(); abortSiblingSteps(abortController); diff --git a/test/scripts/prepare-extension-package-boundary-artifacts.test.ts b/test/scripts/prepare-extension-package-boundary-artifacts.test.ts index d7ba87fa11d..324ee260265 100644 --- a/test/scripts/prepare-extension-package-boundary-artifacts.test.ts +++ b/test/scripts/prepare-extension-package-boundary-artifacts.test.ts @@ -1,3 +1,4 @@ +import { EventEmitter } from "node:events"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -6,12 +7,21 @@ import { createPrefixedOutputWriter, isArtifactSetFresh, parseMode, + runNodeStep, runNodeSteps, runNodeStepsInParallel, } from "../../scripts/prepare-extension-package-boundary-artifacts.mjs"; const tempRoots = new Set(); +function createMockPipe() { + const pipe = new EventEmitter() as EventEmitter & { + setEncoding: (encoding: string) => void; + }; + pipe.setEncoding = () => {}; + return pipe; +} + afterEach(() => { for (const rootDir of tempRoots) { fs.rmSync(rootDir, { force: true, recursive: true }); @@ -58,6 +68,33 @@ describe("prepare-extension-package-boundary-artifacts", () => { expect(Date.now() - startedAt).toBeLessThan(abortBudgetMs); }, 45_000); + it("hard-kills timed out prep steps", async () => { + const signals: Array = []; + const child = new EventEmitter() as EventEmitter & { + kill: (signal?: NodeJS.Signals | number) => boolean; + stderr: ReturnType; + stdout: ReturnType; + }; + child.stdout = createMockPipe(); + child.stderr = createMockPipe(); + child.kill = (signal) => { + signals.push(signal); + return true; + }; + + await expect( + runNodeStep("hung-prep", ["--eval", "setTimeout(() => {}, 60_000)"], 5, { + spawnImpl(command: string, args: string[]) { + expect(command).toBe(process.execPath); + expect(args).toEqual(["--eval", "setTimeout(() => {}, 60_000)"]); + return child; + }, + }), + ).rejects.toThrow("hung-prep timed out after 5ms"); + + expect(signals).toEqual(["SIGKILL"]); + }); + it("runs boundary prep steps serially for local checks", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-boundary-serial-")); tempRoots.add(rootDir);