Skip to content

Commit 8bf892c

Browse files
committed
test(repro): GLR blowup #471 + opencode PATHEXT guard #221
- #471 (RED): deeply-nested ambiguous Perl call chain f(f(f(...))) makes the GLR stack-merge O(n^2) (stack_node_add_link recurses over all shared heads; the #461 recursion-depth cap bounds stack depth, not total iterations). Reproduced in a forked child with alarm(15) at depth 5000; asserts the child is not signal-killed (RED when the quadratic blowup blows the time budget). - #221 (GREEN guard): cbm_find_cli now probes Windows PATHEXT (.exe/.cmd/.bat/.ps1, commit 0485d3f), so opencode is resolvable. Guards that fix on a fake opencode shim placed on PATH. Candidate to close after reporter retest. (#548 held: its agent draft spins up a live HTTP server in a thread to exercise handle_browse; reworking to a lighter harness before adding.) Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
1 parent 806da45 commit 8bf892c

4 files changed

Lines changed: 401 additions & 1 deletion

File tree

Makefile.cbm

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,9 @@ TEST_REPRO_SRCS = \
415415
tests/repro/repro_issue431.c \
416416
tests/repro/repro_issue607.c \
417417
tests/repro/repro_issue403.c \
418-
tests/repro/repro_issue434.c
418+
tests/repro/repro_issue434.c \
419+
tests/repro/repro_issue471.c \
420+
tests/repro/repro_issue221.c
419421

420422
ALL_TEST_SRCS =$(TEST_FOUNDATION_SRCS) $(TEST_EXTRACTION_SRCS) $(TEST_STORE_SRCS) $(TEST_CYPHER_SRCS) $(TEST_MCP_SRCS) $(TEST_DISCOVER_SRCS) $(TEST_GRAPH_BUFFER_SRCS) $(TEST_PIPELINE_SRCS) $(TEST_WATCHER_SRCS) $(TEST_LZ4_SRCS) $(TEST_ZSTD_SRCS) $(TEST_ARTIFACT_SRCS) $(TEST_SQLITE_WRITER_SRCS) $(TEST_GO_LSP_SRCS) $(TEST_C_LSP_SRCS) $(TEST_PHP_LSP_SRCS) $(TEST_CS_LSP_SRCS) $(TEST_CS_LSP_BENCH_SRCS) $(TEST_SCOPE_SRCS) $(TEST_TYPE_REP_SRCS) $(TEST_PY_LSP_SRCS) $(TEST_PY_LSP_BENCH_SRCS) $(TEST_PY_LSP_STRESS_SRCS) $(TEST_PY_LSP_SCALE_SRCS) $(TEST_TS_LSP_SRCS) $(TEST_JAVA_LSP_SRCS) $(TEST_KOTLIN_LSP_SRCS) $(TEST_RUST_LSP_SRCS) $(TEST_TRACES_SRCS) $(TEST_CLI_SRCS) $(TEST_MEM_SRCS) $(TEST_UI_SRCS) $(TEST_HTTPD_SRCS) $(TEST_SECURITY_SRCS) $(TEST_YAML_SRCS) $(TEST_SIMHASH_SRCS) $(TEST_STACK_OVERFLOW_SRCS) $(TEST_INTEGRATION_SRCS)
421423

