From a1266348d68a406e4ee16bbc56ef790bde9db4d8 Mon Sep 17 00:00:00 2001 From: ZETA <126369952+zeta987@users.noreply.github.com> Date: Wed, 1 Apr 2026 03:40:08 +0800 Subject: [PATCH] fix: make test suite portable across platforms (#56) * fix: make test suite portable across platforms Replace hardcoded macOS path with fileURLToPath for cross-platform compatibility. Add .cmd wrapper creation and platform-aware PATH separator in test fixtures so the fake codex binary is discoverable on Windows. Add shell and windowsHide options to the test helper run() function to match production behavior. Test results on Windows improve from 12/64 pass to 59/64 pass. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: skip shell for absolute-path commands to avoid Windows space-in-path breakage When `process.execPath` resolves to a path with spaces (e.g., `C:\Program Files\nodejs\node.exe`), `shell: true` causes cmd.exe to split the path at the space. Guard with `path.isAbsolute()` so only bare command names (which need `.cmd` shim resolution) use the shell. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Dominik Kundel --- tests/commands.test.mjs | 2 +- tests/fake-codex-fixture.mjs | 12 +++++++++++- tests/helpers.mjs | 5 ++++- tests/runtime.test.mjs | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/commands.test.mjs b/tests/commands.test.mjs index 809fdff..62bea9b 100644 --- a/tests/commands.test.mjs +++ b/tests/commands.test.mjs @@ -4,7 +4,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { fileURLToPath } from "node:url"; -const ROOT = path.resolve(fileURLToPath(new URL("..", import.meta.url))); +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const PLUGIN_ROOT = path.join(ROOT, "plugins", "codex"); function read(relativePath) { diff --git a/tests/fake-codex-fixture.mjs b/tests/fake-codex-fixture.mjs index ac7f084..a85fd31 100644 --- a/tests/fake-codex-fixture.mjs +++ b/tests/fake-codex-fixture.mjs @@ -1,4 +1,6 @@ +import fs from "node:fs"; import path from "node:path"; +import process from "node:process"; import { writeExecutable } from "./helpers.mjs"; @@ -507,11 +509,19 @@ rl.on("line", (line) => { }); `; writeExecutable(scriptPath, source); + + // On Windows, npm global binaries are invoked via .cmd wrappers. + // Create a codex.cmd so the fake binary is discoverable by spawn with shell: true. + if (process.platform === "win32") { + const cmdWrapper = `@echo off\r\nnode "%~dp0codex" %*\r\n`; + fs.writeFileSync(path.join(binDir, "codex.cmd"), cmdWrapper, { encoding: "utf8" }); + } } export function buildEnv(binDir) { + const sep = process.platform === "win32" ? ";" : ":"; return { ...process.env, - PATH: `${binDir}:${process.env.PATH}` + PATH: `${binDir}${sep}${process.env.PATH}` }; } diff --git a/tests/helpers.mjs b/tests/helpers.mjs index 799fe3c..945ae0e 100644 --- a/tests/helpers.mjs +++ b/tests/helpers.mjs @@ -1,6 +1,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import process from "node:process"; import { spawnSync } from "node:child_process"; export function makeTempDir(prefix = "codex-plugin-test-") { @@ -16,7 +17,9 @@ export function run(command, args, options = {}) { cwd: options.cwd, env: options.env, encoding: "utf8", - input: options.input + input: options.input, + shell: process.platform === "win32" && !path.isAbsolute(command), + windowsHide: true }); } diff --git a/tests/runtime.test.mjs b/tests/runtime.test.mjs index 15f96c5..56ba6c1 100644 --- a/tests/runtime.test.mjs +++ b/tests/runtime.test.mjs @@ -10,7 +10,7 @@ import { initGitRepo, makeTempDir, run } from "./helpers.mjs"; import { loadBrokerSession } from "../plugins/codex/scripts/lib/broker-lifecycle.mjs"; import { resolveStateDir } from "../plugins/codex/scripts/lib/state.mjs"; -const ROOT = path.resolve(fileURLToPath(new URL("..", import.meta.url))); +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const PLUGIN_ROOT = path.join(ROOT, "plugins", "codex"); const SCRIPT = path.join(PLUGIN_ROOT, "scripts", "codex-companion.mjs"); const STOP_HOOK = path.join(PLUGIN_ROOT, "scripts", "stop-review-gate-hook.mjs");