


























@@ -65,6 +65,9 @@ const cleanStaleGatewayProcessesSync = vi.hoisted(() =>
6565const inspectPortUsage = vi.hoisted(() =>
6666vi.fn(async () => ({ port: 18789, status: "free", listeners: [], hints: [] })),
6767);
68+const probePortUsage = vi.hoisted(() =>
69+vi.fn<typeof import("../infra/ports-probe.js").probePortUsage>(async () => "free"),
70+);
6871const formatPortDiagnostics = vi.hoisted(() => vi.fn(() => ["Port 18789 is already in use."]));
6972const defaultProgramArguments = ["node", "-e", "process.exit(0)"];
7073@@ -262,6 +265,10 @@ vi.mock("../infra/ports.js", () => ({
262265 formatPortDiagnostics,
263266}));
264267268+vi.mock("../infra/ports-probe.js", () => ({
269+ probePortUsage,
270+}));
271+265272vi.mock("node:fs/promises", async () => {
266273const actual = await vi.importActual<typeof import("node:fs/promises")>("node:fs/promises");
267274const wrapped = {
@@ -354,6 +361,8 @@ beforeEach(() => {
354361cleanStaleGatewayProcessesSync.mockReturnValue([]);
355362inspectPortUsage.mockReset();
356363inspectPortUsage.mockResolvedValue({ port: 18789, status: "free", listeners: [], hints: [] });
364+probePortUsage.mockReset();
365+probePortUsage.mockResolvedValue("free");
357366formatPortDiagnostics.mockReset();
358367formatPortDiagnostics.mockReturnValue(["Port 18789 is already in use."]);
359368launchdRestartHandoffState.scheduleDetachedLaunchdRestartHandoff.mockReset();
@@ -1187,6 +1196,42 @@ describe("launchd install", () => {
11871196expect(output).toContain("Stopped LaunchAgent");
11881197});
118911981199+it("waits for the configured gateway port to finish releasing after bootout", async () => {
1200+const env = {
1201+ ...createDefaultLaunchdEnv(),
1202+OPENCLAW_GATEWAY_PORT: "19009",
1203+};
1204+inspectPortUsage.mockResolvedValueOnce({
1205+port: 19009,
1206+status: "busy",
1207+listeners: [],
1208+hints: [],
1209+});
1210+1211+await runStopLaunchAgentWithFakeTimers({ env, stdout: new PassThrough() });
1212+1213+expect(inspectPortUsage).toHaveBeenCalledTimes(1);
1214+expect(probePortUsage).toHaveBeenCalledWith(19009);
1215+});
1216+1217+it("keeps waiting until a bind probe explicitly confirms port release", async () => {
1218+const env = {
1219+ ...createDefaultLaunchdEnv(),
1220+OPENCLAW_GATEWAY_PORT: "19010",
1221+};
1222+inspectPortUsage.mockResolvedValueOnce({
1223+port: 19010,
1224+status: "busy",
1225+listeners: [],
1226+hints: [],
1227+});
1228+probePortUsage.mockResolvedValueOnce("busy").mockResolvedValueOnce("unknown");
1229+1230+await runStopLaunchAgentWithFakeTimers({ env, stdout: new PassThrough() });
1231+1232+expect(probePortUsage).toHaveBeenCalledTimes(3);
1233+});
1234+11901235it("resolves the stop postcondition port from the stored LaunchAgent environment", async () => {
11911236const env = createDefaultLaunchdEnv();
11921237await installLaunchAgent({
@@ -1219,9 +1264,10 @@ describe("launchd install", () => {
12191264listeners: [],
12201265hints: [],
12211266});
1267+probePortUsage.mockResolvedValue("busy");
12221268formatPortDiagnostics.mockReturnValue(["Port 19004 is held by pid 4242."]);
122312691224-await expect(stopLaunchAgent({ env, stdout })).rejects.toThrow(
1270+await expect(runStopLaunchAgentWithFakeTimers({ env, stdout })).rejects.toThrow(
12251271"gateway port 19004 is still busy after LaunchAgent stop\nPort 19004 is held by pid 4242.",
12261272);
12271273@@ -1364,9 +1410,10 @@ describe("launchd install", () => {
13641410listeners: [],
13651411hints: [],
13661412});
1413+probePortUsage.mockResolvedValue("busy");
13671414formatPortDiagnostics.mockReturnValue(["Port 19008 is held by pid 4242."]);
136814151369-await expect(stopLaunchAgent({ env, stdout, disable: true })).rejects.toThrow(
1416+await expect(runStopLaunchAgentWithFakeTimers({ env, stdout, disable: true })).rejects.toThrow(
13701417"gateway port 19008 is still busy after LaunchAgent stop\nPort 19008 is held by pid 4242.",
13711418);
13721419此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。