Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions tests/windows/mcp_stdio.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,34 @@ def close(self):
self.proc.kill()
except Exception:
pass


def wait_projects_with_stats(server, timeout=90.0, poll=1.0):
"""Poll list_projects until a project row carries node/edge counts.

index_repository returns when the pipeline finishes, but the project
stats are published asynchronously; on slow CI runners the first
list_projects can observe the registration row before its counts land
(`nodes` absent/None). That window is environmental, not a product
regression (#1952), so wait it out instead of failing setup on it.

Returns (projects, last_text): the parsed project list (possibly [])
and the last raw list_projects payload for diagnostics.
"""
deadline = time.time() + timeout
last_txt = ""
while True:
resp = server.call_tool("list_projects", {}, timeout=60)
txt, err = server.tool_text(resp)
projects = []
if not err and txt:
last_txt = txt
try:
projects = json.loads(txt).get("projects") or []
except ValueError:
projects = []
if projects and projects[0].get("nodes") is not None:
return projects, last_txt
if time.time() >= deadline:
return projects, last_txt
time.sleep(poll)
26 changes: 19 additions & 7 deletions tests/windows/test_cli_non_ascii_arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import subprocess
import sys
import tempfile
import time

MATH_TS = (
"export function add(a: number, b: number): number { return a + b; }\n"
Expand Down Expand Up @@ -67,13 +68,24 @@ def main():
# CLI path itself works and isolating the failure to argv encoding.
ascii_repo = os.path.join(work, "ascii_repo")
make_fixture(ascii_repo)
env = dict(os.environ)
env["CBM_CACHE_DIR"] = os.path.join(work, "cache_ascii")
ctrl = subprocess.run(
[binary, "cli", "index_repository",
json.dumps({"repo_path": ascii_repo})],
capture_output=True, timeout=120, env=env)
ctrl_out = (ctrl.stdout or b"").decode("utf-8", "replace")
# The control is setup, not the surface under test: a cold runner can
# lose the first one-shot to coordination-daemon startup latency
# (#1952). One retry against a fresh cache separates that from a real
# CLI indexing failure.
ctrl_out = ""
for attempt in ("cache_ascii", "cache_ascii_retry"):
env = dict(os.environ)
env["CBM_CACHE_DIR"] = os.path.join(work, attempt)
ctrl = subprocess.run(
[binary, "cli", "index_repository",
json.dumps({"repo_path": ascii_repo})],
capture_output=True, timeout=120, env=env)
ctrl_out = (ctrl.stdout or b"").decode("utf-8", "replace")
if '"nodes"' in ctrl_out:
break
print("SETUP: ASCII control attempt %r did not index via CLI:\n%s"
% (attempt, ctrl_out[:300]))
time.sleep(2)
if '"nodes"' not in ctrl_out:
print("SETUP FAIL: ASCII control did not index via CLI:\n%s" %
ctrl_out[:300])
Expand Down
32 changes: 25 additions & 7 deletions tests/windows/test_hook_augment.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import subprocess
import sys
import tempfile
import time

SYMBOL = "someIndexedSymbol"
SRC = "export function %s(a: number): number { return a + 1; }\n" % SYMBOL
Expand Down Expand Up @@ -63,9 +64,19 @@ def main():

# repo_path / cwd in the forward-slash drive form Claude Code passes.
repo_fwd = repo.replace("\\", "/")
idx = run_cli(binary, cache, ["cli", "index_repository",
json.dumps({"repo_path": repo_fwd})])
idx_out = (idx.stdout or b"").decode("utf-8", "replace")
# Setup, not the surface under test: retry once so a cold runner's
# coordination-daemon startup latency (#1952) is not misread as a
# broken CLI index. Reindexing the same cache is idempotent.
idx_out = ""
for attempt in (1, 2):
idx = run_cli(binary, cache, ["cli", "index_repository",
json.dumps({"repo_path": repo_fwd})])
idx_out = (idx.stdout or b"").decode("utf-8", "replace")
if '"nodes"' in idx_out:
break
print("SETUP: index attempt %d did not run:\n%s"
% (attempt, idx_out[:300]))
time.sleep(2)
if '"nodes"' not in idx_out:
print("SETUP FAIL: index did not run:\n%s" % idx_out[:300])
return 2
Expand All @@ -88,10 +99,17 @@ def main():
# first — the supported way to keep hooks armed outside MCP sessions —
# and retire it afterwards (with a kill-by-pid backstop so a stuck stop
# can never hang CI).
start = run_cli(binary, cache, ["daemon", "start"], timeout=60)
start_out = (start.stdout or b"").decode("utf-8", "replace")
print("daemon start rc=%d %r" % (start.returncode, start_out[:120]))
if start.returncode != 0:
start_out = ""
rc = 1
for attempt in (1, 2):
start = run_cli(binary, cache, ["daemon", "start"], timeout=60)
start_out = (start.stdout or b"").decode("utf-8", "replace")
rc = start.returncode
print("daemon start attempt %d rc=%d %r" % (attempt, rc, start_out[:120]))
if rc == 0:
break
time.sleep(2)
if rc != 0:
print("SETUP FAIL: permanent daemon did not start")
return 2
pid_match = re.search(r"pid (\d+)", start_out)
Expand Down
Loading
Loading