From eb285055acffc42dfbab17f1498647a0afe07da2 Mon Sep 17 00:00:00 2001 From: fengsilin Date: Thu, 18 Jun 2026 11:35:08 +0800 Subject: [PATCH] test: harden e2e process cleanup --- web/e2e/utils/process.js | 65 ++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/web/e2e/utils/process.js b/web/e2e/utils/process.js index 93e0c26..1366d91 100644 --- a/web/e2e/utils/process.js +++ b/web/e2e/utils/process.js @@ -21,14 +21,61 @@ export function startProcess(command, args, options = {}) { return { child, output }; } -export async function stopProcess(child) { - if (!child || child.killed) return; - await new Promise((resolve) => { - child.once('exit', resolve); - child.kill(process.platform === 'win32' ? undefined : 'SIGTERM'); - setTimeout(() => { - if (!child.killed) child.kill('SIGKILL'); - resolve(); - }, 5000).unref(); +function hasExited(child) { + return child.exitCode !== null || child.signalCode !== null; +} + +function waitForExit(child, timeoutMs) { + if (!child || hasExited(child)) return Promise.resolve(true); + + return new Promise((resolve) => { + let settled = false; + let timer; + + const finish = (exited) => { + if (settled) return; + settled = true; + clearTimeout(timer); + child.off('exit', onExit); + child.off('close', onExit); + resolve(exited); + }; + + const onExit = () => finish(true); + + child.once('exit', onExit); + child.once('close', onExit); + timer = setTimeout(() => finish(false), timeoutMs); + timer.unref(); + }); +} + +function killWindowsProcessTree(pid) { + if (!pid) return Promise.resolve(); + + return new Promise((resolve) => { + const killer = spawn('taskkill', ['/PID', String(pid), '/T', '/F'], { + stdio: 'ignore', + }); + + killer.once('error', resolve); + killer.once('exit', resolve); }); } + +export async function stopProcess(child) { + if (!child || hasExited(child)) return; + + if (process.platform === 'win32') { + await killWindowsProcessTree(child.pid); + await waitForExit(child, 5000); + return; + } + + child.kill('SIGTERM'); + const exited = await waitForExit(child, 5000); + if (exited) return; + + child.kill('SIGKILL'); + await waitForExit(child, 5000); +}