test(ci): deflake windows npm exec coverage

This commit is contained in:
Peter Steinberger 2026-03-31 19:28:11 +01:00
parent 66413487c8
commit b87f33c920
No known key found for this signature in database
2 changed files with 56 additions and 24 deletions

View File

@ -1,6 +1,5 @@
import type { ChildProcess } from "node:child_process";
import { EventEmitter } from "node:events";
import fs from "node:fs";
import process from "node:process";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { OPENCLAW_CLI_ENV_VALUE } from "../infra/openclaw-exec-env.js";
@ -120,29 +119,6 @@ describe("runCommandWithTimeout", () => {
expect(result.code).not.toBe(0);
},
);
it.runIf(process.platform === "win32")(
"on Windows spawns node + npm-cli.js for npm argv to avoid spawn EINVAL",
async () => {
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 10_000 });
expect(result.code).toBe(0);
expect(result.stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/);
},
);
it.runIf(process.platform === "win32")(
"falls back to npm.cmd when npm-cli.js is unavailable",
async () => {
const existsSpy = vi.spyOn(fs, "existsSync").mockReturnValue(false);
try {
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 10_000 });
expect(result.code).toBe(0);
expect(result.stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/);
} finally {
existsSpy.mockRestore();
}
},
);
});
describe("attachChildProcessBridge", () => {

View File

@ -1,4 +1,6 @@
import { EventEmitter } from "node:events";
import fs from "node:fs";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const spawnMock = vi.hoisted(() => vi.fn());
@ -124,6 +126,60 @@ describe("windows command wrapper behavior", () => {
}
});
it("spawns node + npm-cli.js for npm argv to avoid direct .cmd execution", async () => {
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
const existsSpy = vi.spyOn(fs, "existsSync").mockReturnValue(true);
const child = createMockChild({ closeCode: 0, exitCode: 0 });
spawnMock.mockImplementation(() => child);
try {
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 });
expect(result.code).toBe(0);
const captured = spawnMock.mock.calls[0] as SpawnCall | undefined;
if (!captured) {
throw new Error("expected npm shim spawn");
}
expect(captured[0]).toBe(process.execPath);
expect(captured[1][0]).toBe(
path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js"),
);
expect(captured[1][1]).toBe("--version");
expect(captured[2].windowsVerbatimArguments).toBeUndefined();
expect(captured[2].stdio).toEqual(["inherit", "pipe", "pipe"]);
} finally {
existsSpy.mockRestore();
platformSpy.mockRestore();
}
});
it("falls back to npm.cmd when npm-cli.js is unavailable", async () => {
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
const existsSpy = vi.spyOn(fs, "existsSync").mockReturnValue(false);
const expectedComSpec = process.env.ComSpec ?? "cmd.exe";
spawnMock.mockImplementation(
(_command: string, _args: string[], _options: Record<string, unknown>) => createMockChild(),
);
try {
const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 });
expect(result.code).toBe(0);
const captured = spawnMock.mock.calls[0] as SpawnCall | undefined;
if (!captured) {
throw new Error("expected npm.cmd fallback spawn");
}
expect(captured[0]).toBe(expectedComSpec);
expect(captured[1].slice(0, 3)).toEqual(["/d", "/s", "/c"]);
expect(captured[1][3]).toContain("npm.cmd --version");
expect(captured[2].windowsVerbatimArguments).toBe(true);
expect(captured[2].stdio).toEqual(["inherit", "pipe", "pipe"]);
} finally {
existsSpy.mockRestore();
platformSpy.mockRestore();
}
});
it("waits for Windows exitCode settlement after close reports null", async () => {
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
const child = createMockChild({