| 1 | export const sleep = (ms) => new Promise((done) => setTimeout(done, ms)); |
| 2 | |
| 3 | export function processAlive(pid) { |
| 4 | if (!Number.isInteger(pid) || pid <= 0) throw new Error(`invalid process id: ${pid}`); |
| 5 | try { |
| 6 | process.kill(pid, 0); |
| 7 | return true; |
| 8 | } catch (error) { |
| 9 | if (error.code === "ESRCH") return false; |
| 10 | throw error; |
| 11 | } |
| 12 | } |
| 13 | |
| 14 | export async function waitForProcessesToExit(pids, timeout = 5_000) { |
| 15 | const deadline = Date.now() + timeout; |
| 16 | let remaining; |
| 17 | while ((remaining = pids.filter(processAlive)).length > 0) { |
| 18 | if (Date.now() >= deadline) throw new Error(`processes outlived normal app quit: ${remaining.join(", ")}`); |
| 19 | await sleep(100); |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | // ElectronApplication.close invokes app.quit and waits for the application to |
| 24 | // close. A timeout is a failed smoke, never evidence that forced cleanup worked. |
| 25 | export async function closeAndVerify(application, { shellPid, servicePid }, timeout = 15_000) { |
| 26 | let timer; |
| 27 | try { |
| 28 | await Promise.race([ |
| 29 | application.close(), |
| 30 | new Promise((_, reject) => { |
| 31 | timer = setTimeout(() => reject(new Error(`normal app quit did not complete within ${timeout / 1000}s`)), timeout); |
| 32 | }), |
| 33 | ]); |
| 34 | } finally { |
| 35 | clearTimeout(timer); |
| 36 | } |
| 37 | await waitForProcessesToExit([shellPid, servicePid]); |
| 38 | } |
| 39 |