mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-06 13:45:35 +02:00
900a619f31
Round 3 of windows-free-tests fixes. Round 2 (LF gitattributes + server-node.mjs build) cleared shard 1 entirely (skill-collision-sentinel and tab-isolation green). Shard 2 surfaced two more issues: 1. browse/test/claude-bin.test.ts:50 — the "PATH-resolvable override" test creates a fake binary 'fake-claude-cli' (no extension) and expects Bun.which to find it. On Windows, Bun.which probes PATHEXT extensions (.cmd, .exe, .bat) — a bare-name file is not discoverable. Production behavior is correct; the test was Mac/Linux-shaped. Fix: branch on process.platform. On Windows, write 'fake-claude-cli.cmd' with a Windows batch payload instead of a POSIX shebang script. 2. test/gstack-question-log.test.ts (and 18 sibling tests) — spawn a bash shebang script via spawnSync(BIN, args). Git Bash on Windows can run `bash /path/to/script` but spawnSync invokes CreateProcess directly, which doesn't parse #!/usr/bin/env bash. All these tests are Windows-fragile and can't run as-is. Fix: extend WINDOWS_FRAGILE_PATTERNS with `path.join(.., 'bin', ..)` detector. Curates 19 additional tests (benchmark-cli, brain-sync, builder-profile, explain-level-config, gbrain-*, gstack-question-*, hook-scripts, learnings, plan-tune, review-log, secret-sink-harness, taste-engine, telemetry, timeline, uninstall). Curated Windows subset: 95 → 76 tests (~59% of free suite). Still meaningful Windows coverage. The 52 excluded tests are tracked as a follow-up TODO for full Windows parity (shebang-bin spawns + POSIX file modes + raw /tmp/ etc). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
96 lines
3.4 KiB
TypeScript
96 lines
3.4 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import * as path from 'path';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import { resolveClaudeCommand, resolveClaudeBinary } from '../src/claude-bin';
|
|
|
|
// Empty env baseline — no PATH, no overrides — ensures no environmental claude binary leaks in.
|
|
const EMPTY_ENV = { PATH: '', Path: '' } as NodeJS.ProcessEnv;
|
|
|
|
describe('claude-bin', () => {
|
|
test('no override, no PATH match → returns null', () => {
|
|
expect(resolveClaudeCommand(EMPTY_ENV)).toBeNull();
|
|
expect(resolveClaudeBinary(EMPTY_ENV)).toBeNull();
|
|
});
|
|
|
|
test('absolute-path override returned as-is', () => {
|
|
const got = resolveClaudeCommand({
|
|
...EMPTY_ENV,
|
|
GSTACK_CLAUDE_BIN: '/opt/custom/claude',
|
|
});
|
|
expect(got).toEqual({ command: '/opt/custom/claude', argsPrefix: [] });
|
|
});
|
|
|
|
test('CLAUDE_BIN works as fallback alias for GSTACK_CLAUDE_BIN', () => {
|
|
const got = resolveClaudeCommand({
|
|
...EMPTY_ENV,
|
|
CLAUDE_BIN: '/opt/custom/claude',
|
|
});
|
|
expect(got?.command).toBe('/opt/custom/claude');
|
|
});
|
|
|
|
test('GSTACK_CLAUDE_BIN takes precedence over CLAUDE_BIN', () => {
|
|
const got = resolveClaudeCommand({
|
|
...EMPTY_ENV,
|
|
GSTACK_CLAUDE_BIN: '/explicit/path',
|
|
CLAUDE_BIN: '/fallback/path',
|
|
});
|
|
expect(got?.command).toBe('/explicit/path');
|
|
});
|
|
|
|
test('PATH-resolvable override goes through Bun.which (the bug the fork shipped)', () => {
|
|
// Make a fake binary in a temp dir, point PATH at it, set override to bare command name.
|
|
// Windows requires the file to have a PATHEXT-listed extension to be discoverable
|
|
// via Bun.which — without the extension Bun.which returns undefined.
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-bin-test-'));
|
|
const isWindows = process.platform === 'win32';
|
|
const fakeBinName = isWindows ? 'fake-claude-cli.cmd' : 'fake-claude-cli';
|
|
const fakeBin = path.join(tmpDir, fakeBinName);
|
|
fs.writeFileSync(fakeBin, isWindows ? '@echo fake\r\n' : '#!/bin/sh\necho fake\n');
|
|
if (!isWindows) fs.chmodSync(fakeBin, 0o755);
|
|
try {
|
|
const got = resolveClaudeCommand({
|
|
PATH: tmpDir,
|
|
GSTACK_CLAUDE_BIN: 'fake-claude-cli',
|
|
});
|
|
expect(got?.command).toBe(fakeBin);
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('override pointing at missing binary → null (no silent fallback to bare claude)', () => {
|
|
const got = resolveClaudeCommand({
|
|
...EMPTY_ENV,
|
|
GSTACK_CLAUDE_BIN: 'definitely-not-a-real-binary-xyz',
|
|
});
|
|
expect(got).toBeNull();
|
|
});
|
|
|
|
test('GSTACK_CLAUDE_BIN_ARGS as JSON array → parsed argsPrefix', () => {
|
|
const got = resolveClaudeCommand({
|
|
...EMPTY_ENV,
|
|
GSTACK_CLAUDE_BIN: '/opt/custom/claude',
|
|
GSTACK_CLAUDE_BIN_ARGS: '["--no-cache", "--verbose"]',
|
|
});
|
|
expect(got?.argsPrefix).toEqual(['--no-cache', '--verbose']);
|
|
});
|
|
|
|
test('GSTACK_CLAUDE_BIN_ARGS as scalar string → treated as single argument', () => {
|
|
const got = resolveClaudeCommand({
|
|
...EMPTY_ENV,
|
|
GSTACK_CLAUDE_BIN: '/opt/custom/claude',
|
|
GSTACK_CLAUDE_BIN_ARGS: 'claude',
|
|
});
|
|
expect(got?.argsPrefix).toEqual(['claude']);
|
|
});
|
|
|
|
test('argsPrefix empty when no override args set', () => {
|
|
const got = resolveClaudeCommand({
|
|
...EMPTY_ENV,
|
|
GSTACK_CLAUDE_BIN: '/opt/custom/claude',
|
|
});
|
|
expect(got?.argsPrefix).toEqual([]);
|
|
});
|
|
});
|