Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

159 lines
6.4 KiB
C

/*
* repro_issue221.c -- Regression guard for bug #221.
*
* Bug #221: "'install' command does not work for opencode in windows 11"
*
* ROOT CAUSE:
* find_in_path (src/cli/cli.c) probed only the bare executable name
* "opencode" for each PATH entry. On Windows, CLI tools installed via
* mise/npm/scoop ship as extension-bearing shims (.cmd, .ps1, .exe), so
* the bare-name probe never matched and cbm_find_cli("opencode", ...) always
* returned an empty string. The installer therefore concluded opencode was
* absent and skipped wiring it even when it was present on PATH.
*
* FIX (commit 0485d3f, "fix(cli): probe Windows PATHEXT variants in
* find_in_path (#221)"):
* On _WIN32, find_in_path now iterates the common PATHEXT variants
* (.exe, .cmd, .bat, .ps1) for each PATH directory after the bare-name
* probe fails, matching whichever extension-qualified file is present.
*
* REGRESSION GUARD -- expected GREEN on current main (fix is in):
* The fix was committed as 0485d3f and CI (build-windows + test-windows)
* was green before merge. This test is therefore expected to PASS on the
* current codebase. It will turn RED if find_in_path is accidentally
* regressed to bare-name-only lookup.
*
* CROSS-PLATFORM STRATEGY:
* On POSIX: create a plain executable named "opencode" (no extension).
* Bare-name lookup has always worked here, so the test confirms
* cbm_find_cli("opencode", ...) resolves correctly -- the baseline.
* On Windows: create "opencode.cmd" (the most common shim format).
* Before the fix, find_in_path returned "" for this case; after
* the fix it returns the .cmd path -- the regression guard proper.
* Both branches exercise the same public function and assertion; only the
* fixture filename differs.
*
* NOTE: no slash-star inside this block comment to avoid nested-comment UB.
*/
#include <foundation/compat.h>
#include "test_framework.h"
#include "test_helpers.h"
#include <cli/cli.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* ── Minimal local helpers (mirror test_cli.c pattern) ──────────────────── */
static int repro221_write_file(const char *path, const char *content) {
FILE *f = fopen(path, "w");
if (!f)
return -1;
fprintf(f, "%s", content);
fclose(f);
return 0;
}
/* ── Test ───────────────────────────────────────────────────────────────── */
/*
* repro_issue221_opencode_pathext_lookup
*
* Verify that cbm_find_cli("opencode", ...) resolves the opencode executable
* (or its Windows .cmd shim) when the containing directory is on PATH.
*
* CORRECT BEHAVIOUR (post-fix):
* cbm_find_cli returns a non-empty string whose basename starts with
* "opencode" -- meaning find_in_path found the file.
*
* BUGGY BEHAVIOUR (pre-fix, Windows only):
* cbm_find_cli returns "" because find_in_path only probed the bare name
* "opencode" and never tried "opencode.cmd" / "opencode.exe" / etc.
*
* GREEN on current main (fix present): ASSERT fires with a non-empty result.
* RED if regressed: ASSERT fires because result is empty.
*/
TEST(repro_issue221_opencode_pathext_lookup) {
/* Create an isolated temp directory to act as a fake PATH entry. */
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/repro221-XXXXXX");
if (!cbm_mkdtemp(tmpdir))
FAIL("cbm_mkdtemp failed");
/*
* Choose the fixture filename to match the platform convention:
* POSIX -- "opencode" (plain executable; bare-name lookup)
* Windows -- "opencode.cmd" (most common shim installed by mise/npm)
*
* On Windows (pre-fix) find_in_path returned "" for "opencode.cmd"
* because only the bare name was probed. The fix tries .cmd before
* moving to the next PATH entry, so the shim is found.
*/
#ifdef _WIN32
const char *fixture_name = "opencode.cmd";
const char *fixture_content = "@echo off\r\nrem fake opencode shim\r\n";
#else
const char *fixture_name = "opencode";
const char *fixture_content = "#!/bin/sh\n# fake opencode\n";
#endif
char fixture_path[512];
snprintf(fixture_path, sizeof(fixture_path), "%s/%s", tmpdir, fixture_name);
if (repro221_write_file(fixture_path, fixture_content) != 0)
FAIL("failed to write opencode fixture");
/* Make executable (no-op on Windows -- extension decides executability). */
th_make_executable(fixture_path);
/* Swap PATH so only tmpdir is searched, isolating the lookup. */
const char *raw_path = getenv("PATH");
char *old_path = raw_path ? strdup(raw_path) : NULL;
cbm_setenv("PATH", tmpdir, 1);
/*
* The function under test: cbm_find_cli is the public API that calls
* find_in_path internally. We pass a non-existent home_dir so fallback
* paths (~/.local/bin etc.) are never tried -- the only possible match
* is the fixture file created above.
*
* Pre-fix (Windows): find_in_path probed "<tmpdir>/opencode" (absent)
* and returned false. cbm_find_cli returned "".
* Post-fix (Windows): find_in_path also probes "<tmpdir>/opencode.cmd"
* (present), finds it, and cbm_find_cli returns the full path.
* POSIX (before and after): bare-name probe succeeds immediately.
*/
const char *result = cbm_find_cli("opencode", "/nonexistent-home-dir");
/* Restore PATH before any assertion so cleanup is always reached. */
if (old_path) {
cbm_setenv("PATH", old_path, 1);
free(old_path);
}
/*
* PRIMARY ASSERTION -- regression guard for #221.
*
* cbm_find_cli MUST return a non-empty path that contains "opencode".
*
* GREEN (current main, fix present): result points to the fixture file.
* RED (if regressed to bare-name-only on Windows): result is "".
*/
ASSERT_FALSE(result == NULL);
ASSERT(result[0] != '\0');
ASSERT(strstr(result, "opencode") != NULL);
/* Cleanup fixture and temp dir. */
(void)remove(fixture_path);
(void)rmdir(tmpdir);
PASS();
}
/* ── Suite ──────────────────────────────────────────────────────────────── */
SUITE(repro_issue221) {
RUN_TEST(repro_issue221_opencode_pathext_lookup);
}