chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
Reference in New Issue
Block a user