diff --git a/src/shared/setup-agent-sequencing.test.ts b/src/shared/setup-agent-sequencing.test.ts index 5bff3422f95..3d51b5b7476 100644 --- a/src/shared/setup-agent-sequencing.test.ts +++ b/src/shared/setup-agent-sequencing.test.ts @@ -20,6 +20,7 @@ import { } from './setup-agent-startup-policy' const TEMP_DIRS: string[] = [] +const WINDOWS_PROCESS_TEST_TIMEOUT_MS = 30_000 afterEach(() => { for (const dir of TEMP_DIRS.splice(0)) { @@ -330,7 +331,8 @@ describe('createSequencedSetupAgentCommands', () => { expect(startupExit.code).toBe(0) expect(startupExit.stderr).toContain('Waiting for setup to finish before starting agent...') expect(readFileSync(logPath, 'utf8')).toBe('setup-done\r\nagent-start\r\n') - } + }, + WINDOWS_PROCESS_TEST_TIMEOUT_MS ) it.skipIf(process.platform === 'win32')( diff --git a/src/shared/setup-agent-sequencing.windows.test.ts b/src/shared/setup-agent-sequencing.windows.test.ts index b964eb241a7..10b1e76d908 100644 --- a/src/shared/setup-agent-sequencing.windows.test.ts +++ b/src/shared/setup-agent-sequencing.windows.test.ts @@ -11,6 +11,7 @@ import { } from './setup-agent-sequencing' const TEMP_DIRS: string[] = [] +const WINDOWS_PROCESS_TEST_TIMEOUT_MS = 30_000 afterEach(() => { for (const dir of TEMP_DIRS.splice(0)) { @@ -20,61 +21,69 @@ afterEach(() => { describe.skipIf(process.platform !== 'win32')('Windows setup-agent sequencing', () => { it.each([ - 'path with spaces', - 'ampersand&parentheses(test)', - 'caret^percent%bang!', - "apostrophe's directory", - 'Unicode-한글-abc' - ])('preserves the native runner path in %s', async (directoryName) => { - const tempDir = makeTempDir(directoryName) - const runnerScriptPath = join(tempDir, 'setup runner.cmd') - const startupScriptPath = join(tempDir, 'agent startup.ps1') - const logPath = join(dirname(tempDir), 'sequence.log') - const prompt = 'spaces & pipe | caret ^ percent % bang ! "quotes" Unicode 한글 trailing\\' + ['path with spaces', false], + ['ampersand&parentheses(test)', false], + ['caret^percent%bang!', false], + ["apostrophe's directory", false], + ['Unicode-한글-abc', false], + ['forward-slash Git path', true] + ] as const)( + 'preserves the native runner path in %s', + async (directoryName, useForwardSlashes) => { + const tempDir = makeTempDir(directoryName) + const nativeRunnerScriptPath = join(tempDir, 'setup runner.cmd') + const runnerScriptPath = useForwardSlashes + ? nativeRunnerScriptPath.replaceAll('\\', '/') + : nativeRunnerScriptPath + const startupScriptPath = join(tempDir, 'agent startup.ps1') + const logPath = join(dirname(tempDir), 'sequence.log') + const prompt = 'spaces & pipe | caret ^ percent % bang ! "quotes" Unicode 한글 trailing\\' - writeFileSync( - runnerScriptPath, - ['@echo off', `>> "${logPath}" echo setup-done`, 'exit /b 0'].join('\r\n'), - 'utf8' - ) - writeFileSync( - startupScriptPath, - [ - 'param([string]$Value)', - '$utf8 = [System.Text.UTF8Encoding]::new($false)', - `[System.IO.File]::AppendAllText('${quotePowerShell(logPath)}', $Value + [Environment]::NewLine, $utf8)` - ].join('\r\n'), - 'utf8' - ) - - const commands = createSequencedSetupAgentCommands({ - runnerScriptPath, - startupCommand: `& '${quotePowerShell(startupScriptPath)}' '${quotePowerShell(prompt)}'`, - platform: 'windows', - nonce: 'windows-sequence', - waitTimeoutSeconds: 2 - }) - - const setupExit = await waitForExit( - spawnWindowsCommand(dirname(tempDir), 'run setup.cmd', commands.setupCommand) - ) - expect(setupExit.code).toBe(0) - expect(readFileSync(`${runnerScriptPath}.windows-sequence.done`, 'utf8')).toBe( - 'windows-sequence:0\r\n' - ) - - const startupExit = await waitForExit( - spawnWindowsCommand( - dirname(tempDir), - 'run startup.cmd', - commands.startupCommand, - commands.startupEnv + writeFileSync( + runnerScriptPath, + ['@echo off', `>> "${logPath}" echo setup-done`, 'exit /b 0'].join('\r\n'), + 'utf8' ) - ) - expect(startupExit.code).toBe(0) - expect(startupExit.stderr).toContain('Waiting for setup to finish before starting agent...') - expect(readFileSync(logPath, 'utf8')).toBe(`setup-done\r\n${prompt}\r\n`) - }) + writeFileSync( + startupScriptPath, + [ + 'param([string]$Value)', + '$utf8 = [System.Text.UTF8Encoding]::new($false)', + `[System.IO.File]::AppendAllText('${quotePowerShell(logPath)}', $Value + [Environment]::NewLine, $utf8)` + ].join('\r\n'), + 'utf8' + ) + + const commands = createSequencedSetupAgentCommands({ + runnerScriptPath, + startupCommand: `& '${quotePowerShell(startupScriptPath)}' '${quotePowerShell(prompt)}'`, + platform: 'windows', + nonce: 'windows-sequence', + waitTimeoutSeconds: 2 + }) + + const setupExit = await waitForExit( + spawnWindowsCommand(dirname(tempDir), 'run setup.cmd', commands.setupCommand) + ) + expect(setupExit.code).toBe(0) + expect(readFileSync(`${runnerScriptPath}.windows-sequence.done`, 'utf8')).toBe( + 'windows-sequence:0\r\n' + ) + + const startupExit = await waitForExit( + spawnWindowsCommand( + dirname(tempDir), + 'run startup.cmd', + commands.startupCommand, + commands.startupEnv + ) + ) + expect(startupExit.code).toBe(0) + expect(startupExit.stderr).toContain('Waiting for setup to finish before starting agent...') + expect(readFileSync(logPath, 'utf8')).toBe(`setup-done\r\n${prompt}\r\n`) + }, + WINDOWS_PROCESS_TEST_TIMEOUT_MS + ) it('keeps the startup command out of generated cmd.exe source', () => { const startupCommand = 'agent --prompt "& | ^ % ! 한글 trailing\\"' @@ -90,46 +99,50 @@ describe.skipIf(process.platform !== 'win32')('Windows setup-agent sequencing', expect(commands.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]).toBe(startupCommand) }) - it('propagates setup failure without launching the agent', async () => { - const tempDir = makeTempDir('failure path & metacharacters!') - const runnerScriptPath = join(tempDir, 'setup runner.cmd') - const startupScriptPath = join(tempDir, 'agent startup.cmd') - const startupLogPath = join(dirname(tempDir), 'agent-started.log') + it( + 'propagates setup failure without launching the agent', + async () => { + const tempDir = makeTempDir('failure path & metacharacters!') + const runnerScriptPath = join(tempDir, 'setup runner.cmd') + const startupScriptPath = join(tempDir, 'agent startup.cmd') + const startupLogPath = join(dirname(tempDir), 'agent-started.log') - writeFileSync(runnerScriptPath, '@echo off\r\nexit /b 37\r\n', 'utf8') - writeFileSync( - startupScriptPath, - `@echo off\r\necho started>"${startupLogPath}"\r\nexit /b 0\r\n`, - 'utf8' - ) - const commands = createSequencedSetupAgentCommands({ - runnerScriptPath, - startupCommand: `cmd.exe /d /c "${startupScriptPath}"`, - platform: 'windows', - nonce: 'failed-windows-sequence', - waitTimeoutSeconds: 2 - }) - - const setupExit = await waitForExit( - spawnWindowsCommand(dirname(tempDir), 'run failed setup.cmd', commands.setupCommand) - ) - expect(setupExit.code).toBe(37) - expect(readFileSync(`${runnerScriptPath}.failed-windows-sequence.done`, 'utf8')).toBe( - 'failed-windows-sequence:37\r\n' - ) - - const startupExit = await waitForExit( - spawnWindowsCommand( - dirname(tempDir), - 'run blocked startup.cmd', - commands.startupCommand, - commands.startupEnv + writeFileSync(runnerScriptPath, '@echo off\r\nexit /b 37\r\n', 'utf8') + writeFileSync( + startupScriptPath, + `@echo off\r\necho started>"${startupLogPath}"\r\nexit /b 0\r\n`, + 'utf8' ) - ) - expect(startupExit.code).toBe(37) - expect(startupExit.stderr).toContain('Setup failed; skipping agent startup.') - expect(existsSync(startupLogPath)).toBe(false) - }) + const commands = createSequencedSetupAgentCommands({ + runnerScriptPath, + startupCommand: `cmd.exe /d /c "${startupScriptPath}"`, + platform: 'windows', + nonce: 'failed-windows-sequence', + waitTimeoutSeconds: 2 + }) + + const setupExit = await waitForExit( + spawnWindowsCommand(dirname(tempDir), 'run failed setup.cmd', commands.setupCommand) + ) + expect(setupExit.code).toBe(37) + expect(readFileSync(`${runnerScriptPath}.failed-windows-sequence.done`, 'utf8')).toBe( + 'failed-windows-sequence:37\r\n' + ) + + const startupExit = await waitForExit( + spawnWindowsCommand( + dirname(tempDir), + 'run blocked startup.cmd', + commands.startupCommand, + commands.startupEnv + ) + ) + expect(startupExit.code).toBe(37) + expect(startupExit.stderr).toContain('Setup failed; skipping agent startup.') + expect(existsSync(startupLogPath)).toBe(false) + }, + WINDOWS_PROCESS_TEST_TIMEOUT_MS + ) }) function makeTempDir(directoryName: string): string {