perf(core): optimize Windows IDE process detection from O(N) to O(1) (#11048)

This commit is contained in:
Abhi
2025-10-13 22:42:09 -04:00
committed by GitHub
parent 9185f68e52
commit 6787d42de4
2 changed files with 196 additions and 180 deletions
+88 -102
View File
@@ -65,132 +65,118 @@ describe('getIdeProcessInfo', () => {
describe('on Windows', () => {
it('should traverse up and find the great-grandchild of the root process', async () => {
(os.platform as Mock).mockReturnValue('win32');
const processInfoMap = new Map([
[
1000,
{
stdout:
'{"Name":"node.exe","ParentProcessId":900,"CommandLine":"node.exe"}',
},
],
[
900,
{
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"}',
},
],
]);
mockedExec.mockImplementation((command: string) => {
const pidMatch = command.match(/ProcessId=(\d+)/);
if (pidMatch) {
const pid = parseInt(pidMatch[1], 10);
return Promise.resolve(processInfoMap.get(pid));
}
return Promise.reject(new Error('Invalid command for mock'));
});
// process (1000) -> powershell (900) -> code (800) -> wininit (700) -> root (0)
// Ancestors: [1000, 900, 800, 700]
// Target (great-grandchild of root): 900
const processes = [
{
ProcessId: 1000,
ParentProcessId: 900,
Name: 'node.exe',
CommandLine: 'node.exe',
},
{
ProcessId: 900,
ParentProcessId: 800,
Name: 'powershell.exe',
CommandLine: 'powershell.exe',
},
{
ProcessId: 800,
ParentProcessId: 700,
Name: 'code.exe',
CommandLine: 'code.exe',
},
{
ProcessId: 700,
ParentProcessId: 0,
Name: 'wininit.exe',
CommandLine: 'wininit.exe',
},
];
mockedExec.mockResolvedValueOnce({ stdout: JSON.stringify(processes) });
const result = await getIdeProcessInfo();
expect(result).toEqual({ pid: 900, command: 'powershell.exe' });
expect(mockedExec).toHaveBeenCalledWith(
expect.stringContaining('Get-CimInstance Win32_Process'),
expect.anything(),
);
});
it('should handle non-existent process gracefully', async () => {
it('should handle short process chains', 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
// process (1000) -> root (0)
const processes = [
{
ProcessId: 1000,
ParentProcessId: 0,
Name: 'node.exe',
CommandLine: 'node.exe',
},
];
mockedExec.mockResolvedValueOnce({ stdout: JSON.stringify(processes) });
const result = await getIdeProcessInfo();
expect(result).toEqual({ pid: 1000, command: 'fallback.exe' });
expect(result).toEqual({ pid: 1000, command: 'node.exe' });
});
it('should handle PowerShell failure gracefully', async () => {
(os.platform as Mock).mockReturnValue('win32');
mockedExec.mockRejectedValueOnce(new Error('PowerShell failed'));
// Fallback to getProcessInfo for current PID
mockedExec.mockResolvedValueOnce({ stdout: '' }); // ps command fails on windows
const result = await getIdeProcessInfo();
expect(result).toEqual({ pid: 1000, command: '' });
});
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
mockedExec.mockResolvedValueOnce({ stdout: '{"invalid":json}' });
// Fallback to getProcessInfo for current PID
mockedExec.mockResolvedValueOnce({ stdout: '' });
const result = await getIdeProcessInfo();
expect(result).toEqual({ pid: 1000, command: 'fallback.exe' });
expect(result).toEqual({ pid: 1000, command: '' });
});
it('should handle PowerShell errors without crashing the process chain', async () => {
it('should handle single process output from ConvertTo-Json', 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 process = {
ProcessId: 1000,
ParentProcessId: 0,
Name: 'node.exe',
CommandLine: 'node.exe',
};
mockedExec.mockResolvedValueOnce({ stdout: JSON.stringify(process) });
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,
});
expect(result).toEqual({ pid: 1000, command: 'node.exe' });
});
it('should handle partial JSON data with defaults', async () => {
it('should handle missing process in map during traversal', 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
// process (1000) -> parent (900) -> missing (800)
const processes = [
{
ProcessId: 1000,
ParentProcessId: 900,
Name: 'node.exe',
CommandLine: 'node.exe',
},
{
ProcessId: 900,
ParentProcessId: 800,
Name: 'parent.exe',
CommandLine: 'parent.exe',
},
];
mockedExec.mockResolvedValueOnce({ stdout: JSON.stringify(processes) });
const result = await getIdeProcessInfo();
expect(result).toEqual({ pid: 1000, command: 'root.exe' });
// Ancestors: [1000, 900]. Length < 3, returns last (900)
expect(result).toEqual({ pid: 900, command: 'parent.exe' });
});
});
});