From e70c43bcd484f5298515eb76a12a3a87155bf897 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 20 Mar 2026 03:03:57 -0700 Subject: [PATCH] fix: harden windows CI tests and markdown lint --- docs/zh-CN/CODE_OF_CONDUCT.md | 2 +- docs/zh-CN/commands/python-review.md | 2 +- tests/hooks/cost-tracker.test.js | 15 ++++++--- tests/hooks/detect-project-worktree.test.js | 37 +++++++++++++++------ tests/hooks/hooks.test.js | 2 +- tests/lib/resolve-ecc-root.test.js | 13 ++++++-- 6 files changed, 51 insertions(+), 20 deletions(-) diff --git a/docs/zh-CN/CODE_OF_CONDUCT.md b/docs/zh-CN/CODE_OF_CONDUCT.md index 77c5ffd0..09caabde 100644 --- a/docs/zh-CN/CODE_OF_CONDUCT.md +++ b/docs/zh-CN/CODE_OF_CONDUCT.md @@ -71,7 +71,7 @@ ## 归属 -本行为准则改编自 \[贡献者公约]\[homepage] 2.0 版本,可访问 +本行为准则改编自 [贡献者公约][homepage] 2.0 版本,可访问 获取。 社区影响指南的灵感来源于 [Mozilla 的行为准则执行阶梯](https://github.com/mozilla/diversity)。 diff --git a/docs/zh-CN/commands/python-review.md b/docs/zh-CN/commands/python-review.md index 82df0db0..75398c63 100644 --- a/docs/zh-CN/commands/python-review.md +++ b/docs/zh-CN/commands/python-review.md @@ -315,6 +315,6 @@ result = "".join(str(item) for item in items) | 海象运算符 (`:=`) | 3.8+ | | 仅限位置参数 | 3.8+ | | Match 语句 | 3.10+ | -| 类型联合 (\`x | None\`) | 3.10+ | +| 类型联合 (`x \| None`) | 3.10+ | 确保你的项目 `pyproject.toml` 或 `setup.py` 指定了正确的最低 Python 版本。 diff --git a/tests/hooks/cost-tracker.test.js b/tests/hooks/cost-tracker.test.js index a64c5448..3b474912 100644 --- a/tests/hooks/cost-tracker.test.js +++ b/tests/hooks/cost-tracker.test.js @@ -28,6 +28,13 @@ function makeTempDir() { return fs.mkdtempSync(path.join(os.tmpdir(), 'cost-tracker-test-')); } +function withTempHome(homeDir) { + return { + HOME: homeDir, + USERPROFILE: homeDir, + }; +} + function runScript(input, envOverrides = {}) { const inputStr = typeof input === 'string' ? input : JSON.stringify(input); const result = spawnSync('node', [script], { @@ -64,7 +71,7 @@ function runTests() { model: 'claude-sonnet-4-20250514', usage: { input_tokens: 1000, output_tokens: 500 }, }; - const result = runScript(input, { HOME: tmpHome }); + const result = runScript(input, withTempHome(tmpHome)); assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`); const metricsFile = path.join(tmpHome, '.claude', 'metrics', 'costs.jsonl'); @@ -84,7 +91,7 @@ function runTests() { // 3. Handles empty input gracefully (test('handles empty input gracefully', () => { const tmpHome = makeTempDir(); - const result = runScript('', { HOME: tmpHome }); + const result = runScript('', withTempHome(tmpHome)); assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`); // stdout should be empty since input was empty assert.strictEqual(result.stdout, '', 'Expected empty stdout for empty input'); @@ -96,7 +103,7 @@ function runTests() { (test('handles invalid JSON gracefully', () => { const tmpHome = makeTempDir(); const invalidInput = 'not valid json {{{'; - const result = runScript(invalidInput, { HOME: tmpHome }); + const result = runScript(invalidInput, withTempHome(tmpHome)); assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`); // Should still pass through the raw input on stdout assert.strictEqual(result.stdout, invalidInput, 'Expected stdout to contain original invalid input'); @@ -109,7 +116,7 @@ function runTests() { const tmpHome = makeTempDir(); const input = { model: 'claude-sonnet-4-20250514' }; const inputStr = JSON.stringify(input); - const result = runScript(input, { HOME: tmpHome }); + const result = runScript(input, withTempHome(tmpHome)); assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`); assert.strictEqual(result.stdout, inputStr, 'Expected stdout to match original input'); diff --git a/tests/hooks/detect-project-worktree.test.js b/tests/hooks/detect-project-worktree.test.js index 41c86372..1add402e 100644 --- a/tests/hooks/detect-project-worktree.test.js +++ b/tests/hooks/detect-project-worktree.test.js @@ -41,6 +41,20 @@ function cleanupDir(dir) { } } +function toBashPath(filePath) { + if (process.platform !== 'win32') { + return filePath; + } + + return String(filePath) + .replace(/^([A-Za-z]):/, (_, driveLetter) => `/${driveLetter.toLowerCase()}`) + .replace(/\\/g, '/'); +} + +function runBash(command, options = {}) { + return execSync(`bash -lc '${command.replace(/'/g, "'\\''")}'`, options).toString().trim(); +} + const repoRoot = path.resolve(__dirname, '..', '..'); const detectProjectPath = path.join( repoRoot, @@ -98,7 +112,7 @@ test('[ -d ] returns true for .git directory', () => { const dir = path.join(behaviorDir, 'test-d-dir'); fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(path.join(dir, '.git')); - const result = execSync(`bash -c '[ -d "${dir}/.git" ] && echo yes || echo no'`).toString().trim(); + const result = runBash(`[ -d "${toBashPath(path.join(dir, '.git'))}" ] && echo yes || echo no`); assert.strictEqual(result, 'yes'); }); @@ -106,7 +120,7 @@ test('[ -d ] returns false for .git file', () => { const dir = path.join(behaviorDir, 'test-d-file'); fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(path.join(dir, '.git'), 'gitdir: /some/path\n'); - const result = execSync(`bash -c '[ -d "${dir}/.git" ] && echo yes || echo no'`).toString().trim(); + const result = runBash(`[ -d "${toBashPath(path.join(dir, '.git'))}" ] && echo yes || echo no`); assert.strictEqual(result, 'no'); }); @@ -114,7 +128,7 @@ test('[ -e ] returns true for .git directory', () => { const dir = path.join(behaviorDir, 'test-e-dir'); fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(path.join(dir, '.git')); - const result = execSync(`bash -c '[ -e "${dir}/.git" ] && echo yes || echo no'`).toString().trim(); + const result = runBash(`[ -e "${toBashPath(path.join(dir, '.git'))}" ] && echo yes || echo no`); assert.strictEqual(result, 'yes'); }); @@ -122,14 +136,14 @@ test('[ -e ] returns true for .git file', () => { const dir = path.join(behaviorDir, 'test-e-file'); fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(path.join(dir, '.git'), 'gitdir: /some/path\n'); - const result = execSync(`bash -c '[ -e "${dir}/.git" ] && echo yes || echo no'`).toString().trim(); + const result = runBash(`[ -e "${toBashPath(path.join(dir, '.git'))}" ] && echo yes || echo no`); assert.strictEqual(result, 'yes'); }); test('[ -e ] returns false when .git does not exist', () => { const dir = path.join(behaviorDir, 'test-e-none'); fs.mkdirSync(dir, { recursive: true }); - const result = execSync(`bash -c '[ -e "${dir}/.git" ] && echo yes || echo no'`).toString().trim(); + const result = runBash(`[ -e "${toBashPath(path.join(dir, '.git'))}" ] && echo yes || echo no`); assert.strictEqual(result, 'no'); }); @@ -188,20 +202,21 @@ test('detect-project.sh sets PROJECT_NAME and non-global PROJECT_ID for worktree // Source detect-project.sh from the worktree directory and capture results const script = ` - export CLAUDE_PROJECT_DIR="${worktreeDir}" - export HOME="${testDir}" - source "${detectProjectPath}" + export CLAUDE_PROJECT_DIR="${toBashPath(worktreeDir)}" + export HOME="${toBashPath(testDir)}" + source "${toBashPath(detectProjectPath)}" echo "PROJECT_NAME=\${PROJECT_NAME}" echo "PROJECT_ID=\${PROJECT_ID}" `; - const result = execSync(`bash -c '${script.replace(/'/g, "'\\''")}'`, { + const result = execSync(`bash -lc '${script.replace(/'/g, "'\\''")}'`, { cwd: worktreeDir, timeout: 10000, env: { ...process.env, - HOME: testDir, - CLAUDE_PROJECT_DIR: worktreeDir + HOME: toBashPath(testDir), + USERPROFILE: testDir, + CLAUDE_PROJECT_DIR: toBashPath(worktreeDir) } }).toString(); diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js index 9cd2a16e..fe5d7e0e 100644 --- a/tests/hooks/hooks.test.js +++ b/tests/hooks/hooks.test.js @@ -16,7 +16,7 @@ function toBashPath(filePath) { } return String(filePath) - .replace(/^([A-Za-z]):/, (_, driveLetter) => `/mnt/${driveLetter.toLowerCase()}`) + .replace(/^([A-Za-z]):/, (_, driveLetter) => `/${driveLetter.toLowerCase()}`) .replace(/\\/g, '/'); } diff --git a/tests/lib/resolve-ecc-root.test.js b/tests/lib/resolve-ecc-root.test.js index f153c215..15b75540 100644 --- a/tests/lib/resolve-ecc-root.test.js +++ b/tests/lib/resolve-ecc-root.test.js @@ -50,6 +50,15 @@ function setupPluginCache(homeDir, orgName, version) { return cacheDir; } +function withHomeEnv(homeDir, extraEnv = {}) { + return { + PATH: process.env.PATH, + HOME: homeDir, + USERPROFILE: homeDir, + ...extraEnv, + }; +} + function runTests() { console.log('\n=== Testing resolve-ecc-root.js ===\n'); @@ -215,7 +224,7 @@ function runTests() { const result = execFileSync('node', [ '-e', `console.log(${INLINE_RESOLVE})`, ], { - env: { PATH: process.env.PATH, HOME: homeDir }, + env: withHomeEnv(homeDir), encoding: 'utf8', }).trim(); assert.strictEqual(result, expected); @@ -231,7 +240,7 @@ function runTests() { const result = execFileSync('node', [ '-e', `console.log(${INLINE_RESOLVE})`, ], { - env: { PATH: process.env.PATH, HOME: homeDir }, + env: withHomeEnv(homeDir), encoding: 'utf8', }).trim(); assert.strictEqual(result, path.join(homeDir, '.claude'));