tests/repro/repro_issue221.c

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* repro_issue221.c -- Regression guard for bug #221.
3+
*
4+
* Bug #221: "'install' command does not work for opencode in windows 11"
5+
*
6+
* ROOT CAUSE:
7+
* find_in_path (src/cli/cli.c) probed only the bare executable name
8+
* "opencode" for each PATH entry. On Windows, CLI tools installed via
9+
* mise/npm/scoop ship as extension-bearing shims (.cmd, .ps1, .exe), so
10+
* the bare-name probe never matched and cbm_find_cli("opencode", ...) always
11+
* returned an empty string. The installer therefore concluded opencode was
12+
* absent and skipped wiring it even when it was present on PATH.
13+
*
14+
* FIX (commit 0485d3f, "fix(cli): probe Windows PATHEXT variants in
15+
* find_in_path (#221)"):
16+
* On _WIN32, find_in_path now iterates the common PATHEXT variants
17+
* (.exe, .cmd, .bat, .ps1) for each PATH directory after the bare-name
18+
* probe fails, matching whichever extension-qualified file is present.
19+
*
20+
* REGRESSION GUARD -- expected GREEN on current main (fix is in):
21+
* The fix was committed as 0485d3f and CI (build-windows + test-windows)
22+
* was green before merge. This test is therefore expected to PASS on the
23+
* current codebase. It will turn RED if find_in_path is accidentally
24+
* regressed to bare-name-only lookup.
25+
*
26+
* CROSS-PLATFORM STRATEGY:
27+
* On POSIX: create a plain executable named "opencode" (no extension).
28+
* Bare-name lookup has always worked here, so the test confirms
29+
* cbm_find_cli("opencode", ...) resolves correctly -- the baseline.
30+
* On Windows: create "opencode.cmd" (the most common shim format).
31+
* Before the fix, find_in_path returned "" for this case; after
32+
* the fix it returns the .cmd path -- the regression guard proper.
33+
* Both branches exercise the same public function and assertion; only the
34+
* fixture filename differs.
35+
*
36+
* NOTE: no slash-star inside this block comment to avoid nested-comment UB.
37+
*/
38+
39+
#include <foundation/compat.h>
40+
#include "test_framework.h"
41+
#include "test_helpers.h"
42+
#include <cli/cli.h>
43+
44+
#include <string.h>
45+
#include <stdlib.h>
46+
#include <stdio.h>
47+
48+
/* ── Minimal local helpers (mirror test_cli.c pattern) ──────────────────── */
49+
50+
static int repro221_write_file(const char *path, const char *content) {
51+
FILE *f = fopen(path, "w");
52+
if (!f)
53+
return -1;
54+
fprintf(f, "%s", content);
55+
fclose(f);
56+
return 0;
57+
}
58+
59+
/* ── Test ───────────────────────────────────────────────────────────────── */
60+
61+
/*
62+
* repro_issue221_opencode_pathext_lookup
63+
*
64+
* Verify that cbm_find_cli("opencode", ...) resolves the opencode executable
65+
* (or its Windows .cmd shim) when the containing directory is on PATH.
66+
*
67+
* CORRECT BEHAVIOUR (post-fix):
68+
* cbm_find_cli returns a non-empty string whose basename starts with
69+
* "opencode" -- meaning find_in_path found the file.
70+
*
71+
* BUGGY BEHAVIOUR (pre-fix, Windows only):
72+
* cbm_find_cli returns "" because find_in_path only probed the bare name
73+
* "opencode" and never tried "opencode.cmd" / "opencode.exe" / etc.
74+
*
75+
* GREEN on current main (fix present): ASSERT fires with a non-empty result.
76+
* RED if regressed: ASSERT fires because result is empty.
77+
*/
78+
TEST(repro_issue221_opencode_pathext_lookup) {
79+
/* Create an isolated temp directory to act as a fake PATH entry. */
80+
char tmpdir[256];
81+
snprintf(tmpdir, sizeof(tmpdir), "/tmp/repro221-XXXXXX");
82+
if (!cbm_mkdtemp(tmpdir))
83+
FAIL("cbm_mkdtemp failed");
84+
85+
/*
86+
* Choose the fixture filename to match the platform convention:
87+
* POSIX -- "opencode" (plain executable; bare-name lookup)
88+
* Windows -- "opencode.cmd" (most common shim installed by mise/npm)
89+
*
90+
* On Windows (pre-fix) find_in_path returned "" for "opencode.cmd"
91+
* because only the bare name was probed. The fix tries .cmd before
92+
* moving to the next PATH entry, so the shim is found.
93+
*/
94+
#ifdef _WIN32
95+
const char *fixture_name = "opencode.cmd";
96+
const char *fixture_content = "@echo off\r\nrem fake opencode shim\r\n";
97+
#else
98+
const char *fixture_name = "opencode";
99+
const char *fixture_content = "#!/bin/sh\n# fake opencode\n";
100+
#endif
101+
102+
char fixture_path[512];
103+
snprintf(fixture_path, sizeof(fixture_path), "%s/%s", tmpdir, fixture_name);
104+
105+
if (repro221_write_file(fixture_path, fixture_content) != 0)
106+
FAIL("failed to write opencode fixture");
107+
108+
/* Make executable (no-op on Windows -- extension decides executability). */
109+
th_make_executable(fixture_path);
110+
111+
/* Swap PATH so only tmpdir is searched, isolating the lookup. */
112+
const char *raw_path = getenv("PATH");
113+
char *old_path = raw_path ? strdup(raw_path) : NULL;
114+
cbm_setenv("PATH", tmpdir, 1);
115+
116+
/*
117+
* The function under test: cbm_find_cli is the public API that calls
118+
* find_in_path internally. We pass a non-existent home_dir so fallback
119+
* paths (~/.local/bin etc.) are never tried -- the only possible match
120+
* is the fixture file created above.
121+
*
122+
* Pre-fix (Windows): find_in_path probed "<tmpdir>/opencode" (absent)
123+
* and returned false. cbm_find_cli returned "".
124+
* Post-fix (Windows): find_in_path also probes "<tmpdir>/opencode.cmd"
125+
* (present), finds it, and cbm_find_cli returns the full path.
126+
* POSIX (before and after): bare-name probe succeeds immediately.
127+
*/
128+
const char *result = cbm_find_cli("opencode", "/nonexistent-home-dir");
129+
130+
/* Restore PATH before any assertion so cleanup is always reached. */
131+
if (old_path) {
132+
cbm_setenv("PATH", old_path, 1);
133+
free(old_path);
134+
}
135+
136+
/*
137+
* PRIMARY ASSERTION -- regression guard for #221.
138+
*
139+
* cbm_find_cli MUST return a non-empty path that contains "opencode".
140+
*
141+
* GREEN (current main, fix present): result points to the fixture file.
142+
* RED (if regressed to bare-name-only on Windows): result is "".
143+
*/
144+
ASSERT_FALSE(result == NULL);
145+
ASSERT(result[0] != '\0');
146+
ASSERT(strstr(result, "opencode") != NULL);
147+
148+
/* Cleanup fixture and temp dir. */
149+
(void)remove(fixture_path);
150+
(void)rmdir(tmpdir);
151+
152+
PASS();
153+
}
154+
155+
/* ── Suite ──────────────────────────────────────────────────────────────── */
156+
SUITE(repro_issue221) {
157+
RUN_TEST(repro_issue221_opencode_pathext_lookup);
158+
}

0 commit comments

Comments
 (0)