fix: harden windows CI tests and markdown lint

This commit is contained in:
Affaan Mustafa
2026-03-20 03:03:57 -07:00
parent cbccb7fdc0
commit e70c43bcd4
6 changed files with 51 additions and 20 deletions

View File

@@ -71,7 +71,7 @@
## 归属 ## 归属
本行为准则改编自 \[贡献者公约]\[homepage] 2.0 版本,可访问 本行为准则改编自 [贡献者公约][homepage] 2.0 版本,可访问
<https://www.contributor-covenant.org/version/2/0/code_of_conduct.html> 获取。 <https://www.contributor-covenant.org/version/2/0/code_of_conduct.html> 获取。
社区影响指南的灵感来源于 [Mozilla 的行为准则执行阶梯](https://github.com/mozilla/diversity)。 社区影响指南的灵感来源于 [Mozilla 的行为准则执行阶梯](https://github.com/mozilla/diversity)。

View File

@@ -315,6 +315,6 @@ result = "".join(str(item) for item in items)
| 海象运算符 (`:=`) | 3.8+ | | 海象运算符 (`:=`) | 3.8+ |
| 仅限位置参数 | 3.8+ | | 仅限位置参数 | 3.8+ |
| Match 语句 | 3.10+ | | Match 语句 | 3.10+ |
| 类型联合 (\`x | None\`) | 3.10+ | | 类型联合 (`x \| None`) | 3.10+ |
确保你的项目 `pyproject.toml` 或 `setup.py` 指定了正确的最低 Python 版本。 确保你的项目 `pyproject.toml` 或 `setup.py` 指定了正确的最低 Python 版本。

View File

@@ -28,6 +28,13 @@ function makeTempDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'cost-tracker-test-')); return fs.mkdtempSync(path.join(os.tmpdir(), 'cost-tracker-test-'));
} }
function withTempHome(homeDir) {
return {
HOME: homeDir,
USERPROFILE: homeDir,
};
}
function runScript(input, envOverrides = {}) { function runScript(input, envOverrides = {}) {
const inputStr = typeof input === 'string' ? input : JSON.stringify(input); const inputStr = typeof input === 'string' ? input : JSON.stringify(input);
const result = spawnSync('node', [script], { const result = spawnSync('node', [script], {
@@ -64,7 +71,7 @@ function runTests() {
model: 'claude-sonnet-4-20250514', model: 'claude-sonnet-4-20250514',
usage: { input_tokens: 1000, output_tokens: 500 }, 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}`); assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
const metricsFile = path.join(tmpHome, '.claude', 'metrics', 'costs.jsonl'); const metricsFile = path.join(tmpHome, '.claude', 'metrics', 'costs.jsonl');
@@ -84,7 +91,7 @@ function runTests() {
// 3. Handles empty input gracefully // 3. Handles empty input gracefully
(test('handles empty input gracefully', () => { (test('handles empty input gracefully', () => {
const tmpHome = makeTempDir(); 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}`); assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
// stdout should be empty since input was empty // stdout should be empty since input was empty
assert.strictEqual(result.stdout, '', 'Expected empty stdout for empty input'); assert.strictEqual(result.stdout, '', 'Expected empty stdout for empty input');
@@ -96,7 +103,7 @@ function runTests() {
(test('handles invalid JSON gracefully', () => { (test('handles invalid JSON gracefully', () => {
const tmpHome = makeTempDir(); const tmpHome = makeTempDir();
const invalidInput = 'not valid json {{{'; 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}`); assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
// Should still pass through the raw input on stdout // Should still pass through the raw input on stdout
assert.strictEqual(result.stdout, invalidInput, 'Expected stdout to contain original invalid input'); assert.strictEqual(result.stdout, invalidInput, 'Expected stdout to contain original invalid input');
@@ -109,7 +116,7 @@ function runTests() {
const tmpHome = makeTempDir(); const tmpHome = makeTempDir();
const input = { model: 'claude-sonnet-4-20250514' }; const input = { model: 'claude-sonnet-4-20250514' };
const inputStr = JSON.stringify(input); 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.code, 0, `Expected exit code 0, got ${result.code}`);
assert.strictEqual(result.stdout, inputStr, 'Expected stdout to match original input'); assert.strictEqual(result.stdout, inputStr, 'Expected stdout to match original input');

View File

@@ -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 repoRoot = path.resolve(__dirname, '..', '..');
const detectProjectPath = path.join( const detectProjectPath = path.join(
repoRoot, repoRoot,
@@ -98,7 +112,7 @@ test('[ -d ] returns true for .git directory', () => {
const dir = path.join(behaviorDir, 'test-d-dir'); const dir = path.join(behaviorDir, 'test-d-dir');
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
fs.mkdirSync(path.join(dir, '.git')); 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'); assert.strictEqual(result, 'yes');
}); });
@@ -106,7 +120,7 @@ test('[ -d ] returns false for .git file', () => {
const dir = path.join(behaviorDir, 'test-d-file'); const dir = path.join(behaviorDir, 'test-d-file');
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, '.git'), 'gitdir: /some/path\n'); 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'); assert.strictEqual(result, 'no');
}); });
@@ -114,7 +128,7 @@ test('[ -e ] returns true for .git directory', () => {
const dir = path.join(behaviorDir, 'test-e-dir'); const dir = path.join(behaviorDir, 'test-e-dir');
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
fs.mkdirSync(path.join(dir, '.git')); 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'); assert.strictEqual(result, 'yes');
}); });
@@ -122,14 +136,14 @@ test('[ -e ] returns true for .git file', () => {
const dir = path.join(behaviorDir, 'test-e-file'); const dir = path.join(behaviorDir, 'test-e-file');
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, '.git'), 'gitdir: /some/path\n'); 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'); assert.strictEqual(result, 'yes');
}); });
test('[ -e ] returns false when .git does not exist', () => { test('[ -e ] returns false when .git does not exist', () => {
const dir = path.join(behaviorDir, 'test-e-none'); const dir = path.join(behaviorDir, 'test-e-none');
fs.mkdirSync(dir, { recursive: true }); 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'); 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 // Source detect-project.sh from the worktree directory and capture results
const script = ` const script = `
export CLAUDE_PROJECT_DIR="${worktreeDir}" export CLAUDE_PROJECT_DIR="${toBashPath(worktreeDir)}"
export HOME="${testDir}" export HOME="${toBashPath(testDir)}"
source "${detectProjectPath}" source "${toBashPath(detectProjectPath)}"
echo "PROJECT_NAME=\${PROJECT_NAME}" echo "PROJECT_NAME=\${PROJECT_NAME}"
echo "PROJECT_ID=\${PROJECT_ID}" echo "PROJECT_ID=\${PROJECT_ID}"
`; `;
const result = execSync(`bash -c '${script.replace(/'/g, "'\\''")}'`, { const result = execSync(`bash -lc '${script.replace(/'/g, "'\\''")}'`, {
cwd: worktreeDir, cwd: worktreeDir,
timeout: 10000, timeout: 10000,
env: { env: {
...process.env, ...process.env,
HOME: testDir, HOME: toBashPath(testDir),
CLAUDE_PROJECT_DIR: worktreeDir USERPROFILE: testDir,
CLAUDE_PROJECT_DIR: toBashPath(worktreeDir)
} }
}).toString(); }).toString();

View File

@@ -16,7 +16,7 @@ function toBashPath(filePath) {
} }
return String(filePath) return String(filePath)
.replace(/^([A-Za-z]):/, (_, driveLetter) => `/mnt/${driveLetter.toLowerCase()}`) .replace(/^([A-Za-z]):/, (_, driveLetter) => `/${driveLetter.toLowerCase()}`)
.replace(/\\/g, '/'); .replace(/\\/g, '/');
} }

View File

@@ -50,6 +50,15 @@ function setupPluginCache(homeDir, orgName, version) {
return cacheDir; return cacheDir;
} }
function withHomeEnv(homeDir, extraEnv = {}) {
return {
PATH: process.env.PATH,
HOME: homeDir,
USERPROFILE: homeDir,
...extraEnv,
};
}
function runTests() { function runTests() {
console.log('\n=== Testing resolve-ecc-root.js ===\n'); console.log('\n=== Testing resolve-ecc-root.js ===\n');
@@ -215,7 +224,7 @@ function runTests() {
const result = execFileSync('node', [ const result = execFileSync('node', [
'-e', `console.log(${INLINE_RESOLVE})`, '-e', `console.log(${INLINE_RESOLVE})`,
], { ], {
env: { PATH: process.env.PATH, HOME: homeDir }, env: withHomeEnv(homeDir),
encoding: 'utf8', encoding: 'utf8',
}).trim(); }).trim();
assert.strictEqual(result, expected); assert.strictEqual(result, expected);
@@ -231,7 +240,7 @@ function runTests() {
const result = execFileSync('node', [ const result = execFileSync('node', [
'-e', `console.log(${INLINE_RESOLVE})`, '-e', `console.log(${INLINE_RESOLVE})`,
], { ], {
env: { PATH: process.env.PATH, HOME: homeDir }, env: withHomeEnv(homeDir),
encoding: 'utf8', encoding: 'utf8',
}).trim(); }).trim();
assert.strictEqual(result, path.join(homeDir, '.claude')); assert.strictEqual(result, path.join(homeDir, '.claude'));