Hotfix for issue #7730 (#7739)

Co-authored-by: Taylor Mullen <ntaylormullen@google.com>
Co-authored-by: Arya Gummadi <aryagummadi@google.com>
This commit is contained in:
Shreya Keshive
2025-09-04 12:46:02 -07:00
committed by GitHub
parent 76553622f6
commit 2fb14ead1f
4 changed files with 139 additions and 14 deletions

View File

@@ -83,7 +83,7 @@ vi.mock('@google/gemini-cli-core', async () => {
return { return {
...actualServer, ...actualServer,
IdeClient: { IdeClient: {
getInstance: vi.fn().mockReturnValue({ getInstance: vi.fn().mockResolvedValue({
getConnectionStatus: vi.fn(), getConnectionStatus: vi.fn(),
initialize: vi.fn(), initialize: vi.fn(),
shutdown: vi.fn(), shutdown: vi.fn(),

View File

@@ -100,6 +100,16 @@ vi.mock('../services/gitService.js', () => {
return { GitService: GitServiceMock }; return { GitService: GitServiceMock };
}); });
vi.mock('../ide/ide-client.js', () => ({
IdeClient: {
getInstance: vi.fn().mockResolvedValue({
getConnectionStatus: vi.fn(),
initialize: vi.fn(),
shutdown: vi.fn(),
}),
},
}));
describe('Server Config (config.ts)', () => { describe('Server Config (config.ts)', () => {
const MODEL = 'gemini-pro'; const MODEL = 'gemini-pro';
const SANDBOX: SandboxConfig = { const SANDBOX: SandboxConfig = {

View File

@@ -66,13 +66,34 @@ describe('getIdeProcessInfo', () => {
it('should traverse up and find the great-grandchild of the root process', async () => { it('should traverse up and find the great-grandchild of the root process', async () => {
(os.platform as Mock).mockReturnValue('win32'); (os.platform as Mock).mockReturnValue('win32');
const processInfoMap = new Map([ const processInfoMap = new Map([
[1000, { stdout: 'ParentProcessId=900\r\nCommandLine=node.exe\r\n' }], [
1000,
{
stdout:
'{"Name":"node.exe","ParentProcessId":900,"CommandLine":"node.exe"}',
},
],
[ [
900, 900,
{ stdout: 'ParentProcessId=800\r\nCommandLine=powershell.exe\r\n' }, {
stdout:
'{"Name":"powershell.exe","ParentProcessId":800,"CommandLine":"powershell.exe"}',
},
],
[
800,
{
stdout:
'{"Name":"code.exe","ParentProcessId":700,"CommandLine":"code.exe"}',
},
],
[
700,
{
stdout:
'{"Name":"wininit.exe","ParentProcessId":0,"CommandLine":"wininit.exe"}',
},
], ],
[800, { stdout: 'ParentProcessId=700\r\nCommandLine=code.exe\r\n' }],
[700, { stdout: 'ParentProcessId=0\r\nCommandLine=wininit.exe\r\n' }],
]); ]);
mockedExec.mockImplementation((command: string) => { mockedExec.mockImplementation((command: string) => {
const pidMatch = command.match(/ProcessId=(\d+)/); const pidMatch = command.match(/ProcessId=(\d+)/);
@@ -86,5 +107,90 @@ describe('getIdeProcessInfo', () => {
const result = await getIdeProcessInfo(); const result = await getIdeProcessInfo();
expect(result).toEqual({ pid: 900, command: 'powershell.exe' }); expect(result).toEqual({ pid: 900, command: 'powershell.exe' });
}); });
it('should handle non-existent process gracefully', async () => {
(os.platform as Mock).mockReturnValue('win32');
mockedExec
.mockResolvedValueOnce({ stdout: '' }) // Non-existent PID returns empty due to -ErrorAction SilentlyContinue
.mockResolvedValueOnce({
stdout:
'{"Name":"fallback.exe","ParentProcessId":0,"CommandLine":"fallback.exe"}',
}); // Fallback call
const result = await getIdeProcessInfo();
expect(result).toEqual({ pid: 1000, command: 'fallback.exe' });
});
it('should handle malformed JSON output gracefully', async () => {
(os.platform as Mock).mockReturnValue('win32');
mockedExec
.mockResolvedValueOnce({ stdout: '{"invalid":json}' }) // Malformed JSON
.mockResolvedValueOnce({
stdout:
'{"Name":"fallback.exe","ParentProcessId":0,"CommandLine":"fallback.exe"}',
}); // Fallback call
const result = await getIdeProcessInfo();
expect(result).toEqual({ pid: 1000, command: 'fallback.exe' });
});
it('should handle PowerShell errors without crashing the process chain', async () => {
(os.platform as Mock).mockReturnValue('win32');
const processInfoMap = new Map([
[1000, { stdout: '' }], // First process doesn't exist (empty due to -ErrorAction)
[
1001,
{
stdout:
'{"Name":"parent.exe","ParentProcessId":800,"CommandLine":"parent.exe"}',
},
],
[
800,
{
stdout:
'{"Name":"ide.exe","ParentProcessId":0,"CommandLine":"ide.exe"}',
},
],
]);
// Mock the process.pid to test traversal with missing processes
Object.defineProperty(process, 'pid', {
value: 1001,
configurable: true,
});
mockedExec.mockImplementation((command: string) => {
const pidMatch = command.match(/ProcessId=(\d+)/);
if (pidMatch) {
const pid = parseInt(pidMatch[1], 10);
return Promise.resolve(processInfoMap.get(pid) || { stdout: '' });
}
return Promise.reject(new Error('Invalid command for mock'));
});
const result = await getIdeProcessInfo();
// Should return the current process command since traversal continues despite missing processes
expect(result).toEqual({ pid: 1001, command: 'parent.exe' });
// Reset process.pid
Object.defineProperty(process, 'pid', {
value: 1000,
configurable: true,
});
});
it('should handle partial JSON data with defaults', async () => {
(os.platform as Mock).mockReturnValue('win32');
mockedExec
.mockResolvedValueOnce({ stdout: '{"Name":"partial.exe"}' }) // Missing ParentProcessId, defaults to 0
.mockResolvedValueOnce({
stdout:
'{"Name":"root.exe","ParentProcessId":0,"CommandLine":"root.exe"}',
}); // Get grandparent info
const result = await getIdeProcessInfo();
expect(result).toEqual({ pid: 1000, command: 'root.exe' });
});
}); });
}); });

View File

@@ -26,15 +26,24 @@ async function getProcessInfo(pid: number): Promise<{
}> { }> {
const platform = os.platform(); const platform = os.platform();
if (platform === 'win32') { if (platform === 'win32') {
const command = `wmic process where "ProcessId=${pid}" get Name,ParentProcessId,CommandLine /value`; const powershellCommand = [
const { stdout } = await execAsync(command); '$p = Get-CimInstance Win32_Process',
const nameMatch = stdout.match(/Name=([^\n]*)/); `-Filter 'ProcessId=${pid}'`,
const processName = nameMatch ? nameMatch[1].trim() : ''; '-ErrorAction SilentlyContinue;',
const ppidMatch = stdout.match(/ParentProcessId=(\d+)/); 'if ($p) {',
const parentPid = ppidMatch ? parseInt(ppidMatch[1], 10) : 0; '@{Name=$p.Name;ParentProcessId=$p.ParentProcessId;CommandLine=$p.CommandLine}',
const commandLineMatch = stdout.match(/CommandLine=([^\n]*)/); '| ConvertTo-Json',
const commandLine = commandLineMatch ? commandLineMatch[1].trim() : ''; '}',
return { parentPid, name: processName, command: commandLine }; ].join(' ');
const { stdout } = await execAsync(`powershell "${powershellCommand}"`);
const output = stdout.trim();
if (!output) return { parentPid: 0, name: '', command: '' };
const {
Name = '',
ParentProcessId = 0,
CommandLine = '',
} = JSON.parse(output);
return { parentPid: ParentProcessId, name: Name, command: CommandLine };
} else { } else {
const command = `ps -o ppid=,command= -p ${pid}`; const command = `ps -o ppid=,command= -p ${pid}`;
const { stdout } = await execAsync(command); const { stdout } = await execAsync(command);