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

136 lines
6.2 KiB
Python

"""GREEN regression guard — `cli index_repository` honors a non-ASCII repo_path.
Guards the CLI-argv fix for issue #636 / #423 / #20 on native Windows.
The documented entrypoint `codebase-memory-mcp cli index_repository '<json>'`
receives its JSON argument through argv. main() used to take only the narrow
`int main(int argc, char **argv)` (src/main.c), so on Windows the C runtime handed
it argv in the active ANSI code page: a repo_path containing non-ASCII characters
was mangled (or, when yyjson rejected the now-invalid UTF-8, the whole argument was
discarded), and the command failed with "repo_path is required" / "Pipeline failed"
instead of indexing the real directory.
Fixed: on Windows main() now rebuilds argv from the wide command line
(GetCommandLineW + CommandLineToArgvW) and converts each element to UTF-8, so a
non-ASCII repo path survives. This test asserts that fix stays in place — it was RED
before it and is GREEN after. (It is inherently green on Linux/macOS, where argv is
already UTF-8 bytes.)
The directory itself is created with the Windows wide API (Python uses
CreateFileW/_wmkdir under the hood), so it genuinely exists on disk; only the
argv path delivery was lossy.
Exit code: 0 == honored (green), 1 == rejected/mangled (red), 2 == setup error.
Usage:
python test_cli_non_ascii_arg.py <path-to-codebase-memory-mcp[.exe]>
"""
import json
import os
import shutil
import subprocess
import sys
import tempfile
MATH_TS = (
"export function add(a: number, b: number): number { return a + b; }\n"
"export class Calc { total = 0; push(x: number): void { this.total = "
"add(this.total, x); } }\n"
)
def make_fixture(root):
src = os.path.join(root, "src")
os.makedirs(src, exist_ok=True)
with open(os.path.join(src, "math.ts"), "wb") as f:
f.write(MATH_TS.encode("utf-8"))
def main():
if len(sys.argv) < 2:
print("usage: python test_cli_non_ascii_arg.py <binary>")
return 2
binary = os.path.abspath(sys.argv[1])
if not os.path.exists(binary):
print("FAIL: binary not found: %s" % binary)
return 2
work = tempfile.mkdtemp(prefix="cbm_win_cliarg_")
try:
# Non-ASCII repo directory (created via the OS wide API → really exists).
repo = os.path.join(work, "café_日本語_repo")
make_fixture(repo)
cache = os.path.join(work, "cache")
os.makedirs(cache, exist_ok=True)
# Sanity: an ASCII control path must index through the CLI, proving the
# 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")
if '"nodes"' not in ctrl_out:
print("SETUP FAIL: ASCII control did not index via CLI:\n%s" %
ctrl_out[:300])
return 2
env2 = dict(os.environ)
env2["CBM_CACHE_DIR"] = cache
# Exercise the DEFAULT (supervisor-enabled) path, not in-process. The non-ASCII
# repo path must survive BOTH the argv read (main() wide command line) AND the
# supervisor -> worker spawn (CreateProcessW). The suite runner sets
# CBM_INDEX_SUPERVISOR=0 for determinism across the other guards; forcing it OFF
# here would run in-process and mask the spawn-boundary half of #423/#20, so we
# drop the override and let the supervisor wrap the worker as it does for users.
env2.pop("CBM_INDEX_SUPERVISOR", None)
arg = json.dumps({"repo_path": repo}, ensure_ascii=False)
p = subprocess.run([binary, "cli", "index_repository", arg],
capture_output=True, timeout=120, env=env2)
out = (p.stdout or b"").decode("utf-8", "replace")
err = (p.stderr or b"").decode("utf-8", "replace")
honored = '"nodes"' in out and '"nodes":0' not in out.replace(" ", "")
print("ASCII control: indexed OK")
print("non-ASCII argv (supervised): rc=%d" % p.returncode)
print(" stdout: %s" % out[:200].replace("\n", " "))
print(" stderr: %s" % err[-200:].replace("\n", " "))
# #973 variant: the reporter's exact shape — Traditional-Chinese dir,
# FLAG-form --repo-path + --mode fast, supervised. This adds coverage
# of the flag->JSON converter and the repo-path canonicalization,
# which used ANSI _access/_fullpath (locale-dependent — corrupted CJK
# paths on CJK-codepage systems) before the cbm_canonical_path fix.
cjk_repo = os.path.join(work, "\u96f7\u9054\u6e2c\u8a66")
make_fixture(cjk_repo)
env3 = dict(os.environ)
env3["CBM_CACHE_DIR"] = os.path.join(work, "cache_cjk")
env3.pop("CBM_INDEX_SUPERVISOR", None)
p2 = subprocess.run([binary, "cli", "index_repository",
"--repo-path", cjk_repo, "--mode", "fast"],
capture_output=True, timeout=120, env=env3)
out2 = (p2.stdout or b"").decode("utf-8", "replace")
cjk_ok = '"nodes"' in out2 and '"nodes":0' not in out2.replace(" ", "")
print("CJK flag-form (--repo-path, supervised, fast): rc=%d" % p2.returncode)
print(" stdout: %s" % out2[:200].replace("\n", " "))
if not cjk_ok:
print("\nRED: CJK flag-form repo_path was not indexed (#973 — "
"canonicalization or spawn boundary mangled the path).")
return 1
if honored:
print("\nGREEN: CLI honored the non-ASCII repo_path (argv + worker spawn).")
return 0
print("\nRED: CLI did not index the non-ASCII repo_path — the path was mangled "
"either in the argv read (narrow main()) or re-mangled at the "
"supervisor->worker CreateProcess boundary (ANSI code page).")
return 1
finally:
shutil.rmtree(work, ignore_errors=True)
if __name__ == "__main__":
sys.exit(main())