test(windows): spawn cmd.exe hooks verbatim, use a .invalid UNC host, and match CRLF fixtures and native separators

windows-free-tests reds on 364f0f3, each verified on windows-latest
(46 pass, 1 POSIX-only skip, 0 fail):
- investigate-freeze-path cmd.exe cases: Bun MSVC-quoted the command line
  cmd.exe parses, so cmd saw \"bash and failed; Node's shell spawn (what
  Claude Code uses) passes it verbatim.
- investigate-freeze-path UNC cases: a single-label host made each
  filesystem probe wait on network name resolution (cd 11 s, -L 2.8 s);
  a reserved .invalid host fails at once (55-80 ms).
- paid-report-census: the junit fixture is CRLF in a Windows checkout.
- codex-invocation-sites: rendered paths use backslashes on Windows.
This commit is contained in:
garrytan committed 2026-10-04 00:58:31 +00:00
1 parent 9d37bb4a4f
commit dfaf15449c
3 files changed
+11 -6

No files matched your search

+1 -1
View File
@@ -53,7 +53,7 @@ function render(host: string): string[] {
} }
describe('every rendered Codex invocation is classified by the shared validator', () => { describe('every rendered Codex invocation is classified by the shared validator', () => {
const rendered = Object.fromEntries(['claude', 'codex'].map(host => [host, render(host).flatMap(f => codexSites(fs.readFileSync(f, 'utf8'), path.relative(TMP, f)))])); const rendered = Object.fromEntries(['claude', 'codex'].map(host => [host, render(host).flatMap(f => codexSites(fs.readFileSync(f, 'utf8'), path.relative(TMP, f).split(path.sep).join('/')))]));
test('the Claude render has the expected invocation sites', () => { test('the Claude render has the expected invocation sites', () => {
const files = new Set(rendered.claude!.map(s => s.file.replace(/^claude\//, ''))); const files = new Set(rendered.claude!.map(s => s.file.replace(/^claude\//, '')));
+8 -3
View File
@@ -80,9 +80,11 @@ describe('E1: freeze boundary and target share one platform-aware normalization
['C:\\dev\\proj\\src\\', 'C:\\dev\\proj\\src-old\\a.py', 'deny'], ['C:\\dev\\proj\\src\\', 'C:\\dev\\proj\\src-old\\a.py', 'deny'],
['C:\\dev\\proj\\src\\', 'D:\\dev\\proj\\src\\a.py', 'deny'], ['C:\\dev\\proj\\src\\', 'D:\\dev\\proj\\src\\a.py', 'deny'],
['C:\\dev\\proj\\src\\', 'C:\\dev\\proj\\other\\missing\\a.py', 'deny'], ['C:\\dev\\proj\\src\\', 'C:\\dev\\proj\\other\\missing\\a.py', 'deny'],
['\\\\server\\share\\proj\\', '\\\\server\\share\\proj\\a.py', 'allow'], // A reserved .invalid host fails name resolution at once; a single-label
['\\\\server\\share\\proj\\', '\\\\server\\other\\proj\\a.py', 'deny'], // host makes Windows probe the network for ~10 s per filesystem call.
['\\\\server\\share\\proj\\', '/server/share/proj/a.py', 'deny'], ['\\\\server.invalid\\share\\proj\\', '\\\\server.invalid\\share\\proj\\a.py', 'allow'],
['\\\\server.invalid\\share\\proj\\', '\\\\server.invalid\\other\\proj\\a.py', 'deny'],
['\\\\server.invalid\\share\\proj\\', '/server.invalid/share/proj/a.py', 'deny'],
])('boundary %s, target %s → %s', (boundary, target, expected) => { ])('boundary %s, target %s → %s', (boundary, target, expected) => {
expect(freezeDecision(boundary, target)).toBe(expected); expect(freezeDecision(boundary, target)).toBe(expected);
}); });
@@ -196,10 +198,13 @@ describe('E1: registered hook commands run under cmd.exe and POSIX sh (#2354)',
for (const [label, argv] of shells) { for (const [label, argv] of shells) {
test(`${rel} hook #${i + 1} reaches a decision via ${label}`, () => { test(`${rel} hook #${i + 1} reaches a decision via ${label}`, () => {
withInstalledHome((home) => withState(path.join(home, 'frozen'), (env) => { withInstalledHome((home) => withState(path.join(home, 'frozen'), (env) => {
// Claude Code hands cmd.exe its command line verbatim (Node's shell
// spawn); MSVC-quoting it would escape the inner quotes cmd.exe reads.
const r = Bun.spawnSync(argv(home), { const r = Bun.spawnSync(argv(home), {
stdin: Buffer.from(JSON.stringify(input)), stdin: Buffer.from(JSON.stringify(input)),
env: { ...env, HOME: home }, env: { ...env, HOME: home },
timeout: 15_000, timeout: 15_000,
windowsVerbatimArguments: IS_WIN,
}); });
expect(r.exitCode, r.stderr.toString()).toBe(0); expect(r.exitCode, r.stderr.toString()).toBe(0);
expect(decision(r.stdout.toString())).toBe(expected); expect(decision(r.stdout.toString())).toBe(expected);
+2 -2
View File
@@ -74,8 +74,8 @@ describe('census attribution from the 2026-10-03 artifacts', () => {
const qa = junit(dir, 'skill-e2e-qa-workflow'); const qa = junit(dir, 'skill-e2e-qa-workflow');
fs.writeFileSync(qa, fs.readFileSync(qa, 'utf8').replace(/(<testcase name="qa-fix-loop"[^>]*?) \/>/, '$1><skipped /></testcase>')); fs.writeFileSync(qa, fs.readFileSync(qa, 'utf8').replace(/(<testcase name="qa-fix-loop"[^>]*?) \/>/, '$1><skipped /></testcase>'));
const cso = junit(dir, 'skill-e2e-cso'); const cso = junit(dir, 'skill-e2e-cso');
fs.writeFileSync(cso, fs.readFileSync(cso, 'utf8').replace('</testsuite>\n</testsuites>', fs.writeFileSync(cso, fs.readFileSync(cso, 'utf8').replace(/<\/testsuite>(\r?\n)<\/testsuites>/,
'<testcase name="an unlabeled helper" classname="CSO v3" time="1" file="test/skill-e2e-cso.test.ts" line="9" />\n</testsuite>\n</testsuites>')); '<testcase name="an unlabeled helper" classname="CSO v3" time="1" file="test/skill-e2e-cso.test.ts" line="9" />$1</testsuite>$1</testsuites>'));
}); });
expect(r.status, r.out).toBe(0); expect(r.status, r.out).toBe(0);
expect(r.out).toContain(' SKIPPED 1 · INFRA 0 · INCOMPLETE 0 · unattributed 1 · ACTION REQUIRED 0'); expect(r.out).toContain(' SKIPPED 1 · INFRA 0 · INCOMPLETE 0 · unattributed 1 · ACTION REQUIRED 0');