{"version":3,"file":"process-runner.test.d.ts","sourceRoot":"","sources":["../../src/core/process-runner.test.ts"],"names":[],"mappings":"","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { getEventListeners } from \"node:events\";\nimport { describe, expect, it } from \"vitest\";\nimport { runForegroundProcess } from \"./process-runner.js\";\n\nfunction parsePositiveFixturePid(output: string, marker: string): number {\n\tconst match = output.match(new RegExp(`${marker}:(\\\\d+)`));\n\tif (!match) throw new Error(`Process fixture did not emit ${marker} marker. Output: ${output}`);\n\n\tconst pid = Number(match[1]);\n\tif (!Number.isSafeInteger(pid) || pid <= 0)\n\t\tthrow new Error(`Process fixture emitted invalid ${marker}: ${match[1]}`);\n\treturn pid;\n}\n\nfunction runNodeFixture(source: string, options: { timeout?: number; signal?: AbortSignal } = {}) {\n\tconst child = spawn(process.execPath, [\"-e\", source], {\n\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\twindowsHide: true,\n\t});\n\tconst stdout: string[] = [];\n\tconst stderr: string[] = [];\n\treturn {\n\t\tchild,\n\t\tstdout,\n\t\tstderr,\n\t\tresult: runForegroundProcess({\n\t\t\tchild,\n\t\t\tonStdout: (data) => stdout.push(data.toString(\"utf8\")),\n\t\t\tonStderr: (data) => stderr.push(data.toString(\"utf8\")),\n\t\t\tsignal: options.signal,\n\t\t\ttimeout: options.timeout,\n\t\t\tkill: () => child.kill(),\n\t\t}),\n\t};\n}\n\ndescribe(\"runForegroundProcess\", () => {\n\tit(\"waits for a genuinely foreground command\", async () => {\n\t\tconst startedAt = Date.now();\n\t\tconst run = runNodeFixture(\"setTimeout(() => process.stdout.write('done\\\\n'), 150)\");\n\n\t\tawait expect(run.result).resolves.toEqual({ exitCode: 0 });\n\t\texpect(Date.now() - startedAt).toBeGreaterThanOrEqual(100);\n\t\texpect(run.stdout.join(\"\")).toBe(\"done\\n\");\n\t});\n\n\tit(\"completes after wrapper exit when descendant holds inherited pipes\", async () => {\n\t\tconst source = [\n\t\t\t\"const { spawn } = require('node:child_process');\",\n\t\t\t\"const holder = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 5000)'], { stdio: ['ignore', process.stdout, process.stderr] });\",\n\t\t\t\"process.stdout.write('BEFORE_WRAPPER_EXIT\\\\n');\",\n\t\t\t\"process.stderr.write('HOLDER_PID:' + holder.pid + '\\\\n');\",\n\t\t\t\"process.exit(0);\",\n\t\t].join(\" \");\n\t\tconst startedAt = Date.now();\n\t\tconst run = runNodeFixture(source);\n\n\t\tlet settlements = 0;\n\t\tawait expect(run.result.finally(() => settlements++)).resolves.toEqual({ exitCode: 0 });\n\t\texpect(Date.now() - startedAt).toBeLessThan(2000);\n\t\texpect(run.stdout.join(\"\")).toContain(\"BEFORE_WRAPPER_EXIT\");\n\t\texpect(settlements).toBe(1);\n\t\texpect(run.child.listenerCount(\"exit\")).toBe(0);\n\t\texpect(run.child.listenerCount(\"close\")).toBe(0);\n\t\texpect(run.child.listenerCount(\"error\")).toBe(0);\n\t\texpect(run.child.stdout?.listenerCount(\"data\")).toBe(0);\n\t\texpect(run.child.stderr?.listenerCount(\"data\")).toBe(0);\n\n\t\tconst holderPid = parsePositiveFixturePid(run.stderr.join(\"\"), \"HOLDER_PID\");\n\t\ttry {\n\t\t\tprocess.kill(holderPid);\n\t\t} catch {\n\t\t\t// Fixture may have completed during test cleanup.\n\t\t}\n\t});\n\n\tit(\"reports timeout before wrapper exit\", async () => {\n\t\tconst run = runNodeFixture(\"setTimeout(() => {}, 5000)\", { timeout: 0.05 });\n\n\t\tawait expect(run.result).rejects.toThrow(\"timeout:0.05\");\n\t});\n\n\tit(\"reports abort once attached process is terminated\", async () => {\n\t\tconst controller = new AbortController();\n\t\tconst run = runNodeFixture(\"setTimeout(() => {}, 5000)\", { signal: controller.signal });\n\t\tsetTimeout(() => controller.abort(), 25);\n\n\t\tawait expect(run.result).rejects.toThrow(\"aborted\");\n\t\texpect(getEventListeners(controller.signal, \"abort\")).toHaveLength(0);\n\t});\n\n\tit(\"rejects missing and invalid fixture PID markers clearly\", () => {\n\t\texpect(() => parsePositiveFixturePid(\"BEFORE_WRAPPER_EXIT\\n\", \"HOLDER_PID\")).toThrow(\n\t\t\t\"Process fixture did not emit HOLDER_PID marker\",\n\t\t);\n\t\texpect(() => parsePositiveFixturePid(\"HOLDER_PID:0\\n\", \"HOLDER_PID\")).toThrow(\n\t\t\t\"Process fixture emitted invalid HOLDER_PID\",\n\t\t);\n\t});\n});\n"]}