| |
| |
| |
| |
| |
|
|
| import { vi } from 'vitest'; |
| import { |
| RELAUNCH_EXIT_CODE, |
| relaunchApp, |
| _resetRelaunchStateForTesting, |
| isStandardSea, |
| getScriptArgs, |
| isSeaEnvironment, |
| getSpawnConfig, |
| type ProcessWithSea, |
| } from './processUtils.js'; |
| import * as cleanup from './cleanup.js'; |
| import * as handleAutoUpdate from './handleAutoUpdate.js'; |
|
|
| vi.mock('./handleAutoUpdate.js', () => ({ |
| waitForUpdateCompletion: vi.fn().mockResolvedValue(undefined), |
| })); |
|
|
| describe('processUtils', () => { |
| const processExit = vi |
| .spyOn(process, 'exit') |
| .mockReturnValue(undefined as never); |
| const runExitCleanup = vi.spyOn(cleanup, 'runExitCleanup'); |
|
|
| beforeEach(() => { |
| _resetRelaunchStateForTesting(); |
| }); |
|
|
| afterEach(() => vi.clearAllMocks()); |
|
|
| it('should wait for updates, run cleanup, and exit with the relaunch code', async () => { |
| await relaunchApp(); |
| expect(handleAutoUpdate.waitForUpdateCompletion).toHaveBeenCalledTimes(1); |
| expect(runExitCleanup).toHaveBeenCalledTimes(1); |
| expect(processExit).toHaveBeenCalledWith(RELAUNCH_EXIT_CODE); |
| }); |
| }); |
|
|
| describe('SEA handling utilities', () => { |
| const originalArgv = process.argv; |
| const originalExecArgv = process.execArgv; |
| const originalExecPath = process.execPath; |
| const originalIsSea = (process as ProcessWithSea).isSea; |
|
|
| beforeEach(() => { |
| vi.unstubAllEnvs(); |
| vi.stubEnv('NODE_OPTIONS', ''); |
| process.argv = [...originalArgv]; |
| process.execArgv = [...originalExecArgv]; |
| process.execPath = '/fake/exec/path'; |
| delete (process as ProcessWithSea).isSea; |
| }); |
|
|
| afterEach(() => { |
| vi.unstubAllEnvs(); |
| process.argv = originalArgv; |
| process.execArgv = originalExecArgv; |
| process.execPath = originalExecPath; |
| if (originalIsSea) { |
| (process as ProcessWithSea).isSea = originalIsSea; |
| } else { |
| delete (process as ProcessWithSea).isSea; |
| } |
| }); |
|
|
| describe('isStandardSea', () => { |
| it('returns false if argv[0] === argv[1]', () => { |
| process.argv = ['/bin/gemini', '/bin/gemini', 'my-command']; |
| vi.stubEnv('IS_BINARY', 'true'); |
| expect(isStandardSea()).toBe(false); |
| }); |
|
|
| it('returns true if IS_BINARY is true and argv[0] !== argv[1]', () => { |
| process.argv = ['/bin/gemini', 'my-command']; |
| vi.stubEnv('IS_BINARY', 'true'); |
| expect(isStandardSea()).toBe(true); |
| }); |
|
|
| it('returns true if process.isSea() is true and argv[0] !== argv[1]', () => { |
| process.argv = ['/bin/gemini', 'my-command']; |
| (process as ProcessWithSea).isSea = () => true; |
| expect(isStandardSea()).toBe(true); |
| }); |
|
|
| it('returns false in standard node environment', () => { |
| process.argv = ['/bin/node', '/path/to/script.js', 'my-command']; |
| expect(isStandardSea()).toBe(false); |
| }); |
| }); |
|
|
| describe('getScriptArgs', () => { |
| it('slices from index 1 if isStandardSea is true', () => { |
| process.argv = ['/bin/gemini', 'my-command', '--flag']; |
| vi.stubEnv('IS_BINARY', 'true'); |
| expect(getScriptArgs()).toEqual(['my-command', '--flag']); |
| }); |
|
|
| it('slices from index 2 if isStandardSea is false (relaunch SEA or standard node)', () => { |
| |
| process.argv = ['/bin/gemini', '/bin/gemini', 'my-command', '--flag']; |
| vi.stubEnv('IS_BINARY', 'true'); |
| expect(getScriptArgs()).toEqual(['my-command', '--flag']); |
|
|
| |
| process.argv = ['/bin/node', '/path/to/script.js', 'my-command']; |
| vi.stubEnv('IS_BINARY', ''); |
| expect(getScriptArgs()).toEqual(['my-command']); |
| }); |
| }); |
|
|
| describe('isSeaEnvironment', () => { |
| it('returns true if IS_BINARY is true', () => { |
| vi.stubEnv('IS_BINARY', 'true'); |
| expect(isSeaEnvironment()).toBe(true); |
| }); |
|
|
| it('returns true if process.isSea() is true', () => { |
| (process as ProcessWithSea).isSea = () => true; |
| expect(isSeaEnvironment()).toBe(true); |
| }); |
|
|
| it('returns true if argv[0] === argv[1]', () => { |
| process.argv = ['/bin/gemini', '/bin/gemini']; |
| expect(isSeaEnvironment()).toBe(true); |
| }); |
|
|
| it('returns false otherwise', () => { |
| process.argv = ['/bin/node', '/path/to/script.js']; |
| expect(isSeaEnvironment()).toBe(false); |
| }); |
| }); |
|
|
| describe('getSpawnConfig', () => { |
| it('handles standard node mode', () => { |
| process.argv = ['/bin/node', '/path/to/script.js', 'my-command']; |
| process.execArgv = ['--inspect']; |
| process.execPath = '/bin/node'; |
|
|
| const config = getSpawnConfig( |
| ['--max-old-space-size=8192'], |
| ['my-command'], |
| ); |
|
|
| expect(config.spawnArgs).toEqual([ |
| '--inspect', |
| '--max-old-space-size=8192', |
| '/path/to/script.js', |
| 'my-command', |
| ]); |
| expect(config.env['GEMINI_CLI_NO_RELAUNCH']).toBe('true'); |
| expect(config.env['NODE_OPTIONS']).toBeFalsy(); |
| }); |
|
|
| it('handles SEA binary mode with new nodeArgs', () => { |
| vi.stubEnv('IS_BINARY', 'true'); |
| vi.stubEnv('NODE_OPTIONS', '--existing-flag'); |
| process.argv = ['/bin/gemini', 'my-command']; |
| process.execArgv = ['--inspect']; |
| process.execPath = '/bin/gemini'; |
|
|
| const config = getSpawnConfig( |
| ['--max-old-space-size=8192'], |
| ['my-command'], |
| ); |
|
|
| expect(config.spawnArgs).toEqual([ |
| '/bin/gemini', |
| 'my-command', |
| ]); |
| expect(config.env['NODE_OPTIONS']).toBe( |
| '--existing-flag --max-old-space-size=8192', |
| ); |
| expect(config.env['GEMINI_CLI_NO_RELAUNCH']).toBe('true'); |
| }); |
|
|
| it('throws error for complex nodeArgs in SEA mode', () => { |
| vi.stubEnv('IS_BINARY', 'true'); |
|
|
| expect(() => { |
| getSpawnConfig(['--title "My App"'], []); |
| }).toThrow( |
| 'Unsupported node argument for SEA relaunch: --title "My App". Complex escaping is not supported.', |
| ); |
|
|
| expect(() => { |
| getSpawnConfig(['--title=A\\B'], []); |
| }).toThrow(); |
| }); |
| }); |
| }); |
|
|