| 1 | import assert from "node:assert/strict"; |
| 2 | import { fork } from "node:child_process"; |
| 3 | import { once } from "node:events"; |
| 4 | import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; |
| 5 | import { tmpdir } from "node:os"; |
| 6 | import { join } from "node:path"; |
| 7 | import { test } from "node:test"; |
| 8 | import { closeAndVerify, processAlive, waitForProcessesToExit } from "./smoke-lifecycle.mjs"; |
| 9 | |
| 10 | async function ownedProcess(t) { |
| 11 | const directory = mkdtempSync(join(tmpdir(), "reasonix-smoke-lifecycle-")); |
| 12 | const script = join(directory, "child.cjs"); |
| 13 | writeFileSync(script, 'process.on("message", () => process.exit(0)); process.send("ready");\n'); |
| 14 | const child = fork(script, [], { stdio: ["ignore", "ignore", "ignore", "ipc"] }); |
| 15 | t.after(async () => { |
| 16 | if (processAlive(child.pid)) { |
| 17 | const exited = once(child, "exit"); |
| 18 | child.kill("SIGKILL"); |
| 19 | await exited; |
| 20 | } |
| 21 | rmSync(directory, { recursive: true, force: true }); |
| 22 | }); |
| 23 | await once(child, "message"); |
| 24 | return child; |
| 25 | } |
| 26 | |
| 27 | test("normal app close must end both the actual shell and the Go service", async (t) => { |
| 28 | const shell = await ownedProcess(t); |
| 29 | const service = await ownedProcess(t); |
| 30 | let closeCalls = 0; |
| 31 | await closeAndVerify({ close: async () => { closeCalls++; shell.send("quit"); service.send("quit"); } }, { shellPid: shell.pid, servicePid: service.pid }); |
| 32 | assert.equal(closeCalls, 1); |
| 33 | assert.equal(processAlive(shell.pid), false); |
| 34 | assert.equal(processAlive(service.pid), false); |
| 35 | }); |
| 36 | |
| 37 | test("a live process is a verification failure and is never force-killed by verification", async (t) => { |
| 38 | const service = await ownedProcess(t); |
| 39 | await assert.rejects(waitForProcessesToExit([service.pid], 0), /processes outlived normal app quit/); |
| 40 | assert.equal(processAlive(service.pid), true); |
| 41 | }); |
| 42 | |
| 43 | test("a hung normal app close fails without pretending forced cleanup is success", async (t) => { |
| 44 | const shell = await ownedProcess(t); |
| 45 | await assert.rejects(closeAndVerify({ close: () => new Promise(() => {}) }, { shellPid: shell.pid, servicePid: shell.pid }, 10), /normal app quit did not complete/); |
| 46 | assert.equal(processAlive(shell.pid), true); |
| 47 | }); |
| 48 |