| import { spawn } from "node:child_process"; |
| import { once } from "node:events"; |
|
|
| import { describe, expect, it } from "vitest"; |
|
|
| import { |
| getProcessTreeSpawnOptions, |
| isProcessRunning, |
| resolveWindowsCommand, |
| } from "../../scripts/dev-process-utils.mjs"; |
|
|
| describe("dev process utils", () => { |
| it("treats a signaled but unexited process as still running", () => { |
| expect( |
| isProcessRunning({ |
| exitCode: null, |
| signalCode: null, |
| killed: true, |
| }), |
| ).toBe(true); |
| }); |
|
|
| it("treats exited or signaled processes as stopped", () => { |
| expect(isProcessRunning({ exitCode: 0, signalCode: null })).toBe(false); |
| expect(isProcessRunning({ exitCode: null, signalCode: "SIGTERM" })).toBe( |
| false, |
| ); |
| }); |
|
|
| it("sets detached mode according to the platform for process-group cleanup", () => { |
| expect(getProcessTreeSpawnOptions({ cwd: "/tmp" })).toMatchObject({ |
| cwd: "/tmp", |
| detached: process.platform !== "win32", |
| }); |
| }); |
|
|
| it("passes shell metacharacters to services as literal arguments", async () => { |
| const constraint = "agent-client-protocol<0.11"; |
| const child = spawn( |
| process.execPath, |
| ["-e", "process.stdout.write(process.argv[1])", constraint], |
| getProcessTreeSpawnOptions({ |
| shell: true, |
| stdio: ["ignore", "pipe", "pipe"], |
| }), |
| ); |
| let stdout = ""; |
| child.stdout.setEncoding("utf8"); |
| child.stdout.on("data", (chunk) => { |
| stdout += chunk; |
| }); |
|
|
| const [exitCode] = await once(child, "exit"); |
|
|
| expect(exitCode).toBe(0); |
| expect(stdout).toBe(constraint); |
| }); |
| }); |
|
|
| describe("resolveWindowsCommand", () => { |
| const lookup = (cmd: string) => `C:\\Users\\me\\.local\\bin\\${cmd}.exe`; |
|
|
| it("returns the command unchanged on non-Windows platforms", () => { |
| expect(resolveWindowsCommand("uvx", "linux", lookup)).toBe("uvx"); |
| expect(resolveWindowsCommand("uvx", "darwin", lookup)).toBe("uvx"); |
| }); |
|
|
| it("resolves a bare command to its absolute path on Windows so it can spawn without a shell", () => { |
| |
| |
| |
| |
| expect(resolveWindowsCommand("uvx", "win32", lookup)).toBe( |
| "C:\\Users\\me\\.local\\bin\\uvx.exe", |
| ); |
| }); |
|
|
| it("leaves an already-resolved path untouched on Windows", () => { |
| const absolute = "C:\\Windows\\System32\\cmd.exe"; |
| expect(resolveWindowsCommand(absolute, "win32", lookup)).toBe(absolute); |
| }); |
|
|
| it("falls back to the original command when the lookup fails on Windows", () => { |
| expect(resolveWindowsCommand("uvx", "win32", () => null)).toBe("uvx"); |
| }); |
| }); |
|
|