mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 13:22:35 -07:00
197 lines
6.0 KiB
TypeScript
197 lines
6.0 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
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)', () => {
|
|
// Relaunch SEA
|
|
process.argv = ['/bin/gemini', '/bin/gemini', 'my-command', '--flag'];
|
|
vi.stubEnv('IS_BINARY', 'true');
|
|
expect(getScriptArgs()).toEqual(['my-command', '--flag']);
|
|
|
|
// Standard node
|
|
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']; // Should not be duplicated in NODE_OPTIONS
|
|
process.execPath = '/bin/gemini';
|
|
|
|
const config = getSpawnConfig(
|
|
['--max-old-space-size=8192'],
|
|
['my-command'],
|
|
);
|
|
|
|
expect(config.spawnArgs).toEqual([
|
|
'/bin/gemini', // explicitly uses execPath as placeholder
|
|
'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();
|
|
});
|
|
});
|
|
});
|